From 384810efcef904fc6f85036d9b303f93f1ca11c2 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 15 Oct 2026 09:48:18 +0800 Subject: [PATCH] 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. --- .clang-tidy | 5 ++- CLAUDE.md | 10 ++--- cmake/ProjectOptions.cmake | 20 +++++++++- debian/copyright | 6 +-- docs/cpp-core.md | 2 +- scripts/check_cxx_runtime.py | 67 ++++++++++++++++++++++++++++++++++ scripts/check_no_heap.py | 53 ++++++++++++++++++++++----- scripts/no_heap_baseline.txt | 10 +++++ src/CMakeLists.txt | 22 +++++------ src/acl.h | 8 ++++ src/aead.h | 8 ++++ src/aead_internal.h | 8 ++++ src/{base64.c => base64.cpp} | 0 src/base64.h | 8 ++++ src/{cache.c => cache.cpp} | 22 +++++------ src/cache.h | 8 ++++ src/common.h | 8 ++++ src/core.h | 8 ++++ src/core/fixed_map.hpp | 4 +- src/crypto.h | 8 ++++ src/{ipset.c => ipset.cpp} | 2 +- src/ipset.h | 8 ++++ src/jconf.h | 8 ++++ src/{json.c => json.cpp} | 0 src/local.h | 8 ++++ src/manager.h | 8 ++++ src/netutils.h | 8 ++++ src/platform.h | 8 ++++ src/plugin.h | 8 ++++ src/{ppbloom.c => ppbloom.cpp} | 0 src/ppbloom.h | 8 ++++ src/{process.c => process.cpp} | 28 +++++++------- src/redir.h | 8 ++++ src/resolv.h | 8 ++++ src/{rule.c => rule.cpp} | 2 +- src/rule.h | 8 ++++ src/server.h | 8 ++++ src/socks5.h | 8 ++++ src/ss_event.h | 8 ++++ src/ss_process.h | 8 ++++ src/ss_windows.h | 8 ++++ src/{ssurl.c => ssurl.cpp} | 18 +++++---- src/ssurl.h | 8 ++++ src/stream.h | 8 ++++ src/tunnel.h | 8 ++++ src/udprelay.h | 8 ++++ src/utils.c | 14 ++++--- src/utils.h | 8 ++++ tests/CMakeLists.txt | 48 +++++++++++++++++------- 49 files changed, 476 insertions(+), 89 deletions(-) create mode 100644 scripts/check_cxx_runtime.py create mode 100644 scripts/no_heap_baseline.txt rename src/{base64.c => base64.cpp} (100%) rename src/{cache.c => cache.cpp} (93%) rename src/{ipset.c => ipset.cpp} (96%) rename src/{json.c => json.cpp} (100%) rename src/{ppbloom.c => ppbloom.cpp} (100%) rename src/{process.c => process.cpp} (91%) rename src/{rule.c => rule.cpp} (99%) rename src/{ssurl.c => ssurl.cpp} (91%) diff --git a/.clang-tidy b/.clang-tidy index 9b49bceb..4d432db3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -12,7 +12,9 @@ # reserved-identifier: the codebase uses _prefixed names and vendored # uthash.h/generated config.h trip them ~600 times) or produce false # positives with the event callback style (casting-through-void, -# multi-level-implicit-pointer-conversion). +# multi-level-implicit-pointer-conversion). dynamic-static-initializers +# fires on every extern variable declaration in the C headers once they are +# included from C++; none of them is defined or initialised in a header. Checks: > clang-analyzer-*, bugprone-*, @@ -20,6 +22,7 @@ Checks: > -bugprone-assignment-in-if-condition, -bugprone-branch-clone, -bugprone-casting-through-void, + -bugprone-dynamic-static-initializers, -bugprone-easily-swappable-parameters, -bugprone-inc-dec-in-conditions, -bugprone-implicit-widening-of-multiplication-result, diff --git a/CLAUDE.md b/CLAUDE.md index 05e6d4cb..8347cb7a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -103,9 +103,9 @@ A library `libshadowsocks-c` (legacy names `libshadowsocks-libev.*` are kept) is **Shared by all binaries:** - `utils.c` - logging, system utilities, `ss_malloc`/`ss_free` -- `jconf.c` / `json.c` - JSON config file parsing +- `jconf.c` / `json.cpp` - JSON config file parsing - `netutils.c` - network address utilities -- `cache.c` - uthash-based connection cache (UDP relay, AEAD-2022 tables) +- `cache.cpp` - uthash-based connection cache (UDP relay, AEAD-2022 tables) - `udprelay.c` - UDP relay implementation (shared, but uses `#ifdef MODULE_*` for per-binary behavior) - `ss_event.c` / `ss_event.h` - event loop API over libuv - `resolv.c` - asynchronous DNS via c-ares @@ -115,11 +115,11 @@ A library `libshadowsocks-c` (legacy names `libshadowsocks-libev.*` are kept) is - `stream.c` - legacy stream ciphers - `aead.c` - AEAD ciphers (AES-GCM via Mbed TLS/libsodium, ChaCha20-Poly1305 via libsodium) - `aead2022.c` / `aead2022_udp.c` - Shadowsocks 2022 (SIP022), BLAKE3 from `src/blake3` -- `ppbloom.c` - ping-pong bloom filter for salt replay detection (`third_party/bloom`) +- `ppbloom.cpp` - ping-pong bloom filter for salt replay detection (`third_party/bloom`) -**ACL:** `acl.c` / `rule.c` / `ipset.c` - IP prefix sets and PCRE2/literal domain rules. +**ACL:** `acl.c` / `rule.cpp` / `ipset.cpp` - IP prefix sets and PCRE2/literal domain rules. -**Plugins:** `plugin.c` / `process.c` - SIP003 plugin subprocess management. +**Plugins:** `plugin.c` / `process.cpp` - SIP003 plugin subprocess management. **C++ core:** `src/core/*.hpp` - header-only ownership and allocation primitives (pools, handles, fixed buffers, arena, fixed hash map/LRU, intrusive list, bounded heap). See `docs/cpp-core.md`. diff --git a/cmake/ProjectOptions.cmake b/cmake/ProjectOptions.cmake index f73366a8..4daa0958 100644 --- a/cmake/ProjectOptions.cmake +++ b/cmake/ProjectOptions.cmake @@ -26,9 +26,16 @@ function(ss_project_options target) else() target_compile_options(${target} PRIVATE -Wall -Wno-deprecated-declarations -fno-strict-aliasing) # C++ sources: no exceptions or RTTI, and no locks around function - # statics (the event loop is single-threaded). + # statics (the event loop is single-threaded). New C++ code also opts + # into -Wextra through ss_strict_warnings(); modules ported from C get + # it when they are rewritten. target_compile_options(${target} PRIVATE - $<$:-Wextra -fno-exceptions -fno-rtti -fno-threadsafe-statics>) + $<$:-fno-exceptions -fno-rtti -fno-threadsafe-statics>) + # GCC's C++ -Wall adds -Wsign-compare and -Wclass-memaccess, which C's + # -Wall does not have. Modules converted from C keep the warning set + # they were written against; ss_strict_warnings() turns both back on. + target_compile_options(${target} PRIVATE + $<$:-Wno-sign-compare -Wno-class-memaccess>) if(SS_WARNINGS_AS_ERRORS) target_compile_options(${target} PRIVATE -Werror) endif() @@ -54,3 +61,12 @@ function(ss_project_options target) target_link_options(${target} PRIVATE --coverage) endif() endfunction() + +# Extra warnings for code written as C++ from the start (src/core and its +# tests). +function(ss_strict_warnings target) + if(NOT MSVC) + target_compile_options(${target} PRIVATE $<$:-Wextra> + $<$:-Wsign-compare -Wclass-memaccess>) + endif() +endfunction() diff --git a/debian/copyright b/debian/copyright index aa8e2484..148d692f 100644 --- a/debian/copyright +++ b/debian/copyright @@ -41,11 +41,11 @@ Files: scripts/build_deb.sh Copyright: 2018 Roger Shimizu License: GPL-3+ -Files: src/base64.c src/base64.h +Files: src/base64.cpp src/base64.h Copyright: 2006 Ryan Martell License: LGPL-2.1+ -Files: src/json.c src/json.h +Files: src/json.cpp src/json.h Copyright: 2012-2014, James McLaughlin et al. License: BSD-2-clause @@ -53,7 +53,7 @@ Files: src/resolv.c src/resolv.h Copyright: 2011-2014, Dustin Lundquist License: BSD-2-clause -Files: src/rule.c src/rule.h +Files: src/rule.cpp src/rule.h Copyright: 2011-2012, Dustin Lundquist 2011, Manuel Kasper License: BSD-2-clause diff --git a/docs/cpp-core.md b/docs/cpp-core.md index 24ecf11e..ab21d5cb 100644 --- a/docs/cpp-core.md +++ b/docs/cpp-core.md @@ -43,7 +43,7 @@ All binaries are still built from the C sources. | `ref.hpp` | `RefCounted`/`RefPtr`, intrusive shared ownership for the few genuinely shared objects (AEAD-2022 UDP sessions). Used instead of `std::shared_ptr` because its control block cannot be pooled portably. | | `buffer.hpp` | `FixedBuffer`: inline bytes with `std::span` views; replaces `buffer_t`. | | `arena.hpp` | `MonotonicArena` for configuration data that lives as long as the process. | -| `fixed_map.hpp` | `FixedHashMap` (open addressing, backward-shift deletion) and `FixedLru`; replace uthash and `cache.c`. | +| `fixed_map.hpp` | `FixedHashMap` (open addressing, backward-shift deletion) and `FixedLru`; replace uthash and `cache.cpp`. | | `intrusive_list.hpp` | `IntrusiveList`/`ListNode`; replaces `ss_list`. Nodes unlink themselves when destroyed. | | `fixed_heap.hpp` | Bounded size-class allocator to hand to libuv, c-ares and Mbed TLS through their allocator hooks. | diff --git a/scripts/check_cxx_runtime.py b/scripts/check_cxx_runtime.py new file mode 100644 index 00000000..ac0bd178 --- /dev/null +++ b/scripts/check_cxx_runtime.py @@ -0,0 +1,67 @@ +#!/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()) diff --git a/scripts/check_no_heap.py b/scripts/check_no_heap.py index 39fa63b6..7675efbb 100644 --- a/scripts/check_no_heap.py +++ b/scripts/check_no_heap.py @@ -14,7 +14,12 @@ A line that must allocate (a startup reservation) carries a justification: void *base = std::calloc(1, size); // heap-ok: startup reservation -Usage: check_no_heap.py [--root DIR] [--self-test] +Modules converted mechanically from C still allocate the way the C code did +until they are rewritten. scripts/no_heap_baseline.txt lists them with their +current violation counts, and works as a ratchet: a file may not gain +violations, and when it loses some the baseline must be lowered to match. + +Usage: check_no_heap.py [--root DIR] [--baseline FILE] [--self-test] """ import argparse @@ -89,16 +94,42 @@ def check_text(text): return problems -def check_tree(root): +def read_baseline(path): + baseline = {} + if path is None or not path.exists(): + return baseline + for raw in path.read_text(encoding="utf-8").splitlines(): + line = raw.split("#", 1)[0].strip() + if line: + name, count = line.split() + baseline[name] = int(count) + return baseline + + +def check_tree(root, baseline): src = root / "src" files = sorted(p for p in src.rglob("*") if p.suffix in (".cpp", ".hpp") and "blake3" not in p.parts) - failures = 0 + ok = True + total = 0 for path in files: - for number, why, line in check_text(path.read_text(encoding="utf-8")): - print(f"{path.relative_to(root)}:{number}: {why}: {line}") - failures += 1 - print(f"check_no_heap: {len(files)} C++ files, {failures} violation(s)") - return failures == 0 + name = path.relative_to(root).as_posix() + problems = check_text(path.read_text(encoding="utf-8")) + allowed = baseline.pop(name, 0) + total += len(problems) + if len(problems) > allowed: + for number, why, line in problems: + print(f"{name}:{number}: {why}: {line}") + print(f"{name}: {len(problems)} violation(s), baseline allows {allowed}") + ok = False + elif len(problems) < allowed: + print(f"{name}: {len(problems)} violation(s), below the baseline of {allowed}; " + "lower it in scripts/no_heap_baseline.txt") + ok = False + for name in sorted(baseline): + print(f"{name}: listed in the baseline but not a C++ source; remove the entry") + ok = False + print(f"check_no_heap: {len(files)} C++ files, {total} baselined or new violation(s)") + return ok def self_test(): @@ -144,11 +175,15 @@ def self_test(): def main(): parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument("--root", type=pathlib.Path, default=pathlib.Path(__file__).resolve().parent.parent) + parser.add_argument("--baseline", type=pathlib.Path, + help="ratchet file (default: scripts/no_heap_baseline.txt under --root)") parser.add_argument("--self-test", action="store_true", help="run the pattern self-test first") args = parser.parse_args() if args.self_test and not self_test(): return 1 - return 0 if check_tree(args.root.resolve()) else 1 + root = args.root.resolve() + baseline = read_baseline(args.baseline or root / "scripts" / "no_heap_baseline.txt") + return 0 if check_tree(root, baseline) else 1 if __name__ == "__main__": diff --git a/scripts/no_heap_baseline.txt b/scripts/no_heap_baseline.txt new file mode 100644 index 00000000..6cedf8e0 --- /dev/null +++ b/scripts/no_heap_baseline.txt @@ -0,0 +1,10 @@ +# Heap-allocation ratchet for C++ sources converted mechanically from C. +# Format: . check_no_heap.py fails if a file gains +# violations, and also when it loses some, so the number here always goes +# down. Remove a file once it is rewritten on the src/core primitives. +src/cache.cpp 3 +src/ipset.cpp 3 +src/json.cpp 2 +src/process.cpp 26 +src/rule.cpp 2 +src/ssurl.cpp 3 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 255bd791..fe150898 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,8 +10,8 @@ endif () set(SS_ACL_SOURCE acl.c - ipset.c - rule.c + ipset.cpp + rule.cpp ) set(SS_CRYPTO_SOURCE @@ -19,7 +19,7 @@ set(SS_CRYPTO_SOURCE aead.c aead2022.c aead2022_udp.c - base64.c + base64.cpp ) # BLAKE3 (vendored) provides the key derivation for the 2022 ciphers. Only @@ -43,16 +43,16 @@ if(SS_ENABLE_LEGACY) list(APPEND SS_CRYPTO_SOURCE stream.c) endif() if(SS_ENABLE_PLUGINS) - set(SS_PLUGIN_SOURCE plugin.c process.c) + set(SS_PLUGIN_SOURCE plugin.c process.cpp) else() set(SS_PLUGIN_SOURCE plugin_disabled.c) endif() set(SS_SHARED_SOURCES - ppbloom.c + ppbloom.cpp utils.c jconf.c - json.c + json.cpp netutils.c ) if (MINGW) @@ -62,10 +62,10 @@ endif () set(LIBSHADOWSOCKS_LIBEV_SOURCE ${SS_SHARED_SOURCES} udprelay.c - cache.c + cache.cpp local.c resolv.c - ssurl.c + ssurl.cpp ${SS_CRYPTO_SOURCE} ${SS_PLUGIN_SOURCE} ${SS_ACL_SOURCE} @@ -79,7 +79,7 @@ set(SS_LOCAL_SOURCE set(SS_TUNNEL_SOURCE ${SS_SHARED_SOURCES} udprelay.c - cache.c + cache.cpp tunnel.c ${SS_CRYPTO_SOURCE} ${SS_PLUGIN_SOURCE} @@ -88,7 +88,7 @@ set(SS_TUNNEL_SOURCE set(SS_SERVER_SOURCE ${SS_SHARED_SOURCES} udprelay.c - cache.c + cache.cpp resolv.c server.c ${SS_CRYPTO_SOURCE} @@ -104,7 +104,7 @@ set(SS_MANAGER_SOURCE set(SS_REDIR_SOURCE ${SS_SHARED_SOURCES} udprelay.c - cache.c + cache.cpp redir.c ${SS_CRYPTO_SOURCE} ${SS_PLUGIN_SOURCE} diff --git a/src/acl.h b/src/acl.h index 197027c0..e03159c5 100644 --- a/src/acl.h +++ b/src/acl.h @@ -23,6 +23,10 @@ #ifndef _ACL_H #define _ACL_H +#ifdef __cplusplus +extern "C" { +#endif + #define BLACK_LIST 0 #define WHITE_LIST 1 @@ -37,4 +41,8 @@ int get_acl_mode(void); int outbound_block_match_host(const char *host); +#ifdef __cplusplus +} +#endif + #endif // _ACL_H diff --git a/src/aead.h b/src/aead.h index 284207f3..e3116c07 100644 --- a/src/aead.h +++ b/src/aead.h @@ -25,6 +25,10 @@ #include "crypto.h" +#ifdef __cplusplus +extern "C" { +#endif + // currently, XCHACHA20POLY1305IETF is not released yet // XCHACHA20POLY1305 is removed in upstream #ifdef FS_HAVE_XCHACHA20IETF @@ -52,4 +56,8 @@ int aead_2022_encrypt_udp(buffer_t *, cipher_t *, size_t, void **); int aead_2022_decrypt_udp(buffer_t *, cipher_t *, size_t, void **); void aead_2022_udp_session_release(void *); +#ifdef __cplusplus +} +#endif + #endif // _AEAD_H diff --git a/src/aead_internal.h b/src/aead_internal.h index fb4c2e80..b3be0dcb 100644 --- a/src/aead_internal.h +++ b/src/aead_internal.h @@ -25,6 +25,10 @@ #include "crypto.h" +#ifdef __cplusplus +extern "C" { +#endif + /* AEAD method identifiers (indexes into the supported_aead_ciphers tables) */ #define AES128GCM 0 #define AES192GCM 1 @@ -73,4 +77,8 @@ int ss2022_tcp_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx, int ss2022_tcp_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity); +#ifdef __cplusplus +} +#endif + #endif // _AEAD_INTERNAL_H diff --git a/src/base64.c b/src/base64.cpp similarity index 100% rename from src/base64.c rename to src/base64.cpp diff --git a/src/base64.h b/src/base64.h index 01bcda57..7911443a 100644 --- a/src/base64.h +++ b/src/base64.h @@ -23,6 +23,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + /** * Decode a base64-encoded string. * @@ -51,4 +55,8 @@ char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size); */ #define BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) +#ifdef __cplusplus +} +#endif + #endif /* BASE64_H */ diff --git a/src/cache.c b/src/cache.cpp similarity index 93% rename from src/cache.c rename to src/cache.cpp index 6d4fc688..92d3540f 100644 --- a/src/cache.c +++ b/src/cache.cpp @@ -50,20 +50,20 @@ int cache_create(struct cache **dst, const size_t capacity, void (*free_cb)(void *key, void *element)) { - struct cache *new = NULL; + struct cache *created = NULL; if (!dst) { return EINVAL; } - if ((new = malloc(sizeof(*new))) == NULL) { + if ((created = (struct cache *)malloc(sizeof(*created))) == NULL) { return ENOMEM; } - new->max_entries = capacity; - new->entries = NULL; - new->free_cb = free_cb; - *dst = new; + created->max_entries = capacity; + created->entries = NULL; + created->free_cb = free_cb; + *dst = created; return 0; } @@ -212,7 +212,7 @@ int cache_lookup(struct cache *cache, char *key, size_t key_len, void *result) { struct cache_entry *tmp = NULL; - char **dirty_hack = result; + void **out = (void **)result; if (!cache || !key || !result) { return EINVAL; @@ -224,9 +224,9 @@ cache_lookup(struct cache *cache, char *key, size_t key_len, void *result) tmp->ts = ss_monotonic_time(); // NOLINTNEXTLINE(clang-analyzer-core.DivideZero): uthash bucket count is never zero HASH_ADD_KEYPTR(hh, cache->entries, tmp->key, key_len, tmp); - *dirty_hack = tmp->data; + *out = tmp->data; } else { - *dirty_hack = result = NULL; + *out = NULL; } return 0; @@ -279,11 +279,11 @@ cache_insert(struct cache *cache, char *key, size_t key_len, void *data) return EINVAL; } - if ((entry = malloc(sizeof(*entry))) == NULL) { + if ((entry = (struct cache_entry *)malloc(sizeof(*entry))) == NULL) { return ENOMEM; } - entry->key = ss_malloc(key_len + 1); + entry->key = (char *)ss_malloc(key_len + 1); memcpy(entry->key, key, key_len); entry->key[key_len] = 0; diff --git a/src/cache.h b/src/cache.h index 1977c29f..051313b6 100644 --- a/src/cache.h +++ b/src/cache.h @@ -30,6 +30,10 @@ #include "uthash.h" +#ifdef __cplusplus +extern "C" { +#endif + /** * A cache entry */ @@ -58,4 +62,8 @@ int cache_insert(struct cache *cache, char *key, size_t key_len, void *data); int cache_remove(struct cache *cache, char *key, size_t key_len); int cache_key_exist(struct cache *cache, char *key, size_t key_len); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/common.h b/src/common.h index 69e47388..771ff785 100644 --- a/src/common.h +++ b/src/common.h @@ -32,6 +32,10 @@ #include "crypto.h" +#ifdef __cplusplus +extern "C" { +#endif + int init_udprelay(const char *server_host, const char *server_port, #ifdef MODULE_LOCAL const struct sockaddr *remote_addr, const int remote_addr_len, @@ -83,4 +87,8 @@ enum { GETOPT_VAL_IPV4_FIRST }; +#ifdef __cplusplus +} +#endif + #endif // _COMMON_H diff --git a/src/core.h b/src/core.h index 36dc2a8b..b9b8d764 100644 --- a/src/core.h +++ b/src/core.h @@ -6,6 +6,10 @@ #include #include "platform.h" +#ifdef __cplusplus +extern "C" { +#endif + #define ss_container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member))) struct ss_list_item { @@ -71,4 +75,8 @@ ss_ip_init(struct ss_ip *ip, const char *text) ip->version = 0; return -1; } +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/core/fixed_map.hpp b/src/core/fixed_map.hpp index 0385f021..a821e313 100644 --- a/src/core/fixed_map.hpp +++ b/src/core/fixed_map.hpp @@ -2,7 +2,7 @@ #ifndef SS_CORE_FIXED_MAP_HPP #define SS_CORE_FIXED_MAP_HPP -// Fixed-capacity associative containers. Replace uthash (cache.c, +// Fixed-capacity associative containers. Replace uthash (cache.cpp, // ss_event.c, manager.c) without any per-entry allocation. // // FixedHashMap @@ -15,7 +15,7 @@ // FixedLru // Map plus recency order. Values live in a slab and never move, so the // pointer returned by insert() or find() stays valid until that entry -// is erased or evicted. Replaces the pseudo-LRU in cache.c. +// is erased or evicted. Replaces the pseudo-LRU in cache.cpp. // // Keys from the network (UDP peer addresses, session IDs) are hashed with a // per-table seed. Seed it from a CSPRNG in production code so a peer cannot diff --git a/src/crypto.h b/src/crypto.h index f2dbe120..31c32c92 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -42,6 +42,10 @@ typedef crypto_aead_aes256gcm_state aes256gcm_ctx; /* Definitions for mbedTLS */ #include #include + +#ifdef __cplusplus +extern "C" { +#endif typedef mbedtls_cipher_info_t cipher_kt_t; typedef mbedtls_cipher_context_t cipher_evp_t; typedef mbedtls_md_info_t digest_type_t; @@ -206,4 +210,8 @@ extern struct cache *nonce_cache; extern const char *supported_stream_ciphers[]; extern const char *supported_aead_ciphers[]; +#ifdef __cplusplus +} +#endif + #endif // _CRYPTO_H diff --git a/src/ipset.c b/src/ipset.cpp similarity index 96% rename from src/ipset.c rename to src/ipset.cpp index 51723ba2..cedcab2b 100644 --- a/src/ipset.c +++ b/src/ipset.cpp @@ -14,7 +14,7 @@ struct ss_ipset_node { static struct ss_ipset_node * node_new(bool full) { - struct ss_ipset_node *node = ss_malloc(sizeof(*node)); + struct ss_ipset_node *node = (struct ss_ipset_node *)ss_malloc(sizeof(*node)); node->child[0] = node->child[1] = NULL; node->full = full; return node; diff --git a/src/ipset.h b/src/ipset.h index 47801377..6afe2b68 100644 --- a/src/ipset.h +++ b/src/ipset.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct ss_ipset_node; struct ss_ipset { struct ss_ipset_node *root; @@ -15,4 +19,8 @@ void ss_ipset_clear(struct ss_ipset *set); void ss_ipset_assign(struct ss_ipset *set, const uint8_t *address, unsigned width, unsigned prefix, bool present); bool ss_ipset_contains(const struct ss_ipset *set, const uint8_t *address, unsigned width); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/jconf.h b/src/jconf.h index 1a92f1fb..ce1c86e8 100644 --- a/src/jconf.h +++ b/src/jconf.h @@ -22,6 +22,10 @@ #ifndef _JCONF_H #define _JCONF_H +#ifdef __cplusplus +extern "C" { +#endif + #define MAX_PORT_NUM 1024 #define MAX_REMOTE_NUM 10 #define MAX_DSCP_NUM 64 @@ -99,4 +103,8 @@ void free_addr(ss_addr_t *addr); int parse_server_endpoint(const char *str, ss_addr_t *addr); int complete_server_ports(ss_addr_t *addr, int count, const char *fallback, int plugin); +#ifdef __cplusplus +} +#endif + #endif // _JCONF_H diff --git a/src/json.c b/src/json.cpp similarity index 100% rename from src/json.c rename to src/json.cpp diff --git a/src/local.h b/src/local.h index 5de6261e..ae526ab9 100644 --- a/src/local.h +++ b/src/local.h @@ -36,6 +36,10 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct listen_ctx { ss_io io; char *iface; @@ -100,4 +104,8 @@ typedef struct remote { struct sockaddr_storage addr; } remote_t; +#ifdef __cplusplus +} +#endif + #endif // _LOCAL_H diff --git a/src/manager.h b/src/manager.h index 5d58fd10..9c5867be 100644 --- a/src/manager.h +++ b/src/manager.h @@ -33,6 +33,10 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + struct manager_ctx { ss_io io; int fd; @@ -75,4 +79,8 @@ struct server { uint64_t traffic; }; +#ifdef __cplusplus +} +#endif + #endif // _MANAGER_H diff --git a/src/netutils.h b/src/netutils.h index a8cb880d..a0b82eb6 100644 --- a/src/netutils.h +++ b/src/netutils.h @@ -39,6 +39,10 @@ #include #endif +#ifdef __cplusplus +extern "C" { +#endif + /* Hard coded defines for TCP fast open on Android */ #ifdef __ANDROID__ #ifndef TCP_FASTOPEN @@ -127,4 +131,8 @@ int validate_hostname(const char *hostname, const int hostname_len); int is_ipv6only(ss_addr_t *servers, size_t server_num, int ipv6first); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/platform.h b/src/platform.h index c6ec1eff..01b060cf 100644 --- a/src/platform.h +++ b/src/platform.h @@ -17,6 +17,10 @@ typedef SOCKET ss_socket_t; typedef int ss_socket_t; #endif +#ifdef __cplusplus +extern "C" { +#endif + /* Socket errors are separate from CRT errno on Windows. Return normalized * errno constants for portable branching without redefining libc names. */ static inline int @@ -117,4 +121,8 @@ ss_monotonic_time(void) return (double)value.tv_sec + (double)value.tv_nsec / 1000000000.0; #endif } +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/plugin.h b/src/plugin.h index 1a6d4b47..8bf3e14a 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -23,6 +23,10 @@ #ifndef _PLUGIN_H #define _PLUGIN_H +#ifdef __cplusplus +extern "C" { +#endif + #define PLUGIN_EXIT_ERROR -2 #define PLUGIN_EXIT_NORMAL -1 #define PLUGIN_RUNNING 0 @@ -77,4 +81,8 @@ uint16_t get_local_port(); void stop_plugin(); int is_plugin_running(); +#ifdef __cplusplus +} +#endif + #endif // _PLUGIN_H diff --git a/src/ppbloom.c b/src/ppbloom.cpp similarity index 100% rename from src/ppbloom.c rename to src/ppbloom.cpp diff --git a/src/ppbloom.h b/src/ppbloom.h index bfc65bba..258b95da 100644 --- a/src/ppbloom.h +++ b/src/ppbloom.h @@ -23,9 +23,17 @@ #ifndef _PPBLOOM_ #define _PPBLOOM_ +#ifdef __cplusplus +extern "C" { +#endif + int ppbloom_init(int entries, double error); int ppbloom_check(const void *buffer, int len); int ppbloom_add(const void *buffer, int len); void ppbloom_free(void); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/process.c b/src/process.cpp similarity index 91% rename from src/process.c rename to src/process.cpp index d6c6a041..74e15f2c 100644 --- a/src/process.c +++ b/src/process.cpp @@ -32,7 +32,7 @@ static char * copy_string(const char *text) { size_t size = strlen(text) + 1; - char *copy = ss_malloc(size); + char *copy = (char *)ss_malloc(size); memcpy(copy, text, size); return copy; } @@ -40,7 +40,7 @@ copy_string(const char *text) struct ss_process * ss_process_new(const char *program) { - struct ss_process *process = ss_malloc(sizeof(*process)); + struct ss_process *process = (struct ss_process *)ss_malloc(sizeof(*process)); memset(process, 0, sizeof(*process)); process->program = copy_string(program); return process; @@ -49,7 +49,7 @@ ss_process_new(const char *program) void ss_process_arg(struct ss_process *process, const char *argument) { - process->argv = ss_realloc(process->argv, (process->argc + 2) * sizeof(char *)); + process->argv = (char **)ss_realloc(process->argv, (process->argc + 2) * sizeof(char *)); process->argv[process->argc++] = copy_string(argument); process->argv[process->argc] = NULL; } @@ -58,9 +58,9 @@ void ss_process_env(struct ss_process *process, const char *name, const char *value) { size_t size = strlen(name) + strlen(value) + 2; - char *entry = ss_malloc(size); + char *entry = (char *)ss_malloc(size); snprintf(entry, size, "%s=%s", name, value); - process->env = ss_realloc(process->env, (process->envc + 2) * sizeof(char *)); + process->env = (char **)ss_realloc(process->env, (process->envc + 2) * sizeof(char *)); process->env[process->envc++] = entry; process->env[process->envc] = NULL; } @@ -90,7 +90,7 @@ ss_process_start(struct ss_process *process, uint16_t control_port) while (environ[count] != NULL) { count++; } - char **env = ss_malloc((count + process->envc + 1) * sizeof(char *)); + char **env = (char **)ss_malloc((count + process->envc + 1) * sizeof(char *)); for (size_t i = 0; i < count; i++) { if (!overridden(process, environ[i])) { env[used++] = environ[i]; @@ -164,7 +164,7 @@ wide_string(const char *text) { int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, -1, NULL, 0); if (count == 0) return NULL; - wchar_t *wide = ss_malloc((size_t)count * sizeof(wchar_t)); + wchar_t *wide = (wchar_t *)ss_malloc((size_t)count * sizeof(wchar_t)); if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, -1, wide, count)) { free(wide); return NULL; @@ -175,7 +175,7 @@ wide_string(const char *text) static DWORD WINAPI monitor_process(void *argument) { - struct ss_process *process = argument; + struct ss_process *process = (struct ss_process *)argument; WaitForSingleObject(process->child, INFINITE); if (process->control_port != 0) { SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); @@ -207,11 +207,11 @@ sort_environment(wchar_t *env) count++; length += wcslen(entry) + 1; } - wchar_t **entries = ss_malloc((count + 1) * sizeof(*entries)); + wchar_t **entries = (wchar_t **)ss_malloc((count + 1) * sizeof(*entries)); size_t index = 0; for (wchar_t *entry = env; *entry; entry += wcslen(entry) + 1) entries[index++] = entry; qsort(entries, count, sizeof(*entries), compare_environment); - wchar_t *sorted = ss_malloc((length + 1) * sizeof(*sorted)), *out = sorted; + wchar_t *sorted = (wchar_t *)ss_malloc((length + 1) * sizeof(*sorted)), *out = sorted; for (size_t i = 0; i < count; i++) { size_t size = wcslen(entries[i]) + 1; memcpy(out, entries[i], size * sizeof(*out)); @@ -231,7 +231,7 @@ ss_process_start(struct ss_process *process, uint16_t control_port) * Double backslashes before a quote or the closing quote. */ size_t capacity = 1; for (size_t i = 0; i < process->argc; i++) capacity += 2 * strlen(process->argv[i]) + 4; - char *command = ss_malloc(capacity), *out = command; + char *command = (char *)ss_malloc(capacity), *out = command; for (size_t i = 0; i < process->argc; i++) { if (i != 0) *out++ = ' '; *out++ = '"'; @@ -254,7 +254,7 @@ ss_process_start(struct ss_process *process, uint16_t control_port) wchar_t *inherited = GetEnvironmentStringsW(); if (inherited == NULL) { free(wide_command); return -1; } - wchar_t **overrides = ss_malloc((process->envc + 1) * sizeof(wchar_t *)); + wchar_t **overrides = (wchar_t **)ss_malloc((process->envc + 1) * sizeof(wchar_t *)); size_t total = 2; for (const wchar_t *entry = inherited; *entry; entry += wcslen(entry) + 1) total += wcslen(entry) + 1; for (size_t i = 0; i < process->envc; i++) { @@ -266,7 +266,7 @@ ss_process_start(struct ss_process *process, uint16_t control_port) } total += wcslen(overrides[i]) + 1; } - wchar_t *env = ss_malloc(total * sizeof(wchar_t)), *dest = env; + wchar_t *env = (wchar_t *)ss_malloc(total * sizeof(wchar_t)), *dest = env; for (const wchar_t *entry = inherited; *entry; entry += wcslen(entry) + 1) { const wchar_t *equals = wcschr(entry, L'='); bool replace = false; @@ -292,7 +292,7 @@ ss_process_start(struct ss_process *process, uint16_t control_port) PROCESS_INFORMATION child = {0}; startup.cb = sizeof(startup); process->job = CreateJobObjectW(NULL, NULL); - JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = {0}; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = {}; limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; BOOL ok = process->job != NULL && SetInformationJobObject(process->job, JobObjectExtendedLimitInformation, &limits, sizeof(limits)); diff --git a/src/redir.h b/src/redir.h index a03bf9cf..8e9af0e6 100644 --- a/src/redir.h +++ b/src/redir.h @@ -27,6 +27,10 @@ #include "crypto.h" #include "jconf.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct listen_ctx { ss_io io; int remote_num; @@ -75,4 +79,8 @@ typedef struct remote { struct sockaddr *addr; } remote_t; +#ifdef __cplusplus +} +#endif + #endif // _REDIR_H diff --git a/src/resolv.h b/src/resolv.h index 326eca0d..ef35fb44 100644 --- a/src/resolv.h +++ b/src/resolv.h @@ -35,6 +35,10 @@ #include #endif +#ifdef __cplusplus +extern "C" { +#endif + struct ss_loop; struct resolv_query; @@ -44,4 +48,8 @@ void resolv_start(const char *hostname, uint16_t port, void (*free_cb)(void *), void *data); void resolv_shutdown(struct ss_loop *); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/rule.c b/src/rule.cpp similarity index 99% rename from src/rule.c rename to src/rule.cpp index 3821829a..c76dade2 100644 --- a/src/rule.c +++ b/src/rule.cpp @@ -40,7 +40,7 @@ new_rule() { rule_t *rule; - rule = calloc(1, sizeof(rule_t)); + rule = (rule_t *)calloc(1, sizeof(rule_t)); if (rule == NULL) { ERROR("malloc"); return NULL; diff --git a/src/rule.h b/src/rule.h index b98aeed5..45f19c4f 100644 --- a/src/rule.h +++ b/src/rule.h @@ -38,6 +38,10 @@ #include #endif +#ifdef __cplusplus +extern "C" { +#endif + typedef struct rule { struct ss_list_item entries; @@ -61,4 +65,8 @@ void remove_rule(rule_t *); rule_t *new_rule(); int accept_rule_arg(rule_t *, const char *); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/server.h b/src/server.h index 8e0762f6..39407216 100644 --- a/src/server.h +++ b/src/server.h @@ -38,6 +38,10 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct listen_ctx { ss_io io; int fd; @@ -114,4 +118,8 @@ typedef struct remote { struct server *server; } remote_t; +#ifdef __cplusplus +} +#endif + #endif // _SERVER_H diff --git a/src/socks5.h b/src/socks5.h index 42dbcfe2..d94647df 100644 --- a/src/socks5.h +++ b/src/socks5.h @@ -23,6 +23,10 @@ #ifndef _SOCKS5_H #define _SOCKS5_H +#ifdef __cplusplus +extern "C" { +#endif + #define SVERSION 0x05 #define METHOD_NOAUTH 0x00 #define METHOD_UNACCEPTABLE 0xff @@ -72,4 +76,8 @@ struct socks5_response { unsigned char atyp; } __attribute__((packed, aligned(1))); +#ifdef __cplusplus +} +#endif + #endif // _SOCKS5_H diff --git a/src/ss_event.h b/src/ss_event.h index a92bd103..b05ed1d1 100644 --- a/src/ss_event.h +++ b/src/ss_event.h @@ -3,6 +3,10 @@ #ifndef SS_EVENT_H #define SS_EVENT_H #include + +#ifdef __cplusplus +extern "C" { +#endif /* Keep platform headers private: Linux TCP headers differ from libc headers. */ struct uv_loop_s; struct ss_loop; @@ -55,4 +59,8 @@ void ss_timer_again(struct ss_loop *, ss_timer *); void ss_signal_init(ss_signal *, void (*)(struct ss_loop *, ss_signal *, int), int); void ss_signal_start(struct ss_loop *, ss_signal *); void ss_signal_stop(struct ss_loop *, ss_signal *); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/ss_process.h b/src/ss_process.h index d0f97a5f..619060a3 100644 --- a/src/ss_process.h +++ b/src/ss_process.h @@ -3,6 +3,10 @@ #define SS_PROCESS_H #include #include + +#ifdef __cplusplus +extern "C" { +#endif struct ss_process; struct ss_process *ss_process_new(const char *program); void ss_process_arg(struct ss_process *process, const char *argument); @@ -10,4 +14,8 @@ void ss_process_env(struct ss_process *process, const char *name, const char *va int ss_process_start(struct ss_process *process, uint16_t control_port); bool ss_process_running(struct ss_process *process); void ss_process_free(struct ss_process *process); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/ss_windows.h b/src/ss_windows.h index e17892ea..9ddb5b15 100644 --- a/src/ss_windows.h +++ b/src/ss_windows.h @@ -44,6 +44,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + // Check if ConnectEx supported in header #ifdef WSAID_CONNECTEX // Hardcode TCP fast open option @@ -80,6 +84,10 @@ LPFN_CONNECTEX winsock_getconnectex(void); int winsock_dummybind(SOCKET fd, struct sockaddr *sa); #endif +#ifdef __cplusplus +} +#endif + #endif // __MINGW32__ #endif // SS_WINSOCK_COMPAT_H diff --git a/src/ssurl.c b/src/ssurl.cpp similarity index 91% rename from src/ssurl.c rename to src/ssurl.cpp index d319b71c..1b03944e 100644 --- a/src/ssurl.c +++ b/src/ssurl.cpp @@ -50,7 +50,7 @@ find_last(const char *s, size_t len, char c) static char * str_ndup(const char *s, size_t n) { - char *out = ss_malloc(n + 1); + char *out = (char *)ss_malloc(n + 1); memcpy(out, s, n); out[n] = '\0'; @@ -73,7 +73,7 @@ hex_value(char c) static char * percent_decode(const char *s, size_t len) { - char *out = ss_malloc(len + 1); + char *out = (char *)ss_malloc(len + 1); size_t o = 0; for (size_t i = 0; i < len; i++) { @@ -106,7 +106,7 @@ base64_dup(const char *s, size_t len) char *in = str_ndup(s, len); /* decoded output is always shorter than the encoded input */ - uint8_t *out = ss_malloc(len + 1); + uint8_t *out = (uint8_t *)ss_malloc(len + 1); int out_len = base64_decode(out, in, (int)len); ss_free(in); @@ -131,7 +131,7 @@ split_host_port(const char *s, size_t len, ss_url_t *url) return -1; if (s[0] == '[') { - const char *close = memchr(s, ']', len); + const char *close = (const char *)memchr(s, ']', len); if (close == NULL) return -1; @@ -186,9 +186,9 @@ parse_query(const char *s, size_t len, ss_url_t *url) const char *end = s + len; while (s < end) { - const char *amp = memchr(s, '&', (size_t)(end - s)); + const char *amp = (const char *)memchr(s, '&', (size_t)(end - s)); const char *pair_end = amp ? amp : end; - const char *eq = memchr(s, '=', (size_t)(pair_end - s)); + const char *eq = (const char *)memchr(s, '=', (size_t)(pair_end - s)); if (eq != NULL) { size_t name_len = (size_t)(eq - s); @@ -244,13 +244,15 @@ ss_url_parse(const char *uri, ss_url_t *url) return -1; /* The fragment is a human-readable tag and is not part of the config. */ - const char *hash = memchr(body, '#', body_len); + // NOLINTNEXTLINE(bugprone-not-null-terminated-result): memchr searches a bounded span, no string is produced + const char *hash = (const char *)memchr(body, '#', body_len); if (hash != NULL) { url->tag = percent_decode(hash + 1, body_len - (size_t)(hash - body) - 1); body_len = (size_t)(hash - body); } - const char *query = memchr(body, '?', body_len); + // NOLINTNEXTLINE(bugprone-not-null-terminated-result): memchr searches a bounded span, no string is produced + const char *query = (const char *)memchr(body, '?', body_len); size_t head_len = query ? (size_t)(query - body) : body_len; /* diff --git a/src/ssurl.h b/src/ssurl.h index 251f6fb2..f23511b5 100644 --- a/src/ssurl.h +++ b/src/ssurl.h @@ -23,6 +23,10 @@ #ifndef _SSURL_H #define _SSURL_H +#ifdef __cplusplus +extern "C" { +#endif + /* * A parsed ss:// URI. Every field is heap-allocated and owned by the * struct; release them all with ss_url_free(). Fields that the URI did not @@ -55,4 +59,8 @@ int ss_url_parse(const char *uri, ss_url_t *url); /* Free every field and zero the struct. Safe on a zeroed struct. */ void ss_url_free(ss_url_t *url); +#ifdef __cplusplus +} +#endif + #endif // _SSURL_H diff --git a/src/stream.h b/src/stream.h index 35bd98f8..d9bd7ddb 100644 --- a/src/stream.h +++ b/src/stream.h @@ -41,6 +41,10 @@ #include "crypto.h" +#ifdef __cplusplus +extern "C" { +#endif + int stream_encrypt_all(buffer_t *, cipher_t *, size_t); int stream_decrypt_all(buffer_t *, cipher_t *, size_t); int stream_encrypt(buffer_t *, cipher_ctx_t *, size_t); @@ -51,4 +55,8 @@ void stream_ctx_release(cipher_ctx_t *); cipher_t *stream_init(const char *pass, const char *key, const char *method); +#ifdef __cplusplus +} +#endif + #endif // _STREAM_H diff --git a/src/tunnel.h b/src/tunnel.h index 76e0b394..773a6db3 100644 --- a/src/tunnel.h +++ b/src/tunnel.h @@ -34,6 +34,10 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct listen_ctx { ss_io io; ss_addr_t tunnel_addr; @@ -84,4 +88,8 @@ typedef struct remote { uint32_t counter; } remote_t; +#ifdef __cplusplus +} +#endif + #endif // _TUNNEL_H diff --git a/src/udprelay.h b/src/udprelay.h index e020ec99..818ae3d9 100644 --- a/src/udprelay.h +++ b/src/udprelay.h @@ -38,6 +38,10 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + #define MAX_UDP_PACKET_SIZE (65507) #define PACKET_HEADER_SIZE (1 + 28 + 2 + 64) @@ -92,4 +96,8 @@ typedef struct remote_ctx { struct server_ctx *server_ctx; } remote_ctx_t; +#ifdef __cplusplus +} +#endif + #endif // _UDPRELAY_H diff --git a/src/utils.c b/src/utils.c index 6648b240..de010054 100644 --- a/src/utils.c +++ b/src/utils.c @@ -164,8 +164,10 @@ run_as(const char *user) size_t buflen; int err; + /* The loop below gives up beyond 16 KiB, so one fixed buffer covers + * every attempt. */ + char buf[16 * 1024]; for (buflen = 128;; buflen *= 2) { - char buf[buflen]; /* variable length array */ /* Note that we use getpwnam_r() instead of getpwnam(), * which returns its result in a statically allocated buffer and @@ -256,7 +258,7 @@ ss_strndup(const char *s, size_t n) len++; } - ret = ss_malloc(len + 1); + ret = (char *)ss_malloc(len + 1); memcpy(ret, s, len); ret[len] = '\0'; return ret; @@ -302,13 +304,13 @@ ss_aligned_malloc(size_t size) void * ss_realloc(void *ptr, size_t new_size) { - void *new = realloc(ptr, new_size); - if (new == NULL) { + void *resized = realloc(ptr, new_size); + if (resized == NULL) { free(ptr); ptr = NULL; exit(EXIT_FAILURE); } - return new; + return resized; } int @@ -803,7 +805,7 @@ daemonize(const char *path) int set_nofile(int nofile) { - struct rlimit limit = { nofile, nofile }; /* set both soft and hard limit */ + struct rlimit limit = { (rlim_t)nofile, (rlim_t)nofile }; /* set both soft and hard limit */ if (nofile <= 0) { FATAL("nofile must be greater than 0\n"); diff --git a/src/utils.h b/src/utils.h index 0f8c9498..a582a6de 100644 --- a/src/utils.h +++ b/src/utils.h @@ -30,6 +30,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define PORTSTRLEN 16 #define SS_ADDRSTRLEN (INET6_ADDRSTRLEN + PORTSTRLEN + 1) @@ -252,4 +256,8 @@ char *get_default_conf(void); uint16_t load16_be(const void *s); int get_mptcp(int enable); +#ifdef __cplusplus +} +#endif + #endif // _UTILS_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c950432c..49145de9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,6 +60,7 @@ endfunction() # C++ core (src/core): header-only ownership and allocation primitives. foreach(core_test result pool buffer arena fixed_map ownership) ss_add_test(test_core_${core_test} "test_core_${core_test}.cpp" "") + ss_strict_warnings(test_core_${core_test}) endforeach() # C++ sources under src/ must not allocate on the heap (see the script). @@ -68,11 +69,30 @@ if(Python3_Interpreter_FOUND) add_test(NAME check_no_heap COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/check_no_heap.py --self-test) set_tests_properties(check_no_heap PROPERTIES LABELS "unit") + + # The programs and the static library must stay usable without a C++ + # runtime library (see docs/cpp-core.md). Sanitizer runtimes bring their + # own C++ symbols, so skip the check there. + set(SS_RUNTIME_CHECKED) + foreach(target ss-local ss-server ss-tunnel ss-redir ss-manager shadowsocks-c-static) + if(TARGET ${target}) + get_target_property(excluded ${target} EXCLUDE_FROM_ALL) + if(NOT excluded) + list(APPEND SS_RUNTIME_CHECKED $) + endif() + endif() + endforeach() + if(SS_RUNTIME_CHECKED AND CMAKE_NM AND NOT ENABLE_SANITIZERS) + add_test(NAME check_cxx_runtime + COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/check_cxx_runtime.py + --nm ${CMAKE_NM} ${SS_RUNTIME_CHECKED}) + set_tests_properties(check_cxx_runtime PROPERTIES LABELS "unit") + endif() endif() # test_base64 - no external deps ss_add_test(test_base64 - "test_base64.c;${PROJECT_SOURCE_DIR}/src/base64.c" + "test_base64.c;${PROJECT_SOURCE_DIR}/src/base64.cpp" "") # test_utils - needs sodium (for ss_malloc -> uses sodium in utils.c) @@ -82,12 +102,12 @@ ss_add_test(test_utils # test_json - standalone json parser ss_add_test(test_json - "test_json.c;${PROJECT_SOURCE_DIR}/src/json.c" + "test_json.c;${PROJECT_SOURCE_DIR}/src/json.cpp" "${SS_MATH_LIBRARY}") # test_ssurl - ss:// URI parsing (SIP002 and legacy) ss_add_test(test_ssurl - "test_ssurl.c;${PROJECT_SOURCE_DIR}/src/ssurl.c;${PROJECT_SOURCE_DIR}/src/base64.c;${PROJECT_SOURCE_DIR}/src/utils.c" + "test_ssurl.c;${PROJECT_SOURCE_DIR}/src/ssurl.cpp;${PROJECT_SOURCE_DIR}/src/base64.cpp;${PROJECT_SOURCE_DIR}/src/utils.c" "ss_sodium") # test_netutils - address parsing and resolver integration @@ -102,14 +122,14 @@ ss_add_test(test_netutils # test_cache - expiration uses a platform monotonic clock ss_add_test(test_cache - "test_cache.c;${PROJECT_SOURCE_DIR}/src/cache.c;${PROJECT_SOURCE_DIR}/src/utils.c" + "test_cache.c;${PROJECT_SOURCE_DIR}/src/cache.cpp;${PROJECT_SOURCE_DIR}/src/utils.c" "ss_sodium") # test_ppbloom - needs bloom set(TEST_PPBLOOM_LIBS "") list(APPEND TEST_PPBLOOM_LIBS bloom) ss_add_test(test_ppbloom - "test_ppbloom.c;${PROJECT_SOURCE_DIR}/src/ppbloom.c;${PROJECT_SOURCE_DIR}/src/utils.c" + "test_ppbloom.c;${PROJECT_SOURCE_DIR}/src/ppbloom.cpp;${PROJECT_SOURCE_DIR}/src/utils.c" "${TEST_PPBLOOM_LIBS};ss_sodium;${SS_MATH_LIBRARY}") if(SS_ENABLE_REGEX) @@ -119,13 +139,13 @@ set(TEST_RULE_LIBS ss_sodium ) ss_add_test(test_rule - "test_rule.c;${PROJECT_SOURCE_DIR}/src/rule.c;${PROJECT_SOURCE_DIR}/src/utils.c" + "test_rule.c;${PROJECT_SOURCE_DIR}/src/rule.cpp;${PROJECT_SOURCE_DIR}/src/utils.c" "${TEST_RULE_LIBS}") # test_acl - ACL precedence and prefix matching set(TEST_ACL_LIBS ${TEST_RULE_LIBS}) ss_add_test(test_acl - "test_acl.c;${PROJECT_SOURCE_DIR}/src/acl.c;${PROJECT_SOURCE_DIR}/src/ipset.c;${PROJECT_SOURCE_DIR}/src/rule.c;${PROJECT_SOURCE_DIR}/src/utils.c" + "test_acl.c;${PROJECT_SOURCE_DIR}/src/acl.c;${PROJECT_SOURCE_DIR}/src/ipset.cpp;${PROJECT_SOURCE_DIR}/src/rule.cpp;${PROJECT_SOURCE_DIR}/src/utils.c" "${TEST_ACL_LIBS}") endif() @@ -137,7 +157,7 @@ set(TEST_JCONF_LIBS ss_cares ) ss_add_test(test_jconf - "test_jconf.c;${PROJECT_SOURCE_DIR}/src/jconf.c;${PROJECT_SOURCE_DIR}/src/json.c;${PROJECT_SOURCE_DIR}/src/utils.c;${PROJECT_SOURCE_DIR}/src/netutils.c" + "test_jconf.c;${PROJECT_SOURCE_DIR}/src/jconf.c;${PROJECT_SOURCE_DIR}/src/json.cpp;${PROJECT_SOURCE_DIR}/src/utils.c;${PROJECT_SOURCE_DIR}/src/netutils.c" "${TEST_JCONF_LIBS};${SS_MATH_LIBRARY}") # Crypto sources shared by the tests that link the crypto layer. The AEAD-2022 @@ -148,9 +168,9 @@ set(SS_CRYPTO_TEST_SOURCES ${PROJECT_SOURCE_DIR}/src/aead.c ${PROJECT_SOURCE_DIR}/src/aead2022.c ${PROJECT_SOURCE_DIR}/src/aead2022_udp.c - ${PROJECT_SOURCE_DIR}/src/ppbloom.c - ${PROJECT_SOURCE_DIR}/src/cache.c - ${PROJECT_SOURCE_DIR}/src/base64.c + ${PROJECT_SOURCE_DIR}/src/ppbloom.cpp + ${PROJECT_SOURCE_DIR}/src/cache.cpp + ${PROJECT_SOURCE_DIR}/src/base64.cpp ${PROJECT_SOURCE_DIR}/src/utils.c ) @@ -200,11 +220,11 @@ add_test(NAME test_ss_setup set_tests_properties(test_ss_setup PROPERTIES LABELS "integration") ss_add_test(test_ipset - "test_ipset.c;${PROJECT_SOURCE_DIR}/src/ipset.c;${PROJECT_SOURCE_DIR}/src/utils.c" + "test_ipset.c;${PROJECT_SOURCE_DIR}/src/ipset.cpp;${PROJECT_SOURCE_DIR}/src/utils.c" "ss_sodium") ss_add_test(test_process - "test_process.c;${PROJECT_SOURCE_DIR}/src/process.c;${PROJECT_SOURCE_DIR}/src/utils.c" + "test_process.c;${PROJECT_SOURCE_DIR}/src/process.cpp;${PROJECT_SOURCE_DIR}/src/utils.c" "ss_sodium") set_tests_properties(test_process PROPERTIES TIMEOUT 20) @@ -218,7 +238,7 @@ if(SS_ENABLE_REGEX) list(APPEND TEST_LITERAL_LIBS ss_pcre2) endif() ss_add_test(test_acl_literal - "test_acl_literal.c;${PROJECT_SOURCE_DIR}/src/acl.c;${PROJECT_SOURCE_DIR}/src/ipset.c;${PROJECT_SOURCE_DIR}/src/rule.c;${PROJECT_SOURCE_DIR}/src/utils.c" + "test_acl_literal.c;${PROJECT_SOURCE_DIR}/src/acl.c;${PROJECT_SOURCE_DIR}/src/ipset.cpp;${PROJECT_SOURCE_DIR}/src/rule.cpp;${PROJECT_SOURCE_DIR}/src/utils.c" "${TEST_LITERAL_LIBS}") ss_add_test(test_event "test_event.c" "ss_ev")