]>
Commit | Line | Data |
---|---|---|
a8110b72 JM |
1 | #!/usr/bin/python3 |
2 | # Extract information from C headers. | |
04277e02 | 3 | # Copyright (C) 2018-2019 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 JM |
19 | |
20 | import os.path | |
21 | import re | |
22 | import subprocess | |
23 | import tempfile | |
24 | ||
25 | ||
26 | def 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 | ||
78 | def 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 | ||
120 | def 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 | ||
df648905 JM |
139 | def compare_macro_consts(source_1, source_2, cc, macro_re, exclude_re=None, |
140 | allow_extra_1=False, allow_extra_2=False): | |
a8110b72 JM |
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 | |
df648905 JM |
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. | |
a8110b72 JM |
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) | |
df648905 | 160 | ret = 0 |
a8110b72 JM |
161 | for name, value in sorted(macros_1.items()): |
162 | if name not in macros_2: | |
163 | print('Only in first source: %s' % name) | |
df648905 JM |
164 | if not allow_extra_1: |
165 | ret = 1 | |
a8110b72 JM |
166 | elif macros_1[name] != macros_2[name]: |
167 | print('Different values for %s: %s != %s' | |
168 | % (name, macros_1[name], macros_2[name])) | |
df648905 | 169 | ret = 1 |
a8110b72 JM |
170 | for name in sorted(macros_2.keys()): |
171 | if name not in macros_1: | |
172 | print('Only in second source: %s' % name) | |
df648905 JM |
173 | if not allow_extra_2: |
174 | ret = 1 | |
175 | return ret |