mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
- Fix signed integer overflow UB in negation operations by performing
negation in unsigned domain before casting back to signed. Applies to
IOUAmount, XRPAmount, MPTAmount, and throughout STAmount (operator+,
set, canonicalize, xrp/iou/mpt accessors, constructors).
- Fix post-decrement loop patterns that cause unsigned integer overflow
(e.g. `while(n--)`) by replacing with `while(n > 0) { --n; ... }` or
`for` loops in DecayingSample.h, varint.h, base64.cpp, BasicApp.cpp,
and yield_to.h.
- Add Counts::adjustCounter() helper in PeerFinder to safely adjust
size_t counters by signed values without triggering UBSan.
- Fix uninitialized member in ValidatorSite_test and remove
overflow-dependent initialization in LexicalCast_test.
- Drastically reduce ubsan.supp by removing broad per-file suppressions
now that the underlying issues are fixed. Keep only targeted
suppressions for external libraries (RocksDB, protobuf, gRPC, nudb,
snappy, abseil) and intentional unsigned wraps in rippled (STAmount
arithmetic, nft::cipheredTaxon).
- Remove UBSAN_OPTIONS runtime suppressions file from CI workflow.
- Enable UBSan builds for gcc-13 in addition to clang-20 in CI matrix.
- Add fPIC handling in conanfile.py when Address sanitizer is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
336 lines
13 KiB
Python
Executable File
336 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import argparse
|
|
import itertools
|
|
import json
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
THIS_DIR = Path(__file__).parent.resolve()
|
|
|
|
|
|
@dataclass
|
|
class Config:
|
|
architecture: list[dict]
|
|
os: list[dict]
|
|
build_type: list[str]
|
|
cmake_args: list[str]
|
|
|
|
|
|
"""
|
|
Generate a strategy matrix for GitHub Actions CI.
|
|
|
|
On each PR commit we will build a selection of Debian, RHEL, Ubuntu, MacOS, and
|
|
Windows configurations, while upon merge into the develop or release branches,
|
|
we will build all configurations, and test most of them.
|
|
|
|
We will further set additional CMake arguments as follows:
|
|
- All builds will have the `tests`, `werr`, and `xrpld` options.
|
|
- All builds will have the `wextra` option except for GCC 12 and Clang 16.
|
|
- All release builds will have the `assert` option.
|
|
- Certain Debian Bookworm configurations will change the reference fee, enable
|
|
codecov, and enable voidstar in PRs.
|
|
"""
|
|
|
|
|
|
def generate_strategy_matrix(all: bool, config: Config) -> list:
|
|
configurations = []
|
|
for architecture, os, build_type, cmake_args in itertools.product(
|
|
config.architecture, config.os, config.build_type, config.cmake_args
|
|
):
|
|
# The default CMake target is 'all' for Linux and MacOS and 'install'
|
|
# for Windows, but it can get overridden for certain configurations.
|
|
cmake_target = "install" if os["distro_name"] == "windows" else "all"
|
|
|
|
# We build and test all configurations by default, except for Windows in
|
|
# Debug, because it is too slow, as well as when code coverage is
|
|
# enabled as that mode already runs the tests.
|
|
build_only = False
|
|
if os["distro_name"] == "windows" and build_type == "Debug":
|
|
build_only = True
|
|
|
|
# Only generate a subset of configurations in PRs.
|
|
if not all:
|
|
# Debian:
|
|
# - Bookworm using GCC 13: Release on linux/amd64, set the reference
|
|
# fee to 500.
|
|
# - Bookworm using GCC 15: Debug on linux/amd64, enable code
|
|
# coverage (which will be done below).
|
|
# - Bookworm using Clang 16: Debug on linux/amd64, enable voidstar.
|
|
# - Bookworm using Clang 17: Release on linux/amd64, set the
|
|
# reference fee to 1000.
|
|
# - Bookworm using Clang 20: Debug on linux/amd64.
|
|
if os["distro_name"] == "debian":
|
|
skip = True
|
|
if os["distro_version"] == "bookworm":
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
|
|
and build_type == "Release"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=500 {cmake_args}"
|
|
skip = False
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
|
|
and build_type == "Debug"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
skip = False
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-16"
|
|
and build_type == "Debug"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
cmake_args = f"-Dvoidstar=ON {cmake_args}"
|
|
skip = False
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-17"
|
|
and build_type == "Release"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=1000 {cmake_args}"
|
|
skip = False
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20"
|
|
and build_type == "Debug"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
skip = False
|
|
if skip:
|
|
continue
|
|
|
|
# RHEL:
|
|
# - 9 using GCC 12: Debug on linux/amd64.
|
|
# - 10 using Clang: Release on linux/amd64.
|
|
if os["distro_name"] == "rhel":
|
|
skip = True
|
|
if os["distro_version"] == "9":
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
|
and build_type == "Debug"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
skip = False
|
|
elif os["distro_version"] == "10":
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-any"
|
|
and build_type == "Release"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
skip = False
|
|
if skip:
|
|
continue
|
|
|
|
# Ubuntu:
|
|
# - Jammy using GCC 12: Debug on linux/arm64.
|
|
# - Noble using GCC 14: Release on linux/amd64.
|
|
# - Noble using Clang 18: Debug on linux/amd64.
|
|
# - Noble using Clang 19: Release on linux/arm64.
|
|
if os["distro_name"] == "ubuntu":
|
|
skip = True
|
|
if os["distro_version"] == "jammy":
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
|
and build_type == "Debug"
|
|
and architecture["platform"] == "linux/arm64"
|
|
):
|
|
skip = False
|
|
elif os["distro_version"] == "noble":
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14"
|
|
and build_type == "Release"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
skip = False
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-18"
|
|
and build_type == "Debug"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
skip = False
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-19"
|
|
and build_type == "Release"
|
|
and architecture["platform"] == "linux/arm64"
|
|
):
|
|
skip = False
|
|
if skip:
|
|
continue
|
|
|
|
# MacOS:
|
|
# - Debug on macos/arm64.
|
|
if os["distro_name"] == "macos" and not (
|
|
build_type == "Debug" and architecture["platform"] == "macos/arm64"
|
|
):
|
|
continue
|
|
|
|
# Windows:
|
|
# - Release on windows/amd64.
|
|
if os["distro_name"] == "windows" and not (
|
|
build_type == "Release" and architecture["platform"] == "windows/amd64"
|
|
):
|
|
continue
|
|
|
|
# Additional CMake arguments.
|
|
cmake_args = f"{cmake_args} -Dtests=ON -Dwerr=ON -Dxrpld=ON"
|
|
if not f"{os['compiler_name']}-{os['compiler_version']}" in [
|
|
"gcc-12",
|
|
"clang-16",
|
|
]:
|
|
cmake_args = f"{cmake_args} -Dwextra=ON"
|
|
if build_type == "Release":
|
|
cmake_args = f"{cmake_args} -Dassert=ON"
|
|
|
|
# We skip all RHEL on arm64 due to a build failure that needs further
|
|
# investigation.
|
|
if os["distro_name"] == "rhel" and architecture["platform"] == "linux/arm64":
|
|
continue
|
|
|
|
# We skip all clang 20+ on arm64 due to Boost build error.
|
|
if (
|
|
f"{os['compiler_name']}-{os['compiler_version']}"
|
|
in ["clang-20", "clang-21"]
|
|
and architecture["platform"] == "linux/arm64"
|
|
):
|
|
continue
|
|
|
|
# Enable code coverage for Debian Bookworm using GCC 15 in Debug on
|
|
# linux/amd64
|
|
if (
|
|
f"{os['distro_name']}-{os['distro_version']}" == "debian-bookworm"
|
|
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
|
|
and build_type == "Debug"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
cmake_args = f"{cmake_args} -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
|
|
|
|
# Enable unity build for Ubuntu Jammy using GCC 12 in Debug on
|
|
# linux/amd64.
|
|
if (
|
|
f"{os['distro_name']}-{os['distro_version']}" == "ubuntu-jammy"
|
|
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
|
and build_type == "Debug"
|
|
and architecture["platform"] == "linux/amd64"
|
|
):
|
|
cmake_args = f"{cmake_args} -Dunity=ON"
|
|
|
|
# Generate a unique name for the configuration, e.g. macos-arm64-debug
|
|
# or debian-bookworm-gcc-12-amd64-release.
|
|
config_name = os["distro_name"]
|
|
if (n := os["distro_version"]) != "":
|
|
config_name += f"-{n}"
|
|
if (n := os["compiler_name"]) != "":
|
|
config_name += f"-{n}"
|
|
if (n := os["compiler_version"]) != "":
|
|
config_name += f"-{n}"
|
|
config_name += (
|
|
f"-{architecture['platform'][architecture['platform'].find('/')+1:]}"
|
|
)
|
|
config_name += f"-{build_type.lower()}"
|
|
if "-Dcoverage=ON" in cmake_args:
|
|
config_name += "-coverage"
|
|
if "-Dunity=ON" in cmake_args:
|
|
config_name += "-unity"
|
|
|
|
# Add the configuration to the list, with the most unique fields first,
|
|
# so that they are easier to identify in the GitHub Actions UI, as long
|
|
# names get truncated.
|
|
# Add Address and Thread (both coupled with UB) sanitizers for specific bookworm distros.
|
|
# GCC-Asan rippled-embedded tests are failing because of https://github.com/google/sanitizers/issues/856
|
|
if os[
|
|
"distro_version"
|
|
] == "bookworm" and f"{os['compiler_name']}-{os['compiler_version']}" in [
|
|
"gcc-13",
|
|
"clang-20",
|
|
]:
|
|
# Add ASAN + UBSAN configuration.
|
|
configurations.append(
|
|
{
|
|
"config_name": config_name + "-asan-ubsan",
|
|
"cmake_args": cmake_args,
|
|
"cmake_target": cmake_target,
|
|
"build_only": build_only,
|
|
"build_type": build_type,
|
|
"os": os,
|
|
"architecture": architecture,
|
|
"sanitizers": "undefinedbehavior",
|
|
}
|
|
)
|
|
# TSAN is deactivated due to seg faults with latest compilers.
|
|
activate_tsan = False
|
|
if activate_tsan:
|
|
configurations.append(
|
|
{
|
|
"config_name": config_name + "-tsan-ubsan",
|
|
"cmake_args": cmake_args,
|
|
"cmake_target": cmake_target,
|
|
"build_only": build_only,
|
|
"build_type": build_type,
|
|
"os": os,
|
|
"architecture": architecture,
|
|
"sanitizers": "thread,undefinedbehavior",
|
|
}
|
|
)
|
|
else:
|
|
configurations.append(
|
|
{
|
|
"config_name": config_name,
|
|
"cmake_args": cmake_args,
|
|
"cmake_target": cmake_target,
|
|
"build_only": build_only,
|
|
"build_type": build_type,
|
|
"os": os,
|
|
"architecture": architecture,
|
|
"sanitizers": "",
|
|
}
|
|
)
|
|
|
|
return configurations
|
|
|
|
|
|
def read_config(file: Path) -> Config:
|
|
config = json.loads(file.read_text())
|
|
if (
|
|
config["architecture"] is None
|
|
or config["os"] is None
|
|
or config["build_type"] is None
|
|
or config["cmake_args"] is None
|
|
):
|
|
raise Exception("Invalid configuration file.")
|
|
|
|
return Config(**config)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"-a",
|
|
"--all",
|
|
help="Set to generate all configurations (generally used when merging a PR) or leave unset to generate a subset of configurations (generally used when committing to a PR).",
|
|
action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"-c",
|
|
"--config",
|
|
help="Path to the JSON file containing the strategy matrix configurations.",
|
|
required=False,
|
|
type=Path,
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
matrix = []
|
|
if args.config is None or args.config == "":
|
|
matrix += generate_strategy_matrix(
|
|
args.all, read_config(THIS_DIR / "linux.json")
|
|
)
|
|
matrix += generate_strategy_matrix(
|
|
args.all, read_config(THIS_DIR / "macos.json")
|
|
)
|
|
matrix += generate_strategy_matrix(
|
|
args.all, read_config(THIS_DIR / "windows.json")
|
|
)
|
|
else:
|
|
matrix += generate_strategy_matrix(args.all, read_config(args.config))
|
|
|
|
# Generate the strategy matrix.
|
|
print(f"matrix={json.dumps({'include': matrix})}")
|