1#!/usr/bin/python3
2# Verify scripts/glibcelf.py contents against elf/elf.h.
3# Copyright (C) 2022 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 argparse
21import enum
22import sys
23
24import glibcelf
25import glibcextract
26
27errors_encountered = 0
28
29def error(message):
30    global errors_encountered
31    sys.stdout.write('error: {}\n'.format(message))
32    errors_encountered += 1
33
34# The enum constants in glibcelf are expected to have exactly these
35# prefixes.
36expected_constant_prefixes = tuple(
37    'ELFCLASS ELFDATA EM_ ET_ DT_ PF_ PT_ SHF_ SHN_ SHT_ STB_ STT_'.split())
38
39def find_constant_prefix(name):
40    """Returns a matching prefix from expected_constant_prefixes or None."""
41    for prefix in expected_constant_prefixes:
42        if name.startswith(prefix):
43            return prefix
44    return None
45
46def find_enum_types():
47    """A generator for OpenIntEnum and IntFlag classes in glibcelf."""
48    for obj in vars(glibcelf).values():
49        if isinstance(obj, type) and obj.__bases__[0] in (
50                glibcelf._OpenIntEnum, enum.Enum, enum.IntFlag):
51            yield obj
52
53def check_duplicates():
54    """Verifies that enum types do not have duplicate values.
55
56    Different types must have different member names, too.
57
58    """
59    global_seen = {}
60    for typ in find_enum_types():
61        seen = {}
62        last = None
63        for (name, e) in typ.__members__.items():
64            if e.value in seen:
65                error('{} has {}={} and {}={}'.format(
66                    typ, seen[e.value], e.value, name, e.value))
67                last = e
68            else:
69                seen[e.value] = name
70                if last is not None and last.value > e.value:
71                    error('{} has {}={} after {}={}'.format(
72                        typ, name, e.value, last.name, last.value))
73                if name in global_seen:
74                    error('{} used in {} and {}'.format(
75                        name, global_seen[name], typ))
76                else:
77                    global_seen[name] = typ
78
79def check_constant_prefixes():
80    """Check that the constant prefixes match expected_constant_prefixes."""
81    seen = set()
82    for typ in find_enum_types():
83        typ_prefix = None
84        for val in typ:
85            prefix = find_constant_prefix(val.name)
86            if prefix is None:
87                error('constant {!r} for {} has unknown prefix'.format(
88                    val, typ))
89                break
90            elif typ_prefix is None:
91                typ_prefix = prefix
92                seen.add(typ_prefix)
93            elif prefix != typ_prefix:
94                error('prefix {!r} for constant {!r}, expected {!r}'.format(
95                    prefix, val, typ_prefix))
96        if typ_prefix is None:
97            error('empty enum type {}'.format(typ))
98
99    for prefix in sorted(set(expected_constant_prefixes) - seen):
100        error('missing constant prefix {!r}'.format(prefix))
101    # Reverse difference is already covered inside the loop.
102
103def find_elf_h_constants(cc):
104    """Returns a dictionary of relevant constants from <elf.h>."""
105    return glibcextract.compute_macro_consts(
106        source_text='#include <elf.h>',
107        cc=cc,
108        macro_re='|'.join(
109            prefix + '.*' for prefix in expected_constant_prefixes))
110
111# The first part of the pair is a name of an <elf.h> constant that is
112# dropped from glibcelf.  The second part is the constant as it is
113# used in <elf.h>.
114glibcelf_skipped_aliases = (
115    ('EM_ARC_A5', 'EM_ARC_COMPACT'),
116    ('PF_PARISC_SBP', 'PF_HP_SBP')
117)
118
119# Constants that provide little value and are not included in
120# glibcelf: *LO*/*HI* range constants, *NUM constants counting the
121# number of constants.  Also includes the alias names from
122# glibcelf_skipped_aliases.
123glibcelf_skipped_constants = frozenset(
124    [e[0] for e in glibcelf_skipped_aliases]) | frozenset("""
125DT_AARCH64_NUM
126DT_ADDRNUM
127DT_ADDRRNGHI
128DT_ADDRRNGLO
129DT_ALPHA_NUM
130DT_ENCODING
131DT_EXTRANUM
132DT_HIOS
133DT_HIPROC
134DT_IA_64_NUM
135DT_LOOS
136DT_LOPROC
137DT_MIPS_NUM
138DT_NUM
139DT_PPC64_NUM
140DT_PPC_NUM
141DT_PROCNUM
142DT_SPARC_NUM
143DT_VALNUM
144DT_VALRNGHI
145DT_VALRNGLO
146DT_VERSIONTAGNUM
147ELFCLASSNUM
148ELFDATANUM
149ET_HIOS
150ET_HIPROC
151ET_LOOS
152ET_LOPROC
153ET_NUM
154PF_MASKOS
155PF_MASKPROC
156PT_HIOS
157PT_HIPROC
158PT_HISUNW
159PT_LOOS
160PT_LOPROC
161PT_LOSUNW
162SHF_MASKOS
163SHF_MASKPROC
164SHN_HIOS
165SHN_HIPROC
166SHN_HIRESERVE
167SHN_LOOS
168SHN_LOPROC
169SHN_LORESERVE
170SHT_HIOS
171SHT_HIPROC
172SHT_HIPROC
173SHT_HISUNW
174SHT_HIUSER
175SHT_LOOS
176SHT_LOPROC
177SHT_LOSUNW
178SHT_LOUSER
179SHT_NUM
180STB_HIOS
181STB_HIPROC
182STB_LOOS
183STB_LOPROC
184STB_NUM
185STT_HIOS
186STT_HIPROC
187STT_LOOS
188STT_LOPROC
189STT_NUM
190""".strip().split())
191
192def check_constant_values(cc):
193    """Checks the values of <elf.h> constants against glibcelf."""
194
195    glibcelf_constants = {
196        e.name: e for typ in find_enum_types() for e in typ}
197    elf_h_constants = find_elf_h_constants(cc=cc)
198
199    missing_in_glibcelf = (set(elf_h_constants) - set(glibcelf_constants)
200                           - glibcelf_skipped_constants)
201    for name in sorted(missing_in_glibcelf):
202        error('constant {} is missing from glibcelf'.format(name))
203
204    unexpected_in_glibcelf = \
205        set(glibcelf_constants) & glibcelf_skipped_constants
206    for name in sorted(unexpected_in_glibcelf):
207        error('constant {} is supposed to be filtered from glibcelf'.format(
208            name))
209
210    missing_in_elf_h = set(glibcelf_constants) - set(elf_h_constants)
211    for name in sorted(missing_in_elf_h):
212        error('constant {} is missing from <elf.h>'.format(name))
213
214    expected_in_elf_h = glibcelf_skipped_constants - set(elf_h_constants)
215    for name in expected_in_elf_h:
216        error('filtered constant {} is missing from <elf.h>'.format(name))
217
218    for alias_name, name_in_glibcelf in glibcelf_skipped_aliases:
219        if name_in_glibcelf not in glibcelf_constants:
220            error('alias value {} for {} not in glibcelf'.format(
221                name_in_glibcelf, alias_name))
222        elif (int(elf_h_constants[alias_name])
223              != glibcelf_constants[name_in_glibcelf].value):
224            error('<elf.h> has {}={}, glibcelf has {}={}'.format(
225                alias_name, elf_h_constants[alias_name],
226                name_in_glibcelf, glibcelf_constants[name_in_glibcelf]))
227
228    # Check for value mismatches:
229    for name in sorted(set(glibcelf_constants) & set(elf_h_constants)):
230        glibcelf_value = glibcelf_constants[name].value
231        elf_h_value = int(elf_h_constants[name])
232        # On 32-bit architectures <elf.h> as some constants that are
233        # parsed as signed, while they are unsigned in glibcelf.  So
234        # far, this only affects some flag constants, so special-case
235        # them here.
236        if (glibcelf_value != elf_h_value
237            and not (isinstance(glibcelf_constants[name], enum.IntFlag)
238                     and glibcelf_value == 1 << 31
239                     and elf_h_value == -(1 << 31))):
240            error('{}: glibcelf has {!r}, <elf.h> has {!r}'.format(
241                name, glibcelf_value, elf_h_value))
242
243def main():
244    """The main entry point."""
245    parser = argparse.ArgumentParser(
246        description="Check glibcelf.py and elf.h against each other.")
247    parser.add_argument('--cc', metavar='CC',
248                        help='C compiler (including options) to use')
249    args = parser.parse_args()
250
251    check_duplicates()
252    check_constant_prefixes()
253    check_constant_values(cc=args.cc)
254
255    if errors_encountered > 0:
256        print("note: errors encountered:", errors_encountered)
257        sys.exit(1)
258
259if __name__ == '__main__':
260    main()
261