1#!/usr/bin/python3 2# Extract information from C headers. 3# Copyright (C) 2018-2021 Free Software Foundation, Inc. 4# This file is part of the GNU C Library. 5# 6# The GNU C Library is free software; you can redistribute it and/or 7# modify it under the terms of the GNU Lesser General Public 8# License as published by the Free Software Foundation; either 9# version 2.1 of the License, or (at your option) any later version. 10# 11# The GNU C Library is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14# Lesser General Public License for more details. 15# 16# You should have received a copy of the GNU Lesser General Public 17# License along with the GNU C Library; if not, see 18# <https://www.gnu.org/licenses/>. 19 20import os.path 21import re 22import subprocess 23import tempfile 24 25 26def compute_c_consts(sym_data, cc): 27 """Compute the values of some C constants. 28 29 The first argument is a list whose elements are either strings 30 (preprocessor directives, or the special string 'START' to 31 indicate this function should insert its initial boilerplate text 32 in the output there) or pairs of strings (a name and a C 33 expression for the corresponding value). Preprocessor directives 34 in the middle of the list may be used to select which constants 35 end up being evaluated using which expressions. 36 37 """ 38 out_lines = [] 39 for arg in sym_data: 40 if isinstance(arg, str): 41 if arg == 'START': 42 out_lines.append('void\ndummy (void)\n{') 43 else: 44 out_lines.append(arg) 45 continue 46 name = arg[0] 47 value = arg[1] 48 out_lines.append('asm ("/* @@@name@@@%s@@@value@@@%%0@@@end@@@ */" ' 49 ': : \"i\" ((long int) (%s)));' 50 % (name, value)) 51 out_lines.append('}') 52 out_lines.append('') 53 out_text = '\n'.join(out_lines) 54 with tempfile.TemporaryDirectory() as temp_dir: 55 c_file_name = os.path.join(temp_dir, 'test.c') 56 s_file_name = os.path.join(temp_dir, 'test.s') 57 with open(c_file_name, 'w') as c_file: 58 c_file.write(out_text) 59 # Compilation has to be from stdin to avoid the temporary file 60 # name being written into the generated dependencies. 61 cmd = ('%s -S -o %s -x c - < %s' % (cc, s_file_name, c_file_name)) 62 subprocess.check_call(cmd, shell=True) 63 consts = {} 64 with open(s_file_name, 'r') as s_file: 65 for line in s_file: 66 match = re.search('@@@name@@@([^@]*)' 67 '@@@value@@@[^0-9Xxa-fA-F-]*' 68 '([0-9Xxa-fA-F-]+).*@@@end@@@', line) 69 if match: 70 if (match.group(1) in consts 71 and match.group(2) != consts[match.group(1)]): 72 raise ValueError('duplicate constant %s' 73 % match.group(1)) 74 consts[match.group(1)] = match.group(2) 75 return consts 76 77 78def list_macros(source_text, cc): 79 """List the preprocessor macros defined by the given source code. 80 81 The return value is a pair of dicts, the first one mapping macro 82 names to their expansions and the second one mapping macro names 83 to lists of their arguments, or to None for object-like macros. 84 85 """ 86 with tempfile.TemporaryDirectory() as temp_dir: 87 c_file_name = os.path.join(temp_dir, 'test.c') 88 i_file_name = os.path.join(temp_dir, 'test.i') 89 with open(c_file_name, 'w') as c_file: 90 c_file.write(source_text) 91 cmd = ('%s -E -dM -o %s %s' % (cc, i_file_name, c_file_name)) 92 subprocess.check_call(cmd, shell=True) 93 macros_exp = {} 94 macros_args = {} 95 with open(i_file_name, 'r') as i_file: 96 for line in i_file: 97 match = re.fullmatch('#define ([0-9A-Za-z_]+)(.*)\n', line) 98 if not match: 99 raise ValueError('bad -dM output line: %s' % line) 100 name = match.group(1) 101 value = match.group(2) 102 if value.startswith(' '): 103 value = value[1:] 104 args = None 105 elif value.startswith('('): 106 match = re.fullmatch(r'\((.*?)\) (.*)', value) 107 if not match: 108 raise ValueError('bad -dM output line: %s' % line) 109 args = match.group(1).split(',') 110 value = match.group(2) 111 else: 112 raise ValueError('bad -dM output line: %s' % line) 113 if name in macros_exp: 114 raise ValueError('duplicate macro: %s' % line) 115 macros_exp[name] = value 116 macros_args[name] = args 117 return macros_exp, macros_args 118 119 120def compute_macro_consts(source_text, cc, macro_re, exclude_re=None): 121 """Compute the integer constant values of macros defined by source_text. 122 123 Macros must match the regular expression macro_re, and if 124 exclude_re is defined they must not match exclude_re. Values are 125 computed with compute_c_consts. 126 127 """ 128 macros_exp, macros_args = list_macros(source_text, cc) 129 macros_set = {m for m in macros_exp 130 if (macros_args[m] is None 131 and re.fullmatch(macro_re, m) 132 and (exclude_re is None 133 or not re.fullmatch(exclude_re, m)))} 134 sym_data = [source_text, 'START'] 135 sym_data.extend(sorted((m, m) for m in macros_set)) 136 return compute_c_consts(sym_data, cc) 137 138 139def compare_macro_consts(source_1, source_2, cc, macro_re, exclude_re=None, 140 allow_extra_1=False, allow_extra_2=False): 141 """Compare the values of macros defined by two different sources. 142 143 The sources would typically be includes of a glibc header and a 144 kernel header. If allow_extra_1, the first source may define 145 extra macros (typically if the kernel headers are older than the 146 version glibc has taken definitions from); if allow_extra_2, the 147 second source may define extra macros (typically if the kernel 148 headers are newer than the version glibc has taken definitions 149 from). Return 1 if there were any differences other than those 150 allowed, 0 if the macro values were the same apart from any 151 allowed differences. 152 153 """ 154 macros_1 = compute_macro_consts(source_1, cc, macro_re, exclude_re) 155 macros_2 = compute_macro_consts(source_2, cc, macro_re, exclude_re) 156 if macros_1 == macros_2: 157 return 0 158 print('First source:\n%s\n' % source_1) 159 print('Second source:\n%s\n' % source_2) 160 ret = 0 161 for name, value in sorted(macros_1.items()): 162 if name not in macros_2: 163 print('Only in first source: %s' % name) 164 if not allow_extra_1: 165 ret = 1 166 elif macros_1[name] != macros_2[name]: 167 print('Different values for %s: %s != %s' 168 % (name, macros_1[name], macros_2[name])) 169 ret = 1 170 for name in sorted(macros_2.keys()): 171 if name not in macros_1: 172 print('Only in second source: %s' % name) 173 if not allow_extra_2: 174 ret = 1 175 return ret 176