1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
#!/usr/bin/python3
# SPDX-License-Identifier: MIT
#
# 2024 by David Lamparter
#
# this tool edits an FRR source .c file to expand the typesafe DECLARE_DLIST
# et al. definitions. This can be helpful to get better warnings/errors from
# GCC when something re. a typesafe container is involved. You can also use
# it on .h files.
# The actual expansions created by this tool are written to separate files
# called something like "lib/cspf__visited_tsexpand.h" (for a container named
# "visited")
#
# THIS TOOL EDITS THE FILE IN PLACE. MAKE A BACKUP IF YOU HAVE UNSAVED WORK
# IN PROGRESS (which is likely because you're debugging a typesafe container
# problem!)
#
# The PREDECL_XYZ is irrelevant for this tool, it needs to be run on the file
# that has the DECLARE_XYZ (can be .c or .h)
#
# the lines added by this tool all have /* $ts_expand: remove$ */ at the end
# you can undo the effects of this tool by calling sed:
#
# sed -e '/\$ts_expand: remove\$/ d' -i.orig filename.c
import os
import sys
import re
import subprocess
import shlex
decl_re = re.compile(
r"""(?<=\n)[ \t]*DECLARE_(LIST|ATOMLIST|DLIST|HEAP|HASH|(SORTLIST|SKIPLIST|RBTREE|ATOMSORT)_(NON)?UNIQ)\(\s*(?P<name>[^, \t\n]+)\s*,[^)]+\)\s*;[ \t]*\n"""
)
kill_re = re.compile(r"""(?<=\n)[^\n]*/\* \$ts_expand: remove\$ \*/\n""")
src_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# some files may be compiled with different CPPFLAGS, that's not supported
# here...
cpp = subprocess.check_output(
["make", "var-CPP", "var-AM_CPPFLAGS", "var-DEFS"], cwd=src_root
)
cpp = shlex.split(cpp.decode("UTF-8"))
def process_file(filename):
with open(filename, "r") as ifd:
data = ifd.read()
data = kill_re.sub("", data)
before = 0
dirname = os.path.dirname(filename)
basename = os.path.basename(filename).removesuffix(".c").removesuffix(".h")
xname = filename + ".exp"
with open(filename + ".exp", "w") as ofd:
for m in decl_re.finditer(data):
s = m.start()
e = m.end()
ofd.write(data[before:s])
# start gcc/clang with some "magic" options to make it expand the
# typesafe macros, but nothing else.
# -P removes the "#line" markers (which are useless because
# everything ends up on one line anyway)
# -D_TYPESAFE_EXPAND_MACROS prevents the system header files
# (stddef.h, stdint.h, etc.) from being included and expanded
# -imacros loads the macro definitions from typesafe.h, but
# doesn't include any of the "plain text" (i.e. prototypes
# and outside-macro struct definitions) from it
# atomlist.h is sufficient because it includes typesafe.h which
# includes typerb.h, that's all of them
p_expand = subprocess.Popen(
cpp
+ [
"-P",
"-D_TYPESAFE_EXPAND_MACROS",
"-imacros",
"lib/atomlist.h",
"-",
],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
cwd=src_root,
)
# the output will look like shit, all on one line. format it.
p_format = subprocess.Popen(
["clang-format", "-"],
stdin=p_expand.stdout,
stdout=subprocess.PIPE,
cwd=src_root,
)
# pipe between cpp & clang-format needs to be closed
p_expand.stdout.close()
# ... and finally, write the DECLARE_XYZ statement, and ONLY that
# statements. No headers, no other definitions.
p_expand.stdin.write(data[s:e].encode("UTF-8"))
p_expand.stdin.close()
odata = b""
while rd := p_format.stdout.read():
odata = odata + rd
p_expand.wait()
p_format.wait()
# and now that we have the expanded text, write it out, put an
# #include in the .c file, and put "#if 0" around the original
# DECLARE_XYZ statement (otherwise it'll be duplicate...)
newname = os.path.join(dirname, f"{basename}__{m.group('name')}_tsexpand.h")
with open(newname, "wb") as nfd:
nfd.write(odata)
ofd.write(f'#include "{newname}" /* $ts_expand: remove$ */\n')
ofd.write("#if 0 /* $ts_expand: remove$ */\n")
ofd.write(data[s:e])
ofd.write("#endif /* $ts_expand: remove$ */\n")
before = e
ofd.write(data[before:])
os.rename(xname, filename)
if __name__ == "__main__":
for filename in sys.argv[1:]:
process_file(filename)
|