Files
shadowsocks-libev/scripts/check_cxx_runtime.py
T
Max Lv 384810efce C++20 port 2/14: compile the leaf modules as C++ (#3066)
* Make headers and leaf modules compile as C++

Wrap the declarations of every project header in extern "C" (after the
system includes, which must keep C++ linkage), so converted modules and
the remaining C files agree on linkage.

In the leaf modules, cast allocator and memchr results explicitly,
rename identifiers called new, replace the getpwnam_r VLA with the
fixed 16 KiB buffer the loop already capped itself at, and make the
rlimit initialiser conversion explicit. All of this is still valid C.

* Compile the leaf modules as C++

Rename base64, json, ipset, process, ppbloom, rule, ssurl and cache from
.c to .cpp without touching their contents, and update the CMake source
lists, debian/copyright and the docs.

Ported code keeps -Wall; -Wextra is now opt-in through
ss_strict_warnings() for code written as C++ (the core tests).
check_no_heap.py gains a ratchet baseline for the converted modules,
which still allocate the way the C code did. A new check_cxx_runtime
test verifies with nm that the programs and libshadowsocks-c.a still
need no C++ runtime library, so C consumers are unaffected.

* Fix the Windows branches of process.cpp for C++

Cast allocator results and the thread argument explicitly and
value-initialise JOBOBJECT_EXTENDED_LIMIT_INFORMATION with {}. Found by
the zig x86_64-windows-gnu cross build.

* Keep clang-tidy clean for the converted modules

Once the C headers are included from C++ translation units,
bugprone-dynamic-static-initializers reports each extern variable
declaration in them; none is defined in a header, so disable the check.
Mark the two bounded memchr searches in ssurl.cpp, which
bugprone-not-null-terminated-result misreads as string copies.

* 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.

* Keep GCC's C++-only warnings off for modules converted from C

GCC's C++ -Wall includes -Wsign-compare and -Wclass-memaccess, which C's
-Wall does not, so the converted modules failed -Werror on GCC (Linux,
UCRT64) although nothing in them changed. They keep the warning set they
were written against; code under ss_strict_warnings() re-enables both.
2026-10-15 09:48:18 +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())