Files
shadowsocks-libev/scripts/check_cxx_runtime.py
Max Lv e16c6bcd3e check_cxx_runtime: ignore glibc symbol versions
nm on glibc prints __cxa_atexit@GLIBC_2.2.5, which the allowlist for the
libc-provided __cxa_atexit/__cxa_finalize did not match.
2026-10-14 22:51:15 +08:00

68 lines
2.4 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later
"""Fail if programs or libraries need the C++ runtime library.
The C++ core is built without exceptions or RTTI and uses only header-only
parts of the standard library, so the programs and libshadowsocks-c stay
consumable from plain C: a C program linking libshadowsocks-c.a must not need
libstdc++ or libc++. This script lists the undefined symbols of each file with
nm and rejects anything served by the C++ runtime (operator new/delete,
exception and RTTI support, std:: functions).
Usage: check_cxx_runtime.py [--nm NM] FILE...
"""
import argparse
import re
import shutil
import subprocess
import sys
# Itanium-ABI names (with or without the Mach-O leading underscore) that only
# the C++ runtime defines.
RUNTIME = re.compile(
r"^_?(?:"
r"_Zn[wa]|_Zd[la]|" # operator new / delete
r"_ZSt|_ZNSt|_ZNKSt|" # std:: functions and members
r"_ZTI|_ZTS|_ZTV|" # typeinfo / vtables (RTTI, classes from the runtime)
r"__cxa_(?!atexit$|finalize$|thread_atexit_impl$)|" # exception/guard support; libc owns these three
r"__gxx_personality|_Unwind_|__dynamic_cast"
r")"
)
def undefined_symbols(nm, path):
result = subprocess.run([nm, "-u", path], capture_output=True, text=True)
if result.returncode != 0:
raise SystemExit(f"{nm} failed on {path}: {result.stderr.strip()}")
symbols = set()
for line in result.stdout.splitlines():
line = line.strip()
if not line or line.endswith(":"):
continue # archive member headers
# glibc nm prints versioned names (__cxa_atexit@GLIBC_2.2.5).
symbols.add(line.split()[-1].split("@")[0])
return symbols
def main():
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--nm", default=shutil.which("nm") or "nm")
parser.add_argument("files", nargs="+")
args = parser.parse_args()
failed = False
for path in args.files:
bad = sorted(s for s in undefined_symbols(args.nm, path) if RUNTIME.match(s))
if bad:
failed = True
print(f"{path}: needs the C++ runtime:")
for symbol in bad:
print(f" {symbol}")
else:
print(f"{path}: ok")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())