Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# clang-tidy checks for MafiaNet, read by clangd (inline diagnostics) and by
# `run-clang-tidy -p build/<preset>`. Focused on correctness plus the
# mechanical modernizations clang-tidy can fix on its own. Not a CI gate: on
# the legacy tree it would drown the signal.
---
Checks: >
-*,
bugprone-*,
performance-*,
modernize-use-override,
modernize-use-nullptr,
modernize-use-using,
modernize-use-equals-default,
modernize-use-equals-delete,
modernize-use-bool-literals,
modernize-loop-convert,
modernize-use-emplace,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-virtual-class-destructor,
readability-redundant-control-flow,
readability-redundant-string-init,
-bugprone-easily-swappable-parameters,
-bugprone-narrowing-conversions,
-bugprone-reserved-identifier,
-performance-no-int-to-ptr,
-performance-enum-size

# Diagnostics only.
WarningsAsErrors: ''

# Only diagnose project headers, not fetched third-party dependencies.
HeaderFilterRegex: 'Source/(include|src)/.*'

FormatStyle: none
5 changes: 5 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# clangd reads compile_commands.json from the `debug` preset's build tree
# (cmake --preset debug). Point it elsewhere locally with a user config if you
# work from another preset.
CompileFlags:
CompilationDatabase: build/debug
25 changes: 25 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Linux development container. Mainly for working on the Linux-only batched
// datagram paths (recvmmsg/sendmmsg) and running the sanitizer presets from a
// macOS or Windows host: open the folder in VS Code / Codespaces, then
// `cmake --workflow --preset debug` (or asan / tsan).
{
"name": "MafiaNet",
"image": "mcr.microsoft.com/devcontainers/cpp:1-ubuntu-24.04",
// ptrace for gdb; the default seccomp profile also blocks the personality()
// call TSan uses to disable ASLR.
"runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
"postCreateCommand": "sudo apt-get update && sudo apt-get install -y --no-install-recommends libssl-dev clang clangd clang-tidy ccache",
"customizations": {
"vscode": {
"extensions": [
"llvm-vs-code-extensions.vscode-clangd",
"ms-vscode.cmake-tools",
"vadimcn.vscode-lldb"
],
"settings": {
"cmake.useCMakePresets": "always",
"C_Cpp.intelliSenseEngine": "disabled"
}
}
}
}
26 changes: 26 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# EditorConfig -- https://editorconfig.org
# Mirrors .clang-format. No end_of_line: the tree mixes CRLF and LF files, and
# forcing either would rewrite whole files on save.
root = true

[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true

[*.{c,cpp,cc,cxx,h,hpp,hxx,inl}]
indent_style = tab
indent_size = 4
tab_width = 4

[{CMakeLists.txt,*.cmake,CMakePresets.json}]
indent_style = space
indent_size = 4

[*.{yml,yaml,json}]
indent_style = space
indent_size = 2

[*.{md,rst}]
indent_style = space
trim_trailing_whitespace = false
25 changes: 25 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# GitHub Actions and the docs toolchain. The CMake FetchContent pins
# (cmake/Fetch*.cmake, Tests/CMakeLists.txt) are not covered by Dependabot and
# are bumped by hand.
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
groups:
actions:
patterns: ["*"]

- package-ecosystem: pip
directory: /docs
schedule:
interval: monthly
groups:
docs:
patterns: ["*"]

- package-ecosystem: docker
directory: /
schedule:
interval: monthly
103 changes: 103 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ concurrency:
permissions:
contents: read

# sccache keeps compiled objects in the GitHub Actions cache, so a PR rebuilds
# only the translation units it touched. Jobs opt in by running
# mozilla-actions/sccache-action and passing it as the compiler launcher.
env:
SCCACHE_GHA_ENABLED: "true"

jobs:
# Linux: Build and test in Docker container (full network access).
# Tests run through CTest inside the container: one process per test,
Expand Down Expand Up @@ -86,10 +92,15 @@ jobs:
[ -e "Source/include/mafianet/$n" ] && echo "$f:$l: $rest"; done || true)
if [ -n "$bad" ]; then echo "Bare includes of library headers (use \"mafianet/<name>.h\"):"; echo "$bad"; exit 1; fi

