]>
Commit | Line | Data |
---|---|---|
a8110b72 JM |
1 | #!/usr/bin/python3 |
2 | # Extract information from C headers. | |
6d7e8eda | 3 | # Copyright (C) 2018-2023 Free Software Foundation, Inc. |
a8110b72 JM |
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 | |
5a82c748 | 18 | # <https://www.gnu.org/licenses/>. |
a8110b72 | 19 | |
841afa11 | 20 | import collections |
a8110b72 JM |
21 | import os.path |
22 | import re | |
23 | import subprocess | |
24 | import tempfile | |
25 | ||
26 | ||
27 | def compute_c_consts(sym_data, cc): | |
28 | """Compute the values of some C constants. | |
29 | ||
30 | The first argument is a list whose elements are either strings | |
31 | (preprocessor directives, or the special string 'START' to | |
32 | indicate this function should insert its initial boilerplate text | |
33 | in the output there) or pairs of strings (a name and a C | |
34 | expression for the corresponding value). Preprocessor directives | |
35 | in the middle of the list may be used to select which constants | |
36 | end up being evaluated using which expressions. | |
37 | ||
38 | """ | |
39 | out_lines = [] | |
40 | for arg in sym_data: | |
41 | if isinstance(arg, str): | |
42 | if arg == 'START': | |
43 | out_lines.append('void\ndummy (void)\n{') | |
44 | else: | |
45 | out_lines.append(arg) | |
46 | continue | |
47 | name = arg[0] | |
48 | value = arg[1] | |
46baeb61 | 49 | out_lines.append('asm ("/* @@@name@@@%s@@@value@@@%%0@@@end@@@ */" ' |
a8110b72 JM |
50 | ': : \"i\" ((long int) (%s)));' |
51 | % (name, value)) | |
52 | out_lines.append('}') | |
53 | out_lines.append('') | |
54 | out_text = '\n'.join(out_lines) | |
55 | with tempfile.TemporaryDirectory() as temp_dir: | |
56 | c_file_name = os.path.join(temp_dir, 'test.c') | |
57 | s_file_name = os.path.join(temp_dir, 'test.s') | |
58 | with open(c_file_name, 'w') as c_file: | |
59 | c_file.write(out_text) | |
60 | # Compilation has to be from stdin to avoid the temporary file | |
61 | # name being written into the generated dependencies. | |
62 | cmd = ('%s -S -o %s -x c - < %s' % (cc, s_file_name, c_file_name)) | |
63 | subprocess.check_call(cmd, shell=True) | |
64 | consts = {} | |
65 | with open(s_file_name, 'r') as s_file: | |
66 | for line in s_file: | |
67 | match = re.search('@@@name@@@([^@]*)' | |
68 | '@@@value@@@[^0-9Xxa-fA-F-]*' | |
69 | '([0-9Xxa-fA-F-]+).*@@@end@@@', line) | |
70 | if match: | |
71 | if (match.group(1) in consts | |
72 | and match.group(2) != consts[match.group(1)]): | |
73 | raise ValueError('duplicate constant %s' | |
74 | % match.group(1)) | |
75 | consts[match.group(1)] = match.group(2) | |
76 | return consts | |
77 | ||
78 | ||
79 | def list_macros(source_text, cc): | |
80 | """List the preprocessor macros defined by the given source code. | |
81 | ||
82 | The return value is a pair of dicts, the first one mapping macro | |
83 | names to their expansions and the second one mapping macro names | |
84 | to lists of their arguments, or to None for object-like macros. | |
85 | ||
86 | """ | |
87 | with tempfile.TemporaryDirectory() as temp_dir: | |
88 | c_file_name = os.path.join(temp_dir, 'test.c') | |
89 | i_file_name = os.path.join(temp_dir, 'test.i') | |
90 | with open(c_file_name, 'w') as c_file: | |
91 | c_file.write(source_text) | |
92 | cmd = ('%s -E -dM -o %s %s' % (cc, i_file_name, c_file_name)) | |
93 | subprocess.check_call(cmd, shell=True) | |
94 | macros_exp = {} | |
95 | macros_args = {} | |
96 | with open(i_file_name, 'r') as i_file: | |
97 | for line in i_file: | |
98 | match = re.fullmatch('#define ([0-9A-Za-z_]+)(.*)\n', line) | |
99 | if not match: | |
100 | raise ValueError('bad -dM output line: %s' % line) | |
101 | name = match.group(1) | |
102 | value = match.group(2) | |
103 | if value.startswith(' '): | |
104 | value = value[1:] | |
105 | args = None | |
106 | elif value.startswith('('): | |
107 | match = re.fullmatch(r'\((.*?)\) (.*)', value) | |
108 | if not match: | |
109 | raise ValueError('bad -dM output line: %s' % line) | |
110 | args = match.group(1).split(',') | |
111 | value = match.group(2) | |
112 | else: | |
113 | raise ValueError('bad -dM output line: %s' % line) | |
114 | if name in macros_exp: | |
115 | raise ValueError('duplicate macro: %s' % line) | |
116 | macros_exp[name] = value | |
117 | macros_args[name] = args | |
118 | return macros_exp, macros_args | |
119 | ||
120 | ||
121 | def compute_macro_consts(source_text, cc, macro_re, exclude_re=None): | |
122 | """Compute the integer constant values of macros defined by source_text. | |
123 | ||
124 | Macros must match the regular expression macro_re, and if | |
125 | exclude_re is defined they must not match exclude_re. Values are | |
126 | computed with compute_c_consts. | |
127 | ||
128 | """ | |
129 | macros_exp, macros_args = list_macros(source_text, cc) | |
130 | macros_set = {m for m in macros_exp | |
131 | if (macros_args[m] is None | |
132 | and re.fullmatch(macro_re, m) | |
133 | and (exclude_re is None | |
134 | or not re.fullmatch(exclude_re, m)))} | |
135 | sym_data = [source_text, 'START'] | |
136 | sym_data.extend(sorted((m, m) for m in macros_set)) | |
137 | return compute_c_consts(sym_data, cc) | |
138 | ||
139 | ||
df648905 JM |
140 | def compare_macro_consts(source_1, source_2, cc, macro_re, exclude_re=None, |
141 | allow_extra_1=False, allow_extra_2=False): | |
a8110b72 JM |
142 | """Compare the values of macros defined by two different sources. |
143 | ||
144 | The sources would typically be includes of a glibc header and a | |
df648905 JM |
145 | kernel header. If allow_extra_1, the first source may define |
146 | extra macros (typically if the kernel headers are older than the | |
147 | version glibc has taken definitions from); if allow_extra_2, the | |
148 | second source may define extra macros (typically if the kernel | |
149 | headers are newer than the version glibc has taken definitions | |
150 | from). Return 1 if there were any differences other than those | |
151 | allowed, 0 if the macro values were the same apart from any | |
152 | allowed differences. | |
a8110b72 JM |
153 | |
154 | """ | |
155 | macros_1 = compute_macro_consts(source_1, cc, macro_re, exclude_re) | |
156 | macros_2 = compute_macro_consts(source_2, cc, macro_re, exclude_re) | |
157 | if macros_1 == macros_2: | |
158 | return 0 | |
159 | print('First source:\n%s\n' % source_1) | |
160 | print('Second source:\n%s\n' % source_2) | |
df648905 | 161 | ret = 0 |
a8110b72 JM |
162 | for name, value in sorted(macros_1.items()): |
163 | if name not in macros_2: | |
164 | print('Only in first source: %s' % name) | |
df648905 JM |
165 | if not allow_extra_1: |
166 | ret = 1 | |
a8110b72 JM |
167 | elif macros_1[name] != macros_2[name]: |
168 | print('Different values for %s: %s != %s' | |
169 | % (name, macros_1[name], macros_2[name])) | |
df648905 | 170 | ret = 1 |
a8110b72 JM |
171 | for name in sorted(macros_2.keys()): |
172 | if name not in macros_1: | |
173 | print('Only in second source: %s' % name) | |
df648905 JM |
174 | if not allow_extra_2: |
175 | ret = 1 | |
176 | return ret | |
841afa11 AZ |
177 | |
178 | CompileResult = collections.namedtuple("CompileResult", "returncode output") | |
179 | ||
180 | def compile_c_snippet(snippet, cc, extra_cc_args=''): | |
181 | """Compile and return whether the SNIPPET can be build with CC along | |
182 | EXTRA_CC_ARGS compiler flags. Return a CompileResult with RETURNCODE | |
183 | being 0 for success, or the failure value and the compiler output. | |
184 | """ | |
185 | with tempfile.TemporaryDirectory() as temp_dir: | |
186 | c_file_name = os.path.join(temp_dir, 'test.c') | |
187 | obj_file_name = os.path.join(temp_dir, 'test.o') | |
188 | with open(c_file_name, 'w') as c_file: | |
189 | c_file.write(snippet + '\n') | |
190 | cmd = cc.split() + extra_cc_args.split() + ['-c', '-o', obj_file_name, | |
191 | c_file_name] | |
192 | r = subprocess.run(cmd, check=False, stdout=subprocess.PIPE, | |
193 | stderr=subprocess.STDOUT) | |
194 | return CompileResult(r.returncode, r.stdout) |