- name: Set up sccache
uses: mozilla-actions/sccache-action@v0.0.11

- name: Configure CMake
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
-DMAFIANET_BUILD_TESTS=ON \
-DMAFIANET_BUILD_EXTRAS=ON

Expand Down Expand Up @@ -139,10 +150,15 @@ jobs:
- name: Install OpenSSL
run: sudo apt-get update && sudo apt-get install -y libssl-dev

- name: Set up sccache
uses: mozilla-actions/sccache-action@v0.0.11

- name: Configure CMake (minimal)
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
-DMAFIANET_MINIMAL=ON

- name: Build
Expand All @@ -151,6 +167,88 @@ jobs:
- name: Report library size
run: ls -l build/Source/libMafiaNetStatic.a build/Source/libmafianet.so*

# Sanitizers, via the asan / tsan presets (RelWithDebInfo + tests; see
# CMakePresets.json for why not Debug). Every target is instrumented,
# third-party dependencies included, so TSan sees both sides of each access.
#
# TSan is advisory: RakNet's public getters read connection state owned by the
# network thread without locking, by design. Those races are baselined in
# ci/tsan.supp so the job flags new ones; removing them needs a
# concurrency-model change, not a CI tweak.
sanitizers:
runs-on: ubuntu-latest
timeout-minutes: 60
continue-on-error: ${{ matrix.preset == 'tsan' }}
strategy:
fail-fast: false
matrix:
preset: [asan, tsan]
name: sanitizers (${{ matrix.preset }})
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y libssl-dev ninja-build clang

# Ubuntu 24.04 runners use 32 bits of mmap randomisation, which older TSan
# runtimes cannot map their shadow memory around.
- name: Reduce ASLR entropy for TSan
if: matrix.preset == 'tsan'
run: sudo sysctl vm.mmap_rnd_bits=28

- name: Set up sccache
uses: mozilla-actions/sccache-action@v0.0.11

- name: Configure CMake
run: |
cmake --preset ${{ matrix.preset }} -G Ninja \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache

- name: Build
run: cmake --build --preset ${{ matrix.preset }}

- name: Run unit tests
run: |
ctest --preset ${{ matrix.preset }} -L unit \
--parallel 4 \
--output-junit junit-unit.xml

# Retries absorb loopback timing misses, which sanitizer slowdown makes
# more frequent. They must not absorb a sanitizer report, so the log of
# every attempt is kept and the next step fails on any report in it.
- name: Run integration tests
run: |
ctest --preset ${{ matrix.preset }} -L integration \
--repeat until-pass:3 \
--output-log build/${{ matrix.preset }}/integration.log \
--output-junit junit-integration.xml
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Fail on sanitizer reports from any attempt
run: |
# A missing log would make grep exit 2, which the if reads as "clean".
test -f build/${{ matrix.preset }}/integration.log || { echo "::error::integration.log missing"; exit 1; }
if grep -nE 'ERROR: AddressSanitizer|WARNING: ThreadSanitizer|runtime error:' \
build/${{ matrix.preset }}/integration.log; then
echo "::error::A sanitizer reported an error in an attempt that a retry later passed"
exit 1
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-sanitizers-${{ matrix.preset }}
path: |
build/${{ matrix.preset }}/junit-*.xml
build/${{ matrix.preset }}/integration.log
if-no-files-found: ignore

# macOS: Build and run both suites (unit + integration).
macos:
runs-on: macos-latest
Expand All @@ -163,10 +261,15 @@ jobs:
- name: Install OpenSSL
run: brew install openssl@3

- name: Set up sccache
uses: mozilla-actions/sccache-action@v0.0.11

- name: Configure CMake
run: |
cmake -B build \
-DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3) \
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
-DMAFIANET_BUILD_SAMPLES=ON \
-DMAFIANET_BUILD_TESTS=ON

Expand Down
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ Available generators (run `cmake --help` for full list):
| `MAFIANET_BUILD_EXTRAS` | OFF | Build `MafiaNetExtras` (EmailSender, HTTPConnection*, Telnet/RakNet transports, ConsoleServer, command parsers, PacketConsoleLogger); forced ON by `MAFIANET_BUILD_SAMPLES` |
| `MAFIANET_DISABLED_FEATURES` | (empty) | Semicolon list of plugins to compile out (`_RAKNET_SUPPORT_<Name>=0`); names validated against `MAFIANET_OPTIONAL_FEATURES` in `Source/CMakeLists.txt` |
| `MAFIANET_MINIMAL` | OFF | Compile out every optional plugin (core transport only); built by the `linux-minimal` CI job |
| `MAFIANET_SANITIZER` | (empty) | `address+undefined` or `thread`; applied to every target. Prefer the `asan` / `tsan` presets |
| `MAFIANET_USE_COMPILER_CACHE` | ON | Use sccache/ccache as compiler launcher when found on `PATH` |

Presets (`CMakePresets.json`, CMake 3.25+): `debug`, `release`, `ci`, `minimal`, `asan`, `tsan`.
`cmake --workflow --preset <debug|asan|tsan>` configures, builds and tests in one go; run
sanitizer tests through `ctest --preset asan|tsan` so the sanitizer options and
`ci/tsan.supp` are applied. Known by-design TSan races live in `ci/tsan.supp`; add an entry only
for a race that is part of RakNet's lock-free getter model, never to silence a new bug.

When adding or removing a plugin guarded by a `_RAKNET_SUPPORT_<Name>` flag, update
`MAFIANET_OPTIONAL_FEATURES` in `Source/CMakeLists.txt` as well, or the CMake
Expand Down
31 changes: 31 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,42 @@ set(MAFIANET_DISABLED_FEATURES "" CACHE STRING
"Semicolon-separated plugin names to compile out (see MAFIANET_OPTIONAL_FEATURES in Source/CMakeLists.txt)")
option(MAFIANET_MINIMAL "Compile out every optional plugin (core transport only)" OFF)

# Sanitizers (Clang/GCC only): a "+"- or ","-separated list such as
# "address+undefined" or "thread" ("thread" cannot be combined with "address").
# Recoverable UBSan findings are made fatal so they fail the test that hit them.
set(MAFIANET_SANITIZER "" CACHE STRING "Build with sanitizers (e.g. address+undefined, thread)")

# Compiler cache. sccache or ccache is picked up automatically when installed,
# unless the caller already chose a launcher (CI passes one explicitly).
option(MAFIANET_USE_COMPILER_CACHE "Use sccache/ccache when available" ON)

# Ensure at least one library type is built
if(NOT MAFIANET_BUILD_SHARED AND NOT MAFIANET_BUILD_STATIC)
message(FATAL_ERROR "At least one of MAFIANET_BUILD_SHARED or MAFIANET_BUILD_STATIC must be ON")
endif()

if(MAFIANET_USE_COMPILER_CACHE AND NOT CMAKE_CXX_COMPILER_LAUNCHER AND NOT CMAKE_C_COMPILER_LAUNCHER)
find_program(MAFIANET_COMPILER_CACHE NAMES sccache ccache)
if(MAFIANET_COMPILER_CACHE)
message(STATUS "MafiaNet: compiler cache ${MAFIANET_COMPILER_CACHE}")
set(CMAKE_C_COMPILER_LAUNCHER "${MAFIANET_COMPILER_CACHE}")
set(CMAKE_CXX_COMPILER_LAUNCHER "${MAFIANET_COMPILER_CACHE}")
endif()
endif()

# Applied globally, so FetchContent dependencies are instrumented too: a
# sanitizer runtime with half the program uninstrumented reports false positives
# (notably TSan) and misses overflows that cross into third-party code.
if(MAFIANET_SANITIZER)
if(MSVC)
message(FATAL_ERROR "MAFIANET_SANITIZER is not supported with MSVC")
endif()
string(REPLACE "+" "," _mafianet_san "${MAFIANET_SANITIZER}")
message(STATUS "MafiaNet: sanitizers ${_mafianet_san}")
add_compile_options(-fsanitize=${_mafianet_san} -fno-sanitize-recover=all -fno-omit-frame-pointer)
add_link_options(-fsanitize=${_mafianet_san})
endif()

# Dependencies
find_package(OpenSSL 3.0.0 REQUIRED)
find_package(Threads REQUIRED)
Expand Down
Loading
Loading