mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-05 17:56:49 +00:00
Compare commits
75 Commits
a1q123456/
...
dangell7/d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
159ea3d7dd | ||
|
|
ad3d172a1f | ||
|
|
ad7232cbc5 | ||
|
|
93836f22db | ||
|
|
c7ecfc6a97 | ||
|
|
6809690fad | ||
|
|
5b6e8b6f93 | ||
|
|
028f0cb5da | ||
|
|
15b3ed1ae7 | ||
|
|
cce4cfef10 | ||
|
|
afbccf971a | ||
|
|
2f65cb5610 | ||
|
|
d4ebd6a168 | ||
|
|
551f3c3b96 | ||
|
|
aa5e4ff89f | ||
|
|
977e5a7dba | ||
|
|
648ec747f2 | ||
|
|
c8b42a7f48 | ||
|
|
4ad94ae2ff | ||
|
|
411286c519 | ||
|
|
e8bdbaa1e8 | ||
|
|
6340c986c9 | ||
|
|
170eb5e588 | ||
|
|
590906dadf | ||
|
|
448ae8b9df | ||
|
|
45b1f4dbeb | ||
|
|
8012b5d34f | ||
|
|
6c2266c5c7 | ||
|
|
aa55392453 | ||
|
|
c4c95dbe76 | ||
|
|
a761b0d43c | ||
|
|
cdee9a675c | ||
|
|
779b49cd93 | ||
|
|
4f8142fd10 | ||
|
|
4a9f72c73e | ||
|
|
7afdd71a54 | ||
|
|
af89854a43 | ||
|
|
d6c4e6cb93 | ||
|
|
d67e06102a | ||
|
|
8c71ec803d | ||
|
|
8e2aa33f64 | ||
|
|
13b72a4120 | ||
|
|
fcae50a487 | ||
|
|
a4720d0449 | ||
|
|
50244a8637 | ||
|
|
5e1c35f7f7 | ||
|
|
27f7fdb3a6 | ||
|
|
6e6fb9cdf3 | ||
|
|
e092c52409 | ||
|
|
d050073842 | ||
|
|
8490206228 | ||
|
|
8995564ed6 | ||
|
|
182d844996 | ||
|
|
37b895b678 | ||
|
|
c6053f5d64 | ||
|
|
31180f94c2 | ||
|
|
6407f0fa52 | ||
|
|
4d0ea8ae36 | ||
|
|
dbd646bd53 | ||
|
|
6ae090ba45 | ||
|
|
7be98d95de | ||
|
|
f7275b7ad9 | ||
|
|
46b997b774 | ||
|
|
147da57348 | ||
|
|
3547112540 | ||
|
|
4dc923dcc5 | ||
|
|
158df5394c | ||
|
|
a6bd9251d2 | ||
|
|
9ae29612ea | ||
|
|
82abf2a849 | ||
|
|
7cfa5d4610 | ||
|
|
248cb29681 | ||
|
|
7a449edebb | ||
|
|
19da25812b | ||
|
|
7cd503859e |
96
.clang-tidy
96
.clang-tidy
@@ -1,11 +1,10 @@
|
||||
---
|
||||
# This entire group of checks was applied to all cpp files but not all header files.
|
||||
# ---
|
||||
Checks: "-*,
|
||||
bugprone-argument-comment,
|
||||
bugprone-assert-side-effect,
|
||||
bugprone-bad-signal-to-kill-thread,
|
||||
bugprone-bool-pointer-implicit-conversion,
|
||||
bugprone-capturing-this-in-member-variable,
|
||||
bugprone-casting-through-void,
|
||||
bugprone-chained-comparison,
|
||||
bugprone-compare-pointer-to-member-virtual-function,
|
||||
@@ -25,6 +24,7 @@ Checks: "-*,
|
||||
bugprone-lambda-function-name,
|
||||
bugprone-macro-parentheses,
|
||||
bugprone-macro-repeated-side-effects,
|
||||
bugprone-misleading-setter-of-reference,
|
||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
bugprone-misplaced-pointer-arithmetic-in-alloc,
|
||||
bugprone-misplaced-widening-cast,
|
||||
@@ -66,17 +66,17 @@ Checks: "-*,
|
||||
bugprone-terminating-continue,
|
||||
bugprone-throw-keyword-missing,
|
||||
bugprone-too-small-loop-variable,
|
||||
# bugprone-unchecked-optional-access, # see https://github.com/XRPLF/rippled/pull/6502
|
||||
bugprone-unchecked-optional-access,
|
||||
bugprone-undefined-memory-manipulation,
|
||||
bugprone-undelegated-constructor,
|
||||
bugprone-unhandled-exception-at-new,
|
||||
bugprone-unhandled-self-assignment,
|
||||
bugprone-unique-ptr-array-mismatch,
|
||||
bugprone-unsafe-functions,
|
||||
bugprone-use-after-move, # has issues
|
||||
bugprone-unused-local-non-trivial-variable,
|
||||
bugprone-unused-raii,
|
||||
bugprone-unused-return-value,
|
||||
bugprone-unused-local-non-trivial-variable,
|
||||
bugprone-use-after-move,
|
||||
bugprone-virtual-near-miss,
|
||||
cppcoreguidelines-init-variables,
|
||||
cppcoreguidelines-misleading-capture-default-by-value,
|
||||
@@ -85,8 +85,10 @@ Checks: "-*,
|
||||
cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
cppcoreguidelines-rvalue-reference-param-not-moved,
|
||||
cppcoreguidelines-use-default-member-init,
|
||||
cppcoreguidelines-use-enum-class,
|
||||
cppcoreguidelines-virtual-class-destructor,
|
||||
hicpp-ignored-remove-result,
|
||||
llvm-namespace-comment,
|
||||
misc-const-correctness,
|
||||
misc-definitions-in-headers,
|
||||
misc-header-include-cycle,
|
||||
@@ -98,6 +100,7 @@ Checks: "-*,
|
||||
misc-unused-alias-decls,
|
||||
misc-unused-using-decls,
|
||||
modernize-concat-nested-namespaces,
|
||||
modernize-deprecated-headers,
|
||||
modernize-make-shared,
|
||||
modernize-make-unique,
|
||||
modernize-pass-by-value,
|
||||
@@ -106,13 +109,13 @@ Checks: "-*,
|
||||
modernize-use-emplace,
|
||||
modernize-use-equals-default,
|
||||
modernize-use-equals-delete,
|
||||
modernize-use-nodiscard,
|
||||
modernize-use-override,
|
||||
modernize-use-ranges,
|
||||
modernize-use-scoped-lock,
|
||||
modernize-use-starts-ends-with,
|
||||
modernize-use-std-numbers,
|
||||
modernize-use-using,
|
||||
modernize-deprecated-headers,
|
||||
llvm-namespace-comment,
|
||||
performance-faster-string-find,
|
||||
performance-for-range-copy,
|
||||
performance-implicit-conversion-in-loop,
|
||||
@@ -121,6 +124,7 @@ Checks: "-*,
|
||||
performance-move-constructor-init,
|
||||
performance-no-automatic-move,
|
||||
performance-trivially-destructible,
|
||||
readability-ambiguous-smartptr-reset-call,
|
||||
readability-avoid-nested-conditional-operator,
|
||||
readability-avoid-return-with-void-value,
|
||||
readability-braces-around-statements,
|
||||
@@ -131,6 +135,7 @@ Checks: "-*,
|
||||
readability-duplicate-include,
|
||||
readability-else-after-return,
|
||||
readability-enum-initial-value,
|
||||
readability-identifier-naming,
|
||||
readability-implicit-bool-conversion,
|
||||
readability-make-member-function-const,
|
||||
readability-math-missing-parentheses,
|
||||
@@ -148,52 +153,49 @@ Checks: "-*,
|
||||
readability-use-std-min-max
|
||||
"
|
||||
# ---
|
||||
# other checks that have issues that need to be resolved:
|
||||
#
|
||||
# readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names
|
||||
# readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable
|
||||
# readability-identifier-naming, # https://github.com/XRPLF/rippled/pull/6571
|
||||
# ---
|
||||
#
|
||||
|
||||
CheckOptions:
|
||||
readability-braces-around-statements.ShortStatementLines: 2
|
||||
# readability-identifier-naming.MacroDefinitionCase: UPPER_CASE
|
||||
# readability-identifier-naming.ClassCase: CamelCase
|
||||
# readability-identifier-naming.StructCase: CamelCase
|
||||
# readability-identifier-naming.UnionCase: CamelCase
|
||||
# readability-identifier-naming.EnumCase: CamelCase
|
||||
# readability-identifier-naming.EnumConstantCase: CamelCase
|
||||
# readability-identifier-naming.ScopedEnumConstantCase: CamelCase
|
||||
# readability-identifier-naming.GlobalConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.GlobalConstantPrefix: "k"
|
||||
# readability-identifier-naming.GlobalVariableCase: CamelCase
|
||||
# readability-identifier-naming.GlobalVariablePrefix: "g"
|
||||
# readability-identifier-naming.ConstexprFunctionCase: camelBack
|
||||
# readability-identifier-naming.ConstexprMethodCase: camelBack
|
||||
# readability-identifier-naming.ClassMethodCase: camelBack
|
||||
# readability-identifier-naming.ClassMemberCase: camelBack
|
||||
# readability-identifier-naming.ClassConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.ClassConstantPrefix: "k"
|
||||
# readability-identifier-naming.StaticConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.StaticConstantPrefix: "k"
|
||||
# readability-identifier-naming.StaticVariableCase: UPPER_CASE
|
||||
# readability-identifier-naming.StaticVariablePrefix: "k"
|
||||
# readability-identifier-naming.ConstexprVariableCase: UPPER_CASE
|
||||
# readability-identifier-naming.ConstexprVariablePrefix: "k"
|
||||
# readability-identifier-naming.LocalConstantCase: camelBack
|
||||
# readability-identifier-naming.LocalVariableCase: camelBack
|
||||
# readability-identifier-naming.TemplateParameterCase: CamelCase
|
||||
# readability-identifier-naming.ParameterCase: camelBack
|
||||
# readability-identifier-naming.FunctionCase: camelBack
|
||||
# readability-identifier-naming.MemberCase: camelBack
|
||||
# readability-identifier-naming.PrivateMemberSuffix: _
|
||||
# readability-identifier-naming.ProtectedMemberSuffix: _
|
||||
# readability-identifier-naming.PublicMemberSuffix: ""
|
||||
# readability-identifier-naming.FunctionIgnoredRegexp: ".*tag_invoke.*"
|
||||
bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
|
||||
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc
|
||||
|
||||
misc-include-cleaner.IgnoreHeaders: ".*/(detail|impl)/.*;.*fwd\\.h(pp)?;time.h;stdlib.h;sqlite3.h;netinet/in\\.h;sys/resource\\.h;sys/sysinfo\\.h;linux/sysinfo\\.h;__chrono/.*;bits/.*;_abort\\.h;boost/uuid/uuid_hash.hpp;boost/beast/core/flat_buffer\\.hpp;boost/beast/http/field\\.hpp;boost/beast/http/dynamic_body\\.hpp;boost/beast/http/message\\.hpp;boost/beast/http/read\\.hpp;boost/beast/http/write\\.hpp;openssl/obj_mac\\.h"
|
||||
#
|
||||
HeaderFilterRegex: '^.*/(test|xrpl|xrpld)/.*\.(h|hpp)$'
|
||||
|
||||
readability-braces-around-statements.ShortStatementLines: 2
|
||||
readability-identifier-naming.MacroDefinitionCase: UPPER_CASE
|
||||
readability-identifier-naming.ClassCase: CamelCase
|
||||
readability-identifier-naming.StructCase: CamelCase
|
||||
readability-identifier-naming.UnionCase: CamelCase
|
||||
readability-identifier-naming.EnumCase: CamelCase
|
||||
readability-identifier-naming.EnumConstantCase: CamelCase
|
||||
readability-identifier-naming.ScopedEnumConstantCase: CamelCase
|
||||
readability-identifier-naming.GlobalConstantCase: CamelCase
|
||||
readability-identifier-naming.GlobalConstantPrefix: "k"
|
||||
readability-identifier-naming.GlobalVariableCase: CamelCase
|
||||
readability-identifier-naming.GlobalVariablePrefix: "g"
|
||||
readability-identifier-naming.ConstexprFunctionCase: camelBack
|
||||
readability-identifier-naming.ConstexprMethodCase: camelBack
|
||||
readability-identifier-naming.ClassMethodCase: camelBack
|
||||
readability-identifier-naming.ClassMemberCase: camelBack
|
||||
readability-identifier-naming.ClassConstantCase: CamelCase
|
||||
readability-identifier-naming.ClassConstantPrefix: "k"
|
||||
readability-identifier-naming.StaticConstantCase: CamelCase
|
||||
readability-identifier-naming.StaticConstantPrefix: "k"
|
||||
readability-identifier-naming.StaticVariableCase: camelBack
|
||||
readability-identifier-naming.ConstexprVariableCase: camelBack
|
||||
readability-identifier-naming.LocalConstantCase: camelBack
|
||||
readability-identifier-naming.LocalVariableCase: camelBack
|
||||
readability-identifier-naming.TemplateParameterCase: CamelCase
|
||||
readability-identifier-naming.ParameterCase: camelBack
|
||||
readability-identifier-naming.FunctionCase: camelBack
|
||||
readability-identifier-naming.MemberCase: camelBack
|
||||
readability-identifier-naming.PrivateMemberSuffix: _
|
||||
readability-identifier-naming.ProtectedMemberSuffix: _
|
||||
readability-identifier-naming.PublicMemberSuffix: ""
|
||||
readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$"
|
||||
|
||||
HeaderFilterRegex: '^.*/(test|xrpl|xrpld)/.*\.(h|hpp|ipp)$'
|
||||
ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$'
|
||||
WarningsAsErrors: "*"
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
# This file is sorted in reverse chronological order, with the most recent commits at the top.
|
||||
# The commits listed here are ignored by git blame, which is useful for formatting-only commits that would otherwise obscure the history of changes to a file.
|
||||
|
||||
# refactor: Enable clang-tidy `readability-identifier-naming` check (#6571)
|
||||
8995564ed6b9e453e144bb663303072a3c1ba305
|
||||
# refactor: Enable remaining clang-tidy `cppcoreguidelines` checks (#6538)
|
||||
72f4cb097f626b08b02fc3efcb4aa11cb2e7adb8
|
||||
# refactor: Rename system name from 'ripple' to 'xrpld' (#6347)
|
||||
|
||||
43
.github/actions/print-env/action.yml
vendored
43
.github/actions/print-env/action.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: Print build environment
|
||||
description: "Print environment and some tooling versions"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Check configuration (Windows)
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Checking environment variables.'
|
||||
set
|
||||
|
||||
- name: Check configuration (Linux and macOS)
|
||||
if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Checking path.'
|
||||
echo ${PATH} | tr ':' '\n'
|
||||
|
||||
echo 'Checking environment variables.'
|
||||
env | sort
|
||||
|
||||
echo 'Checking compiler version.'
|
||||
${{ runner.os == 'Linux' && '${CC}' || 'clang' }} --version
|
||||
|
||||
echo 'Checking Ninja version.'
|
||||
ninja --version
|
||||
|
||||
echo 'Checking nproc version.'
|
||||
nproc --version
|
||||
|
||||
- name: Check configuration (all)
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Checking Ccache version.'
|
||||
ccache --version
|
||||
|
||||
echo 'Checking CMake version.'
|
||||
cmake --version
|
||||
|
||||
echo 'Checking Conan version.'
|
||||
conan --version
|
||||
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -33,17 +33,6 @@ updates:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/print-env/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/setup-conan/
|
||||
schedule:
|
||||
|
||||
@@ -93,6 +93,7 @@ test.core > xrpl.basics
|
||||
test.core > xrpl.core
|
||||
test.core > xrpld.core
|
||||
test.core > xrpl.json
|
||||
test.core > xrpl.protocol
|
||||
test.core > xrpl.rdb
|
||||
test.core > xrpl.server
|
||||
test.csf > xrpl.basics
|
||||
@@ -187,10 +188,16 @@ test.toplevel > xrpl.json
|
||||
test.unit_test > xrpl.basics
|
||||
test.unit_test > xrpl.protocol
|
||||
tests.libxrpl > xrpl.basics
|
||||
tests.libxrpl > xrpl.core
|
||||
tests.libxrpl > xrpl.json
|
||||
tests.libxrpl > xrpl.ledger
|
||||
tests.libxrpl > xrpl.net
|
||||
tests.libxrpl > xrpl.nodestore
|
||||
tests.libxrpl > xrpl.protocol
|
||||
tests.libxrpl > xrpl.protocol_autogen
|
||||
tests.libxrpl > xrpl.server
|
||||
tests.libxrpl > xrpl.shamap
|
||||
tests.libxrpl > xrpl.tx
|
||||
xrpl.conditions > xrpl.basics
|
||||
xrpl.conditions > xrpl.protocol
|
||||
xrpl.core > xrpl.basics
|
||||
|
||||
2
.github/scripts/rename/config.sh
vendored
2
.github/scripts/rename/config.sh
vendored
@@ -62,7 +62,7 @@ ${SED_COMMAND} -i 's@ripple/@xrpld/@g' src/test/core/Config_test.cpp
|
||||
${SED_COMMAND} -i 's/Rippled/File/g' src/test/core/Config_test.cpp
|
||||
|
||||
# Restore the old config file name in the code that maintains support for now.
|
||||
${SED_COMMAND} -i 's/configLegacyName = "xrpld.cfg"/configLegacyName = "rippled.cfg"/g' src/xrpld/core/detail/Config.cpp
|
||||
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/g' src/xrpld/core/detail/Config.cpp
|
||||
|
||||
# Restore an URL.
|
||||
${SED_COMMAND} -i 's/connect-your-xrpld-to-the-xrp-test-net.html/connect-your-rippled-to-the-xrp-test-net.html/g' cfg/xrpld-example.cfg
|
||||
|
||||
2
.github/scripts/rename/docs.sh
vendored
2
.github/scripts/rename/docs.sh
vendored
@@ -90,7 +90,7 @@ ${SED_COMMAND} -i 's/www.ripple.com/www.xrpl.org/g' src/test/protocol/Seed_test.
|
||||
# Restore specific changes.
|
||||
${SED_COMMAND} -i 's@b5efcc/src/xrpld@b5efcc/src/ripple@' include/xrpl/protocol/README.md
|
||||
${SED_COMMAND} -i 's/dbPrefix_ = "xrpldb"/dbPrefix_ = "rippledb"/' src/xrpld/app/misc/SHAMapStoreImp.h # cspell: disable-line
|
||||
${SED_COMMAND} -i 's/configLegacyName = "xrpld.cfg"/configLegacyName = "rippled.cfg"/' src/xrpld/core/detail/Config.cpp
|
||||
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/' src/xrpld/core/detail/Config.cpp
|
||||
|
||||
popd
|
||||
echo "Renaming complete."
|
||||
|
||||
119
.github/scripts/strategy-matrix/generate.py
vendored
119
.github/scripts/strategy-matrix/generate.py
vendored
@@ -32,7 +32,32 @@ We will further set additional CMake arguments as follows:
|
||||
"""
|
||||
|
||||
|
||||
def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
def build_config_name(os_entry: dict[str, str], platform: str, build_type: str) -> str:
|
||||
parts = [os_entry["distro_name"]]
|
||||
for key in ("distro_version", "compiler_name", "compiler_version"):
|
||||
if value := os_entry[key]:
|
||||
parts.append(value)
|
||||
parts.append("arm64" if "arm64" in platform else "amd64")
|
||||
parts.append(build_type.lower())
|
||||
return "-".join(parts)
|
||||
|
||||
|
||||
def generate_packaging_matrix(config: Config) -> list[dict]:
|
||||
"""Emit one entry per os entry with `package: true`. Architecture is
|
||||
hardcoded to linux/amd64 here (and the runner is hardcoded at the
|
||||
workflow level) until arm64 packaging is ready.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"artifact_name": f"xrpld-{build_config_name(os, 'linux/amd64', 'Release')}",
|
||||
"os": os,
|
||||
}
|
||||
for os in config.os
|
||||
if os.get("package", False)
|
||||
]
|
||||
|
||||
|
||||
def generate_strategy_matrix(all: bool, config: Config) -> list[dict]:
|
||||
configurations = []
|
||||
for architecture, os, build_type, cmake_args in itertools.product(
|
||||
config.architecture, config.os, config.build_type, config.cmake_args
|
||||
@@ -51,27 +76,28 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
# 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 GCC 13: Debug on linux/amd64, set the reference
|
||||
# fee to 500 and enable code coverage (which will be done below).
|
||||
# - Bookworm using GCC 15: Debug on linux/amd64, enable Address and
|
||||
# UB sanitizers (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.
|
||||
# - Bookworm using Clang 20: Debug on linux/amd64, enable Address
|
||||
# and UB sanitizers (which will be done below).
|
||||
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 build_type == "Debug"
|
||||
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 build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
@@ -89,8 +115,9 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
):
|
||||
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=1000 {cmake_args}"
|
||||
skip = False
|
||||
elif os["distro_version"] == "trixie":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20"
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
@@ -99,14 +126,15 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
continue
|
||||
|
||||
# RHEL:
|
||||
# - 9 using GCC 12: Debug on linux/amd64.
|
||||
# - 9 using GCC 12: Debug and Release on linux/amd64
|
||||
# (Release is required for RPM packaging).
|
||||
# - 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 build_type in ["Debug", "Release"]
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
@@ -121,7 +149,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
continue
|
||||
|
||||
# Ubuntu:
|
||||
# - Jammy using GCC 12: Debug on linux/arm64.
|
||||
# - Jammy using GCC 12: Debug on linux/arm64, Release on
|
||||
# linux/amd64 (Release is required for DEB packaging).
|
||||
# - Noble using GCC 14: Release on linux/amd64.
|
||||
# - Noble using Clang 18: Debug on linux/amd64.
|
||||
# - Noble using Clang 19: Release on linux/arm64.
|
||||
@@ -134,6 +163,12 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
):
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
elif os["distro_version"] == "noble":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14"
|
||||
@@ -187,17 +222,18 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
|
||||
# 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"]
|
||||
os["compiler_name"] == "clang"
|
||||
and os["compiler_version"].isdigit()
|
||||
and int(os["compiler_version"]) >= 20
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
):
|
||||
continue
|
||||
|
||||
# Enable code coverage for Debian Bookworm using GCC 15 in Debug on
|
||||
# linux/amd64
|
||||
# Enable code coverage for Debian Bookworm using GCC 13 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 f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
@@ -215,17 +251,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
|
||||
# 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()}"
|
||||
config_name = build_config_name(os, architecture["platform"], build_type)
|
||||
if "-Dcoverage=ON" in cmake_args:
|
||||
config_name += "-coverage"
|
||||
if "-Dunity=ON" in cmake_args:
|
||||
@@ -234,23 +260,39 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
# 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.
|
||||
# Add Address and UB sanitizers as separate configurations for specific
|
||||
# bookworm distros. Thread sanitizer is currently disabled (see below).
|
||||
# GCC-Asan xrpld-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']}" == "clang-20"
|
||||
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
|
||||
) or (
|
||||
os["distro_version"] == "trixie"
|
||||
and f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22"
|
||||
):
|
||||
# Add ASAN + UBSAN configuration.
|
||||
# Add ASAN and UBSAN configurations for both gcc-15 and clang-22
|
||||
configurations.append(
|
||||
{
|
||||
"config_name": config_name + "-asan-ubsan",
|
||||
"config_name": config_name + "-asan",
|
||||
"cmake_args": cmake_args,
|
||||
"cmake_target": cmake_target,
|
||||
"build_only": build_only,
|
||||
"build_type": build_type,
|
||||
"os": os,
|
||||
"architecture": architecture,
|
||||
"sanitizers": "address,undefinedbehavior",
|
||||
"sanitizers": "address",
|
||||
}
|
||||
)
|
||||
configurations.append(
|
||||
{
|
||||
"config_name": config_name + "-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.
|
||||
@@ -313,10 +355,19 @@ if __name__ == "__main__":
|
||||
required=False,
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--packaging",
|
||||
help="Emit the packaging matrix (derived from the 'package' field on os entries) instead of the build/test matrix.",
|
||||
action="store_true",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
matrix = []
|
||||
if args.config is None or args.config == "":
|
||||
if args.packaging:
|
||||
config_path = args.config if args.config else THIS_DIR / "linux.json"
|
||||
matrix += generate_packaging_matrix(read_config(config_path))
|
||||
elif args.config is None or args.config == "":
|
||||
matrix += generate_strategy_matrix(
|
||||
args.all, read_config(THIS_DIR / "linux.json")
|
||||
)
|
||||
|
||||
65
.github/scripts/strategy-matrix/linux.json
vendored
65
.github/scripts/strategy-matrix/linux.json
vendored
@@ -15,196 +15,205 @@
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "13",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "15",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "16",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "17",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "18",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "19",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "20",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "15",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "20",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "21",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "22",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "8",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "8",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "any",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9",
|
||||
"package": true
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "13",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "any",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "10",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "10",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "any",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "jammy",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9",
|
||||
"package": true
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "13",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "16",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "17",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "18",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "19",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "4c086b9"
|
||||
}
|
||||
],
|
||||
"build_type": ["Debug", "Release"],
|
||||
|
||||
101
.github/workflows/build-nix-image.yml
vendored
Normal file
101
.github/workflows/build-nix-image.yml
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
name: Build Nix Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-image.yml"
|
||||
- "docker/nix.Dockerfile"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-nix-image.yml"
|
||||
- "docker/nix.Dockerfile"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
UBUNTU_VERSION: "20.04"
|
||||
RHEL_VERSION: "9"
|
||||
DEBIAN_VERSION: "bookworm"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and push Nix image (${{ matrix.distro }})
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- distro: nixos
|
||||
- distro: ubuntu
|
||||
- distro: rhel
|
||||
- distro: debian
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Determine base image
|
||||
id: vars
|
||||
run: |
|
||||
case "${{ matrix.distro }}" in
|
||||
nixos)
|
||||
echo "base_image=nixos/nix:latest" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
ubuntu)
|
||||
echo "base_image=ubuntu:${UBUNTU_VERSION}" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
rhel)
|
||||
echo "base_image=registry.access.redhat.com/ubi${RHEL_VERSION}/ubi:latest" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
debian)
|
||||
echo "base_image=debian:${DEBIAN_VERSION}" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name == 'push'
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
with:
|
||||
images: ghcr.io/xrplf/ci/nix-${{ matrix.distro }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-,format=short
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
file: docker/nix.Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: BASE_IMAGE=${{ steps.vars.outputs.base_image }}
|
||||
2
.github/workflows/check-pr-title.yml
vendored
2
.github/workflows/check-pr-title.yml
vendored
@@ -11,4 +11,4 @@ on:
|
||||
jobs:
|
||||
check_title:
|
||||
if: ${{ github.event.pull_request.draft != true }}
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@a5d8dd35be543365e90a11358447130c8763871d
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@291206777251b4d493641b5afbdf7c23009d2988
|
||||
|
||||
17
.github/workflows/on-pr.yml
vendored
17
.github/workflows/on-pr.yml
vendored
@@ -58,20 +58,19 @@ jobs:
|
||||
|
||||
# Keep the paths below in sync with those in `on-trigger.yml`.
|
||||
.github/actions/build-deps/**
|
||||
.github/actions/build-test/**
|
||||
.github/actions/generate-version/**
|
||||
.github/actions/setup-conan/**
|
||||
.github/scripts/strategy-matrix/**
|
||||
.github/workflows/reusable-build.yml
|
||||
.github/workflows/reusable-build-test-config.yml
|
||||
.github/workflows/reusable-build-test.yml
|
||||
.github/workflows/reusable-clang-tidy.yml
|
||||
.github/workflows/reusable-clang-tidy-files.yml
|
||||
.github/workflows/reusable-package.yml
|
||||
.github/workflows/reusable-strategy-matrix.yml
|
||||
.github/workflows/reusable-test.yml
|
||||
.github/workflows/reusable-upload-recipe.yml
|
||||
.clang-tidy
|
||||
.codecov.yml
|
||||
cfg/**
|
||||
cmake/**
|
||||
conan/**
|
||||
external/**
|
||||
@@ -81,6 +80,10 @@ jobs:
|
||||
CMakeLists.txt
|
||||
conanfile.py
|
||||
conan.lock
|
||||
LICENSE.md
|
||||
package/**
|
||||
README.md
|
||||
|
||||
- name: Check whether to run
|
||||
# This step determines whether the rest of the workflow should
|
||||
# run. The rest of the workflow will run if this job runs AND at
|
||||
@@ -137,6 +140,11 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
needs: [should-run, build-test]
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
|
||||
upload-recipe:
|
||||
needs:
|
||||
- should-run
|
||||
@@ -171,9 +179,10 @@ jobs:
|
||||
- check-rename
|
||||
- clang-tidy
|
||||
- build-test
|
||||
- package
|
||||
- upload-recipe
|
||||
- notify-clio
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fail
|
||||
run: false
|
||||
run: exit 1
|
||||
|
||||
23
.github/workflows/on-tag.yml
vendored
23
.github/workflows/on-tag.yml
vendored
@@ -1,5 +1,5 @@
|
||||
# This workflow uploads the libxrpl recipe to the Conan remote when a versioned
|
||||
# tag is pushed.
|
||||
# This workflow uploads the libxrpl recipe to the Conan remote and builds
|
||||
# release packages when a versioned tag is pushed.
|
||||
name: Tag
|
||||
|
||||
on:
|
||||
@@ -22,3 +22,22 @@ jobs:
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
|
||||
build-test:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [linux]
|
||||
with:
|
||||
ccache_enabled: false
|
||||
os: ${{ matrix.os }}
|
||||
strategy_matrix: minimal
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
needs: build-test
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
|
||||
12
.github/workflows/on-trigger.yml
vendored
12
.github/workflows/on-trigger.yml
vendored
@@ -15,20 +15,19 @@ on:
|
||||
|
||||
# Keep the paths below in sync with those in `on-pr.yml`.
|
||||
- ".github/actions/build-deps/**"
|
||||
- ".github/actions/build-test/**"
|
||||
- ".github/actions/generate-version/**"
|
||||
- ".github/actions/setup-conan/**"
|
||||
- ".github/scripts/strategy-matrix/**"
|
||||
- ".github/workflows/reusable-build.yml"
|
||||
- ".github/workflows/reusable-build-test-config.yml"
|
||||
- ".github/workflows/reusable-build-test.yml"
|
||||
- ".github/workflows/reusable-clang-tidy.yml"
|
||||
- ".github/workflows/reusable-clang-tidy-files.yml"
|
||||
- ".github/workflows/reusable-package.yml"
|
||||
- ".github/workflows/reusable-strategy-matrix.yml"
|
||||
- ".github/workflows/reusable-test.yml"
|
||||
- ".github/workflows/reusable-upload-recipe.yml"
|
||||
- ".clang-tidy"
|
||||
- ".codecov.yml"
|
||||
- "cfg/**"
|
||||
- "cmake/**"
|
||||
- "conan/**"
|
||||
- "external/**"
|
||||
@@ -38,6 +37,9 @@ on:
|
||||
- "CMakeLists.txt"
|
||||
- "conanfile.py"
|
||||
- "conan.lock"
|
||||
- "LICENSE.md"
|
||||
- "package/**"
|
||||
- "README.md"
|
||||
|
||||
# Run at 06:32 UTC on every day of the week from Monday through Friday. This
|
||||
# will force all dependencies to be rebuilt, which is useful to verify that
|
||||
@@ -98,3 +100,7 @@ jobs:
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
|
||||
package:
|
||||
needs: build-test
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@9307df762265e15c745ddcdb38a581c989f7f349
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@5e942d61bf32f7557a7c159cfac4712a687b3e3a
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
|
||||
|
||||
15
.github/workflows/reusable-build-test-config.yml
vendored
15
.github/workflows/reusable-build-test-config.yml
vendored
@@ -116,7 +116,7 @@ jobs:
|
||||
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Print build environment
|
||||
uses: ./.github/actions/print-env
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
@@ -143,7 +143,6 @@ jobs:
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
SANITIZERS: ${{ inputs.sanitizers }}
|
||||
CMAKE_ARGS: ${{ inputs.cmake_args }}
|
||||
run: |
|
||||
cmake \
|
||||
@@ -182,7 +181,7 @@ jobs:
|
||||
- name: Build the binary
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
|
||||
BUILD_NPROC: ${{ runner.os == 'Linux' && '16' || steps.nproc.outputs.nproc }}
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
CMAKE_TARGET: ${{ inputs.cmake_target }}
|
||||
run: |
|
||||
@@ -284,8 +283,16 @@ jobs:
|
||||
|
||||
- name: Show test failure summary
|
||||
if: ${{ failure() && !inputs.build_only }}
|
||||
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
env:
|
||||
WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
run: |
|
||||
if [ ! -d "${WORKING_DIR}" ]; then
|
||||
echo "Working directory '${WORKING_DIR}' does not exist."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "${WORKING_DIR}"
|
||||
|
||||
if [ ! -f unittest.log ]; then
|
||||
echo "unittest.log not found; embedded tests may not have run."
|
||||
exit 0
|
||||
|
||||
175
.github/workflows/reusable-clang-tidy-files.yml
vendored
175
.github/workflows/reusable-clang-tidy-files.yml
vendored
@@ -1,175 +0,0 @@
|
||||
name: Run clang-tidy on files
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
files:
|
||||
description: "List of files to check (empty means check all files)"
|
||||
type: string
|
||||
default: ""
|
||||
create_issue_on_failure:
|
||||
description: "Whether to create an issue if the check failed"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
# Conan installs the generators in the build/generators directory, see the
|
||||
# layout() method in conanfile.py. We then run CMake from the build directory.
|
||||
BUILD_DIR: build
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
run-clang-tidy:
|
||||
name: Run clang tidy
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/ci/debian-trixie:clang-21-sha-53033a2"
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
- name: Print build environment
|
||||
uses: ./.github/actions/print-env
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
|
||||
- name: Setup Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
|
||||
- name: Build dependencies
|
||||
uses: ./.github/actions/build-deps
|
||||
with:
|
||||
build_nproc: ${{ steps.nproc.outputs.nproc }}
|
||||
build_type: ${{ env.BUILD_TYPE }}
|
||||
log_verbosity: verbose
|
||||
|
||||
- name: Configure CMake
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
cmake \
|
||||
-G 'Ninja' \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-Dtests=ON \
|
||||
-Dwerr=ON \
|
||||
-Dxrpld=ON \
|
||||
..
|
||||
|
||||
# clang-tidy needs headers generated from proto files
|
||||
- name: Build libxrpl.libpb
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb
|
||||
|
||||
- name: Run clang tidy
|
||||
id: run_clang_tidy
|
||||
continue-on-error: true
|
||||
env:
|
||||
TARGETS: ${{ inputs.files != '' && inputs.files || 'src tests' }}
|
||||
run: |
|
||||
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee clang-tidy-output.txt
|
||||
|
||||
- name: Upload clang-tidy output
|
||||
if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: clang-tidy-results
|
||||
path: clang-tidy-output.txt
|
||||
retention-days: 30
|
||||
|
||||
- name: Generate git diff
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
git diff | tee clang-tidy-git-diff.txt
|
||||
|
||||
- name: Upload clang-tidy diff output
|
||||
if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: clang-tidy-git-diff
|
||||
path: clang-tidy-git-diff.txt
|
||||
retention-days: 30
|
||||
|
||||
- name: Create an issue
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure }}
|
||||
id: create_issue
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Prepare issue body with clang-tidy output
|
||||
cat > issue.md <<EOF
|
||||
## Clang-tidy Check Failed
|
||||
|
||||
**Workflow:** ${{ github.workflow }}
|
||||
**Run ID:** ${{ github.run_id }}
|
||||
**Commit:** ${{ github.sha }}
|
||||
**Branch/Ref:** ${{ github.ref }}
|
||||
**Triggered by:** ${{ github.actor }}
|
||||
|
||||
### Clang-tidy Output:
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
# Append clang-tidy output (filter for errors and warnings)
|
||||
if [ -f clang-tidy-output.txt ]; then
|
||||
# Extract lines containing 'error:', 'warning:', or 'note:'
|
||||
grep -E '(error:|warning:|note:)' clang-tidy-output.txt > filtered-output.txt || true
|
||||
|
||||
# If filtered output is empty, use original (might be a different error format)
|
||||
if [ ! -s filtered-output.txt ]; then
|
||||
cp clang-tidy-output.txt filtered-output.txt
|
||||
fi
|
||||
|
||||
# Truncate if too large
|
||||
head -c 60000 filtered-output.txt >> issue.md
|
||||
if [ "$(wc -c < filtered-output.txt)" -gt 60000 ]; then
|
||||
echo "" >> issue.md
|
||||
echo "... (output truncated, see artifacts for full output)" >> issue.md
|
||||
fi
|
||||
|
||||
rm filtered-output.txt
|
||||
else
|
||||
echo "No output file found" >> issue.md
|
||||
fi
|
||||
|
||||
cat >> issue.md <<EOF
|
||||
\`\`\`
|
||||
|
||||
**Workflow run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
---
|
||||
*This issue was automatically created by the clang-tidy workflow.*
|
||||
EOF
|
||||
|
||||
# Create the issue
|
||||
gh issue create \
|
||||
--label "Bug,Clang-tidy" \
|
||||
--title "Clang-tidy check failed" \
|
||||
--body-file ./issue.md \
|
||||
> create_issue.log
|
||||
|
||||
created_issue="$(sed 's|.*/||' create_issue.log)"
|
||||
echo "created_issue=$created_issue" >> $GITHUB_OUTPUT
|
||||
echo "Created issue #$created_issue"
|
||||
|
||||
rm -f create_issue.log issue.md clang-tidy-output.txt
|
||||
|
||||
- name: Fail the workflow if clang-tidy failed
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
echo "Clang-tidy check failed!"
|
||||
exit 1
|
||||
189
.github/workflows/reusable-clang-tidy.yml
vendored
189
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Clang-tidy check
|
||||
name: Run clang-tidy on files
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
@@ -16,40 +16,175 @@ defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
BUILD_TYPE: Debug # Debug so that ASSERTS and such participate in clang-tidy check
|
||||
|
||||
OUTPUT_FILE: clang-tidy-output.txt
|
||||
DIFF_FILE: clang-tidy-git-diff.txt
|
||||
ISSUE_FILE: clang-tidy-issue.md
|
||||
|
||||
jobs:
|
||||
determine-files:
|
||||
name: Determine files to check
|
||||
if: ${{ inputs.check_only_changed }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
clang_tidy_config_changed: ${{ steps.changed_clang_tidy.outputs.any_changed }}
|
||||
any_cpp_changed: ${{ steps.changed_files.outputs.any_changed }}
|
||||
all_changed_files: ${{ steps.changed_files.outputs.all_changed_files }}
|
||||
permissions:
|
||||
contents: read
|
||||
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@224f3c48d3014d082a1129237b8291ff0b0a331f
|
||||
|
||||
run-clang-tidy:
|
||||
name: Run clang tidy
|
||||
needs: [determine-files]
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/ci/debian-trixie:clang-21-sha-53033a2"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed C++ files
|
||||
id: changed_files
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
with:
|
||||
files: |
|
||||
**/*.cpp
|
||||
**/*.h
|
||||
**/*.ipp
|
||||
separator: " "
|
||||
enable_ccache: false
|
||||
|
||||
- name: Get changed clang-tidy configuration
|
||||
id: changed_clang_tidy
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
- name: Print build environment
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
|
||||
- name: Setup Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
|
||||
- name: Build dependencies
|
||||
uses: ./.github/actions/build-deps
|
||||
with:
|
||||
files: |
|
||||
.clang-tidy
|
||||
build_nproc: ${{ steps.nproc.outputs.nproc }}
|
||||
build_type: ${{ env.BUILD_TYPE }}
|
||||
log_verbosity: verbose
|
||||
|
||||
run-clang-tidy:
|
||||
needs: [determine-files]
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.any_cpp_changed == 'true' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
|
||||
uses: ./.github/workflows/reusable-clang-tidy-files.yml
|
||||
with:
|
||||
files: ${{ (needs.determine-files.outputs.clang_tidy_config_changed != 'true' && inputs.check_only_changed) && needs.determine-files.outputs.all_changed_files || '' }}
|
||||
create_issue_on_failure: ${{ inputs.create_issue_on_failure }}
|
||||
- name: Configure CMake
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
cmake \
|
||||
-G 'Ninja' \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-Dtests=ON \
|
||||
-Dwerr=ON \
|
||||
-Dxrpld=ON \
|
||||
..
|
||||
|
||||
# clang-tidy needs headers generated from proto files
|
||||
- name: Build libxrpl.libpb
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb
|
||||
|
||||
- name: Run clang tidy
|
||||
id: run_clang_tidy
|
||||
continue-on-error: true
|
||||
env:
|
||||
TARGETS: ${{ (needs.determine-files.outputs.clang_tidy_config_changed != 'true' && inputs.check_only_changed) && needs.determine-files.outputs.cpp_changed_files || 'src tests' }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}"
|
||||
|
||||
- name: Print errors
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
sed '/error\||/!d' "${OUTPUT_FILE}"
|
||||
|
||||
- name: Upload clang-tidy output
|
||||
if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
path: ${{ env.OUTPUT_FILE }}
|
||||
archive: false
|
||||
retention-days: 30
|
||||
|
||||
- name: Check for changes
|
||||
id: files_changed
|
||||
continue-on-error: true
|
||||
run: |
|
||||
git diff --exit-code
|
||||
|
||||
- name: Fix style
|
||||
if: ${{ steps.files_changed.outcome != 'success' }}
|
||||
run: |
|
||||
pre-commit run --all-files || true
|
||||
|
||||
- name: Generate git diff
|
||||
if: ${{ steps.files_changed.outcome != 'success' }}
|
||||
run: |
|
||||
git diff | tee "${DIFF_FILE}"
|
||||
|
||||
- name: Upload clang-tidy diff output
|
||||
if: ${{ github.event.repository.visibility == 'public' && steps.files_changed.outcome != 'success' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
path: ${{ env.DIFF_FILE }}
|
||||
archive: false
|
||||
retention-days: 30
|
||||
|
||||
- name: Write issue header
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
cat > "${ISSUE_FILE}" <<EOF
|
||||
## Clang-tidy Check Failed
|
||||
|
||||
### Clang-tidy Output:
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
- name: Append clang-tidy output to issue body (filter for errors and warnings)
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
if [ -f "${OUTPUT_FILE}" ]; then
|
||||
# Extract lines containing 'error:', 'warning:', or 'note:'
|
||||
grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" > filtered-output.txt || true
|
||||
|
||||
# If filtered output is empty, use original (might be a different error format)
|
||||
if [ ! -s filtered-output.txt ]; then
|
||||
cp "${OUTPUT_FILE}" filtered-output.txt
|
||||
fi
|
||||
|
||||
# Truncate if too large
|
||||
head -c 60000 filtered-output.txt >> "${ISSUE_FILE}"
|
||||
if [ "$(wc -c < filtered-output.txt)" -gt 60000 ]; then
|
||||
echo "" >> "${ISSUE_FILE}"
|
||||
echo "... (output truncated, see artifacts for full output)" >> "${ISSUE_FILE}"
|
||||
fi
|
||||
|
||||
rm filtered-output.txt
|
||||
else
|
||||
echo "No output file found" >> "${ISSUE_FILE}"
|
||||
fi
|
||||
|
||||
- name: Append issue footer
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
cat >> "${ISSUE_FILE}" <<EOF
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
*This issue was automatically created by the clang-tidy workflow.*
|
||||
EOF
|
||||
|
||||
- name: Create issue
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure }}
|
||||
uses: XRPLF/actions/create-issue@36d450d12d301e8410c1b7936e5de70c291cbe36
|
||||
with:
|
||||
title: "Clang-tidy check failed"
|
||||
body_file: ${{ env.ISSUE_FILE }}
|
||||
labels: "Bug,Clang-tidy"
|
||||
assignees: "godexsoft,mathbunnyru"
|
||||
|
||||
- name: Fail if clang-tidy found issues
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
echo "Clang-tidy check failed!"
|
||||
exit 1
|
||||
|
||||
99
.github/workflows/reusable-package.yml
vendored
Normal file
99
.github/workflows/reusable-package.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
|
||||
# Discovers which configurations to package from linux.json (os entries
|
||||
# with "package": true) and fans out one job per entry. Today only
|
||||
# linux/amd64 is emitted; the architecture is hardcoded both here
|
||||
# (runner) and in generate.py.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pkg_release:
|
||||
description: "Package release number. Increment when repackaging the same executable."
|
||||
required: false
|
||||
type: string
|
||||
default: "1"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
|
||||
jobs:
|
||||
generate-matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.13
|
||||
|
||||
- name: Generate packaging matrix
|
||||
id: generate
|
||||
working-directory: .github/scripts/strategy-matrix
|
||||
run: |
|
||||
./generate.py --packaging --config=linux.json >> "${GITHUB_OUTPUT}"
|
||||
|
||||
generate-version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/actions/generate-version
|
||||
src/libxrpl/protocol/BuildInfo.cpp
|
||||
- name: Generate version
|
||||
id: version
|
||||
uses: ./.github/actions/generate-version
|
||||
|
||||
package:
|
||||
needs: [generate-matrix, generate-version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
name: "${{ matrix.artifact_name }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) }}
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Download pre-built binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Make binary executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld"
|
||||
|
||||
- name: Build package
|
||||
env:
|
||||
PKG_VERSION: ${{ needs.generate-version.outputs.version }}
|
||||
PKG_RELEASE: ${{ inputs.pkg_release }}
|
||||
run: ./package/build_pkg.sh
|
||||
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }}
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/*.deb
|
||||
${{ env.BUILD_DIR }}/debbuild/*.ddeb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/*.rpm
|
||||
if-no-files-found: error
|
||||
2
.github/workflows/upload-conan-deps.yml
vendored
2
.github/workflows/upload-conan-deps.yml
vendored
@@ -75,7 +75,7 @@ jobs:
|
||||
enable_ccache: false
|
||||
|
||||
- name: Print build environment
|
||||
uses: ./.github/actions/print-env
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
|
||||
@@ -70,7 +70,11 @@ repos:
|
||||
rev: a42085ade523f591dca134379a595e7859986445 # frozen: v9.7.0
|
||||
hooks:
|
||||
- id: cspell # Spell check changed files
|
||||
exclude: (.config/cspell.config.yaml|^include/xrpl/protocol_autogen/(transactions|ledger_entries)/)
|
||||
exclude: |
|
||||
(?x)^(
|
||||
.config/cspell.config.yaml|
|
||||
include/xrpl/protocol_autogen/(transactions|ledger_entries)/.*
|
||||
)$
|
||||
- id: cspell # Spell check the commit message
|
||||
name: check commit message spelling
|
||||
args:
|
||||
|
||||
@@ -28,6 +28,8 @@ This section contains changes targeting a future version.
|
||||
|
||||
### Additions
|
||||
|
||||
- `ledger_entry`, `account_objects`: The `Delegate` ledger entry now includes an optional `DestinationNode` field, which stores the index into the authorized account's owner directory. This field is present on entries created after bidirectional directory tracking was introduced and may appear in RPC responses for those entries. ([#6681](https://github.com/XRPLF/rippled/pull/6681))
|
||||
|
||||
- `server_definitions`: Added the following new sections to the response ([#6321](https://github.com/XRPLF/rippled/pull/6321)):
|
||||
- `TRANSACTION_FORMATS`: Describes the fields and their optionality for each transaction type, including common fields shared across all transactions.
|
||||
- `LEDGER_ENTRY_FORMATS`: Describes the fields and their optionality for each ledger entry type, including common fields shared across all ledger entries.
|
||||
@@ -40,6 +42,14 @@ This section contains changes targeting a future version.
|
||||
- Peer Crawler: The `port` field in `overlay.active[]` now consistently returns an integer instead of a string for outbound peers. [#6318](https://github.com/XRPLF/rippled/pull/6318)
|
||||
- `ping`: The `ip` field is no longer returned as an empty string for proxied connections without a forwarded-for header. It is now omitted, consistent with the behavior for identified connections. [#6730](https://github.com/XRPLF/rippled/pull/6730)
|
||||
- gRPC `GetLedgerDiff`: Fixed error message that incorrectly said "base ledger not validated" when the desired ledger was not validated. [#6730](https://github.com/XRPLF/rippled/pull/6730)
|
||||
- `account_channels`: The `destination_account` field now returns an error if the value is not a string. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `subscribe`: The `taker` field in the `books` array now returns an error if the value is not a string. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `account_info`: The `urlgravatar` field now uses HTTPS instead of HTTP. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `ledger`: The `full`, `accounts`, `transactions`, `expand`, `binary`, `owner_funds`, and `queue` fields now return an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `ledger_data`: The `binary` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `submit`: The `fail_hard` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `subscribe`: The `taker` field in the `books` array now returns `actMalformed` instead of `badIssuer` if the value is not a valid account. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- Fixed a bug in `Forwarded` HTTP header parsing where the extracted IP address could be incorrect when no comma or semicolon delimiter follows the address. This could cause the server to misidentify a client's IP address when operating behind a reverse proxy. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
|
||||
## XRP Ledger server version 3.1.0
|
||||
|
||||
|
||||
8
BUILD.md
8
BUILD.md
@@ -141,7 +141,7 @@ Alternatively, you can pull our recipes from the repository and export them loca
|
||||
|
||||
```bash
|
||||
# Define which recipes to export.
|
||||
recipes=('abseil' 'ed25519' 'grpc' 'm4' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi')
|
||||
recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi')
|
||||
|
||||
# Selectively check out the recipes from our CCI fork.
|
||||
cd external
|
||||
@@ -530,16 +530,16 @@ stored inside the build directory, as either of:
|
||||
## Sanitizers
|
||||
|
||||
To build dependencies and xrpld with sanitizer instrumentation, set the
|
||||
`SANITIZERS` environment variable (only once before running conan and cmake) and use the `sanitizers` profile in conan:
|
||||
`SANITIZERS` environment variable when running `conan install` and use the `sanitizers` profile:
|
||||
|
||||
```bash
|
||||
export SANITIZERS=address,undefinedbehavior
|
||||
|
||||
conan install .. --output-folder . --profile:all sanitizers --build missing --settings build_type=Debug
|
||||
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Debug -Dxrpld=ON -Dtests=ON ..
|
||||
```
|
||||
|
||||
You can then build and test as usual, with the generated `xrpld` binary containing the sanitizer instrumentation. When you run it, it will report any sanitizer errors it detects in the console output.
|
||||
|
||||
See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -134,6 +134,7 @@ endif()
|
||||
include(XrplCore)
|
||||
include(XrplProtocolAutogen)
|
||||
include(XrplInstall)
|
||||
include(XrplPackaging)
|
||||
include(XrplValidatorKeys)
|
||||
|
||||
if(tests)
|
||||
|
||||
@@ -348,8 +348,8 @@ For this reason:
|
||||
- Contract description for `UNREACHABLE` should describe the _unexpected_
|
||||
situation which caused the line to have been reached.
|
||||
- Example good name for an
|
||||
`UNREACHABLE` macro `"Json::operator==(Value, Value) : invalid type"`; example
|
||||
good name for an `XRPL_ASSERT` macro `"Json::Value::asCString : valid type"`.
|
||||
`UNREACHABLE` macro `"json::operator==(Value, Value) : invalid type"`; example
|
||||
good name for an `XRPL_ASSERT` macro `"json::Value::asCString : valid type"`.
|
||||
- Example **bad** name
|
||||
`"RFC1751::insert(char* s, int x, int start, int length) : length is greater than or equal zero"`
|
||||
(missing namespace, unnecessary full function signature, description too verbose).
|
||||
|
||||
113
SECURITY.md
113
SECURITY.md
@@ -22,117 +22,10 @@ Responsible investigation includes, but isn't limited to, the following:
|
||||
- Not targeting physical security measures, or attempting to use social engineering, spam, distributed denial of service (DDOS) attacks, etc.
|
||||
- Investigating bugs in a way that makes a reasonable, good faith effort not to be disruptive or harmful to the XRP Ledger and the broader ecosystem.
|
||||
|
||||
### Responsible Disclosure
|
||||
|
||||
If you discover a vulnerability or potential threat, or if you _think_
|
||||
you have, please reach out by dropping an email using the contact
|
||||
information below.
|
||||
|
||||
Your report should include the following:
|
||||
|
||||
- Your contact information (typically, an email address);
|
||||
- The description of the vulnerability;
|
||||
- The attack scenario (if any);
|
||||
- The steps to reproduce the vulnerability;
|
||||
- Any other relevant details or artifacts, including code, scripts or patches.
|
||||
|
||||
In your email, please describe the issue or potential threat. If possible, include a "repro" (code that can reproduce the issue) or describe the best way to reproduce and replicate the issue. Please make your report as detailed and comprehensive as possible.
|
||||
|
||||
For more information on responsible disclosure, please read this [Wikipedia article](https://en.wikipedia.org/wiki/Responsible_disclosure).
|
||||
|
||||
## Report Handling Process
|
||||
|
||||
Please report the bug directly to us and limit further disclosure. If you want to prove that you knew the bug as of a given time, consider using a cryptographic pre-commitment: hash the content of your report and publish the hash on a medium of your choice (e.g. on Twitter or as a memo in a transaction) as "proof" that you had written the text at a given point in time.
|
||||
|
||||
Once we receive a report, we:
|
||||
|
||||
1. Assign two people to independently evaluate the report;
|
||||
2. Consider their recommendations;
|
||||
3. If action is necessary, formulate a plan to address the issue;
|
||||
4. Communicate privately with the reporter to explain our plan.
|
||||
5. Prepare, test and release a version which fixes the issue; and
|
||||
6. Announce the vulnerability publicly.
|
||||
|
||||
We will triage and respond to your disclosure within 24 hours. Beyond that, we will work to analyze the issue in more detail, formulate, develop and test a fix.
|
||||
|
||||
While we commit to responding with 24 hours of your initial report with our triage assessment, we cannot guarantee a response time for the remaining steps. We will communicate with you throughout this process, letting you know where we are and keeping you updated on the timeframe.
|
||||
|
||||
## Bug Bounty Program
|
||||
|
||||
[Ripple](https://ripple.com) is generously sponsoring a bug bounty program for vulnerabilities in [`xrpld`](https://github.com/XRPLF/rippled) (and other related projects, like [`xrpl.js`](https://github.com/XRPLF/xrpl.js), [`xrpl-py`](https://github.com/XRPLF/xrpl-py), [`xrpl4j`](https://github.com/XRPLF/xrpl4j)).
|
||||
[Ripple](https://ripple.com) is generously sponsoring a bug bounty program for vulnerabilities in [`xrpld`](https://github.com/XRPLF/rippled) (and other related projects, like [`Clio`](https://github.com/XRPLF/clio), [`xrpl.js`](https://github.com/XRPLF/xrpl.js), [`xrpl-py`](https://github.com/XRPLF/xrpl-py), [`xrpl4j`](https://github.com/XRPLF/xrpl4j)).
|
||||
|
||||
This program allows us to recognize and reward individuals or groups that identify and report bugs. In summary, in order to qualify for a bounty, the bug must be:
|
||||
This program allows us to recognize and reward individuals or groups that identify and report bugs.
|
||||
|
||||
1. **In scope**. Only bugs in software under the scope of the program qualify. Currently, that means `xrpld`, `xrpl.js`, `xrpl-py`, `xrpl4j`.
|
||||
2. **Relevant**. A security issue, posing a danger to user funds, privacy, or the operation of the XRP Ledger.
|
||||
3. **Original and previously unknown**. Bugs that are already known and discussed in public do not qualify. Previously reported bugs, even if publicly unknown, are not eligible.
|
||||
4. **Specific**. We welcome general security advice or recommendations, but we cannot pay bounties for that.
|
||||
5. **Fixable**. There has to be something we can do to permanently fix the problem. Note that bugs in other people’s software may still qualify in some cases. For example, if you find a bug in a library that we use which can compromise the security of software that is in scope and we can get it fixed, you may qualify for a bounty.
|
||||
6. **Unused**. If you use the exploit to attack the XRP Ledger, you do not qualify for a bounty. If you report a vulnerability used in an ongoing or past attack and there is specific, concrete evidence that suggests you are the attacker we reserve the right not to pay a bounty.
|
||||
|
||||
The amount paid varies dramatically. Vulnerabilities that are harmless on their own, but could form part of a critical exploit will usually receive a bounty. Full-blown exploits can receive much higher bounties. Please don’t hold back partial vulnerabilities while trying to construct a full-blown exploit. We will pay a bounty to anyone who reports a complete chain of vulnerabilities even if they have reported each component of the exploit separately and those vulnerabilities have been fixed in the meantime. However, to qualify for a the full bounty, you must to have been the first to report each of the partial exploits.
|
||||
|
||||
### Contacting Us
|
||||
|
||||
To report a qualifying bug, please send a detailed report to:
|
||||
|
||||
| Email Address | bugs@ripple.com |
|
||||
| :-----------: | :-------------------------------------------------- |
|
||||
| Short Key ID | `0xA9F514E0` |
|
||||
| Long Key ID | `0xD900855AA9F514E0` |
|
||||
| Fingerprint | `B72C 0654 2F2A E250 2763 A268 D900 855A A9F5 14E0` |
|
||||
|
||||
The full PGP key for this address, which is also available on several key servers (e.g. on [keyserver.ubuntu.com](https://keyserver.ubuntu.com)), is:
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
mQINBGkSZAQBEACprU199OhgdsOsygNjiQV4msuN3vDOUooehL+NwfsGfW79Tbqq
|
||||
Q2u7uQ3NZjW+M2T4nsDwuhkr7pe7xSReR5W8ssaczvtUyxkvbMClilcgZ2OSCAuC
|
||||
N9tzJsqOqkwBvXoNXkn//T2jnPz0ZU2wSF+NrEibq5FeuyGdoX3yXXBxq9pW9HzK
|
||||
HkQll63QSl6BzVSGRQq+B6lGgaZGLwf3mzmIND9Z5VGLNK2jKynyz9z091whNG/M
|
||||
kV+E7/r/bujHk7WIVId07G5/COTXmSr7kFnNEkd2Umw42dkgfiNKvlmJ9M7c1wLK
|
||||
KbL9Eb4ADuW6rRc5k4s1e6GT8R4/VPliWbCl9SE32hXH8uTkqVIFZP2eyM5WRRHs
|
||||
aKzitkQG9UK9gcb0kdgUkxOvvgPHAe5IuZlcHFzU4y0dBbU1VEFWVpiLU0q+IuNw
|
||||
5BRemeHc59YNsngkmAZ+/9zouoShRusZmC8Wzotv75C2qVBcjijPvmjWAUz0Zunm
|
||||
Lsr+O71vqHE73pERjD07wuD/ISjiYRYYE/bVrXtXLZijC7qAH4RE3nID+2ojcZyO
|
||||
/2jMQvt7un56RsGH4UBHi3aBHi9bUoDGCXKiQY981cEuNaOxpou7Mh3x/ONzzSvk
|
||||
sTV6nl1LOZHykN1JyKwaNbTSAiuyoN+7lOBqbV04DNYAHL88PrT21P83aQARAQAB
|
||||
tB1SaXBwbGUgTGFicyA8YnVnc0ByaXBwbGUuY29tPokCTgQTAQgAOBYhBLcsBlQv
|
||||
KuJQJ2OiaNkAhVqp9RTgBQJpEmQEAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA
|
||||
AAoJENkAhVqp9RTgBzgP/i7y+aDWl1maig1XMdyb+o0UGusumFSW4Hmj278wlKVv
|
||||
usgLPihYgHE0PKrv6WRyKOMC1tQEcYYN93M+OeQ1vFhS2YyURq6RCMmh4zq/awXG
|
||||
uZbG36OURB5NH8lGBOHiN/7O+nY0CgenBT2JWm+GW3nEOAVOVm4+r5GlpPlv+Dp1
|
||||
NPBThcKXFMnH73++NpSQoDzTfRYHPxhDAX3jkLi/moXfSanOLlR6l94XNNN0jBHW
|
||||
Quao0rzf4WSXq9g6AS224xhAA5JyIcFl8TX7hzj5HaFn3VWo3COoDu4U7H+BM0fl
|
||||
85yqiMQypp7EhN2gxpMMWaHY5TFM85U/bFXFYfEgihZ4/gt4uoIzsNI9jlX7mYvG
|
||||
KFdDij+oTlRsuOxdIy60B3dKcwOH9nZZCz0SPsN/zlRWgKzK4gDKdGhFkU9OlvPu
|
||||
94ZqscanoiWKDoZkF96+sjgfjkuHsDK7Lwc1Xi+T4drHG/3aVpkYabXox+lrKB/S
|
||||
yxZjeqOIQzWPhnLgCaLyvsKo5hxKzL0w3eURu8F3IS7RgOOlljv4M+Me9sEVcdNV
|
||||
aN3/tQwbaomSX1X5D5YXqhBwC3rU3wXwamsscRTGEpkV+JCX6KUqGP7nWmxCpAly
|
||||
FL05XuOd5SVHJjXLeuje0JqLUpN514uL+bThWwDbDTdAdwW3oK/2WbXz7IfJRLBj
|
||||
uQINBGkSZAQBEADdI3SL2F72qkrgFqXWE6HSRBu9bsAvTE5QrRPWk7ux6at537r4
|
||||
S4sIw2dOwLvbyIrDgKNq3LQ5wCK88NO/NeCOFm4AiCJSl3pJHXYnTDoUxTrrxx+o
|
||||
vSRI4I3fHEql/MqzgiAb0YUezjgFdh3vYheMPp/309PFbOLhiFqEcx80Mx5h06UH
|
||||
gDzu1qNj3Ec+31NLic5zwkrAkvFvD54d6bqYR3SEgMau6aYEewpGHbWBi2pLqSi2
|
||||
lQcAeOFixqGpTwDmAnYR8YtjBYepy0MojEAdTHcQQlOYSDk4q4elG+io2N8vECfU
|
||||
rD6ORecN48GXdZINYWTAdslrUeanmBdgQrYkSpce8TSghgT9P01SNaXxmyaehVUO
|
||||
lqI4pcg5G2oojAE8ncNS3TwDtt7daTaTC3bAdr4PXDVAzNAiewjMNZPB7xidkDGQ
|
||||
Y4W1LxTMXyJVWxehYOH7tsbBRKninlfRnLgYzmtIbNRAAvNcsxU6ihv3AV0WFknN
|
||||
YbSzotEv1Xq/5wk309x8zCDe+sP0cQicvbXafXmUzPAZzeqFg+VLFn7F9MP1WGlW
|
||||
B1u7VIvBF1Mp9Nd3EAGBAoLRdRu+0dVWIjPTQuPIuD9cCatJA0wVaKUrjYbBMl88
|
||||
a12LixNVGeSFS9N7ADHx0/o7GNT6l88YbaLP6zggUHpUD/bR+cDN7vllIQARAQAB
|
||||
iQI2BBgBCAAgFiEEtywGVC8q4lAnY6Jo2QCFWqn1FOAFAmkSZAQCGwwACgkQ2QCF
|
||||
Wqn1FOAfAA/8CYq4p0p4bobY20CKEMsZrkBTFJyPDqzFwMeTjgpzqbD7Y3Qq5QCK
|
||||
OBbvY02GWdiIsNOzKdBxiuam2xYP9WHZj4y7/uWEvT0qlPVmDFu+HXjoJ43oxwFd
|
||||
CUp2gMuQ4cSL3X94VRJ3BkVL+tgBm8CNY0vnTLLOO3kum/R69VsGJS1JSGUWjNM+
|
||||
4qwS3mz+73xJu1HmERyN2RZF/DGIZI2PyONQQ6aH85G1Dd2ohu2/DBAkQAMBrPbj
|
||||
FrbDaBLyFhODxU3kTWqnfLlaElSm2EGdIU2yx7n4BggEa//NZRMm5kyeo4vzhtlQ
|
||||
YIVUMLAOLZvnEqDnsLKp+22FzNR/O+htBQC4lPywl53oYSALdhz1IQlcAC1ru5KR
|
||||
XPzhIXV6IIzkcx9xNkEclZxmsuy5ERXyKEmLbIHAlzFmnrldlt2ZgXDtzaorLmxj
|
||||
klKibxd5tF50qOpOivz+oPtFo7n+HmFa1nlVAMxlDCUdM0pEVeYDKI5zfVwalyhZ
|
||||
NnjpakdZSXMwgc7NP/hH9buF35hKDp7EckT2y3JNYwHsDdy1icXN2q40XZw5tSIn
|
||||
zkPWdu3OUY8PISohN6Pw4h0RH4ZmoX97E8sEfmdKaT58U4Hf2aAv5r9IWCSrAVqY
|
||||
u5jvac29CzQR9Kal0A+8phHAXHNFD83SwzIC0syaT9ficAguwGH8X6Q=
|
||||
=nGuD
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
We have partnered with Bugcrowd to manage this program. It is a private program, and security researchers can participate based on invitation. If you need access to the program, please email bugs@ripple.com with your Bugcrowd handle or Bugcrowd registered email, and we will get you added to the program. Once you have been added, please submit vulnerability reports through Bugcrowd, not by email. The detailed bug bounty policy is available on the Bugcrowd website.
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
# https://vl.ripple.com
|
||||
# https://unl.xrplf.org
|
||||
# http://127.0.0.1:8000
|
||||
# file:///etc/opt/xrpld/vl.txt
|
||||
# file:///etc/xrpld/vl.txt
|
||||
#
|
||||
# [validator_list_keys]
|
||||
#
|
||||
|
||||
@@ -527,6 +527,17 @@
|
||||
#
|
||||
# The current default (which is subject to change) is 300 seconds.
|
||||
#
|
||||
# verify_endpoints = <0 | 1>
|
||||
#
|
||||
# If set to 0, the server will skip validation of endpoint
|
||||
# addresses received in TMEndpoints peer protocol messages,
|
||||
# allowing addresses that are not publicly routable or have a
|
||||
# port of 0. The default is 1 (verification enabled).
|
||||
#
|
||||
# WARNING: Disabling this option is a security risk and should
|
||||
# only be used for local testing and debugging. Do not disable
|
||||
# on mainnet.
|
||||
#
|
||||
#
|
||||
# [transaction_queue] EXPERIMENTAL
|
||||
#
|
||||
@@ -1258,7 +1269,7 @@
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# account_reserve = 10000000 # 10 XRP
|
||||
# account_reserve = 1000000 # 1 XRP
|
||||
#
|
||||
# owner_reserve = <drops>
|
||||
#
|
||||
@@ -1270,7 +1281,7 @@
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# owner_reserve = 2000000 # 2 XRP
|
||||
# owner_reserve = 200000 # 0.2 XRP
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
|
||||
@@ -118,7 +118,7 @@ if(MSVC)
|
||||
NOMINMAX
|
||||
# TODO: Resolve these warnings, don't just silence them
|
||||
_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
|
||||
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:Debug>,$<NOT:$<BOOL:${is_ci}>>>:_CRTDBG_MAP_ALLOC>
|
||||
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:Debug>>:_CRTDBG_MAP_ALLOC>
|
||||
)
|
||||
target_link_libraries(common INTERFACE -errorreport:none -machine:X64)
|
||||
else()
|
||||
|
||||
@@ -23,7 +23,6 @@ target_compile_definitions(
|
||||
BOOST_FILESYSTEM_NO_DEPRECATED
|
||||
>
|
||||
$<$<NOT:$<BOOL:${boost_show_deprecated}>>:
|
||||
BOOST_COROUTINES2_NO_DEPRECATION_WARNING
|
||||
BOOST_BEAST_ALLOW_DEPRECATED
|
||||
BOOST_FILESYSTEM_DEPRECATED
|
||||
>
|
||||
|
||||
44
cmake/XrplPackaging.cmake
Normal file
44
cmake/XrplPackaging.cmake
Normal file
@@ -0,0 +1,44 @@
|
||||
#[===================================================================[
|
||||
Linux packaging support: 'package' target.
|
||||
|
||||
The packaging script (package/build_pkg.sh) installs to FHS-standard
|
||||
paths (/usr/bin, /etc/xrpld, etc.) regardless of CMAKE_INSTALL_PREFIX,
|
||||
so no prefix guard is needed here.
|
||||
#]===================================================================]
|
||||
if(NOT is_linux)
|
||||
message(STATUS "Packaging not supported on non-Linux hosts")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED pkg_release)
|
||||
set(pkg_release 1)
|
||||
endif()
|
||||
|
||||
find_program(RPMBUILD_EXECUTABLE rpmbuild)
|
||||
find_program(DPKG_BUILDPACKAGE_EXECUTABLE dpkg-buildpackage)
|
||||
|
||||
if(NOT (RPMBUILD_EXECUTABLE OR DPKG_BUILDPACKAGE_EXECUTABLE))
|
||||
message(
|
||||
STATUS
|
||||
"Neither rpmbuild nor dpkg-buildpackage found; 'package' target not available"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(package_env
|
||||
SRC_DIR=${CMAKE_SOURCE_DIR}
|
||||
BUILD_DIR=${CMAKE_BINARY_DIR}
|
||||
PKG_VERSION=${xrpld_version}
|
||||
PKG_RELEASE=${pkg_release}
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
package
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E env ${package_env}
|
||||
${CMAKE_SOURCE_DIR}/package/build_pkg.sh
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS xrpld
|
||||
COMMENT "Building Linux package (deb/rpm inferred from host tooling)"
|
||||
VERBATIM
|
||||
)
|
||||
@@ -1,138 +1,33 @@
|
||||
#[===================================================================[
|
||||
Configure sanitizers based on environment variables.
|
||||
Apply sanitizer flags built by the Conan profile.
|
||||
|
||||
This module reads the following environment variables:
|
||||
- SANITIZERS: The sanitizers to enable. Possible values:
|
||||
- "address"
|
||||
- "address,undefinedbehavior"
|
||||
- "thread"
|
||||
- "thread,undefinedbehavior"
|
||||
- "undefinedbehavior"
|
||||
Parsing, validation, and flag construction are performed in conan/profiles/sanitizers.
|
||||
This module reads the following CMake variables injected by the Conan toolchain via extra_variables:
|
||||
|
||||
The compiler type and platform are detected in CompilationEnv.cmake.
|
||||
The sanitizer compile options are applied to the 'common' interface library
|
||||
which is linked to all targets in the project.
|
||||
- SANITIZERS: The active sanitizers (e.g. "address,undefinedbehavior").
|
||||
- SANITIZERS_COMPILER_FLAGS: Space-separated compiler flags.
|
||||
- SANITIZERS_LINKER_FLAGS: Space-separated linker flags.
|
||||
|
||||
Internal flag variables set by this module:
|
||||
|
||||
- SANITIZER_TYPES: List of sanitizer types to enable (e.g., "address",
|
||||
"thread", "undefined"). And two more flags for undefined behavior sanitizer (e.g., "float-divide-by-zero", "unsigned-integer-overflow").
|
||||
This list is joined with commas and passed to -fsanitize=<list>.
|
||||
|
||||
- SANITIZERS_COMPILE_FLAGS: Compiler flags for sanitizer instrumentation.
|
||||
Includes:
|
||||
* -fno-omit-frame-pointer: Preserves frame pointers for stack traces
|
||||
* -O1: Minimum optimization for reasonable performance
|
||||
* -fsanitize=<types>: Enables sanitizer instrumentation
|
||||
* -fsanitize-ignorelist=<path>: (Clang only) Compile-time ignorelist
|
||||
* -mcmodel=large/medium: (GCC only) Code model for large binaries
|
||||
* -Wno-stringop-overflow: (GCC only) Suppresses false positive warnings
|
||||
* -Wno-tsan: (For GCC TSAN combination only) Suppresses atomic_thread_fence warnings
|
||||
|
||||
- SANITIZERS_LINK_FLAGS: Linker flags for sanitizer runtime libraries.
|
||||
Includes:
|
||||
* -fsanitize=<types>: Links sanitizer runtime libraries
|
||||
* -mcmodel=large/medium: (GCC only) Matches compile-time code model
|
||||
|
||||
- SANITIZERS_RELOCATION_FLAGS: (GCC only) Code model flags for linking.
|
||||
Used to handle large instrumented binaries on x86_64:
|
||||
* -mcmodel=large: For AddressSanitizer (prevents relocation errors)
|
||||
* -mcmodel=medium: For ThreadSanitizer (large model is incompatible)
|
||||
The flags are applied to the 'common' interface library which is linked to all targets in the project.
|
||||
#]===================================================================]
|
||||
|
||||
include_guard(GLOBAL)
|
||||
include(CompilationEnv)
|
||||
|
||||
# Read environment variable
|
||||
set(SANITIZERS "")
|
||||
if(DEFINED ENV{SANITIZERS})
|
||||
set(SANITIZERS "$ENV{SANITIZERS}")
|
||||
endif()
|
||||
|
||||
# Set SANITIZERS_ENABLED flag for use in other modules
|
||||
if(SANITIZERS MATCHES "address|thread|undefinedbehavior")
|
||||
set(SANITIZERS_ENABLED TRUE)
|
||||
else()
|
||||
if(NOT DEFINED SANITIZERS)
|
||||
set(SANITIZERS_ENABLED FALSE)
|
||||
return()
|
||||
endif()
|
||||
set(SANITIZERS_ENABLED TRUE)
|
||||
|
||||
# Sanitizers are not supported on Windows/MSVC
|
||||
if(is_msvc)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Sanitizers are not supported on Windows/MSVC. "
|
||||
"Please unset the SANITIZERS environment variable."
|
||||
)
|
||||
endif()
|
||||
message(STATUS "=== Configuring Sanitizers ===")
|
||||
message(STATUS " SANITIZERS: ${SANITIZERS}")
|
||||
message(STATUS " Compile flags: ${SANITIZERS_COMPILER_FLAGS}")
|
||||
message(STATUS " Link flags: ${SANITIZERS_LINKER_FLAGS}")
|
||||
|
||||
message(STATUS "Configuring sanitizers: ${SANITIZERS}")
|
||||
|
||||
# Parse SANITIZERS value to determine which sanitizers to enable
|
||||
set(enable_asan FALSE)
|
||||
set(enable_tsan FALSE)
|
||||
set(enable_ubsan FALSE)
|
||||
|
||||
# Normalize SANITIZERS into a list
|
||||
set(san_list "${SANITIZERS}")
|
||||
string(REPLACE "," ";" san_list "${san_list}")
|
||||
separate_arguments(san_list)
|
||||
|
||||
foreach(san IN LISTS san_list)
|
||||
if(san STREQUAL "address")
|
||||
set(enable_asan TRUE)
|
||||
elseif(san STREQUAL "thread")
|
||||
set(enable_tsan TRUE)
|
||||
elseif(san STREQUAL "undefinedbehavior")
|
||||
set(enable_ubsan TRUE)
|
||||
else()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Unsupported sanitizer type: ${san}"
|
||||
"Supported: address, thread, undefinedbehavior and their combinations."
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Validate sanitizer compatibility
|
||||
if(enable_asan AND enable_tsan)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"AddressSanitizer and ThreadSanitizer are incompatible and cannot be enabled simultaneously. "
|
||||
"Use 'address' or 'thread', optionally with 'undefinedbehavior'."
|
||||
)
|
||||
endif()
|
||||
|
||||
# Frame pointer is required for meaningful stack traces. Sanitizers recommend minimum of -O1 for reasonable performance
|
||||
set(SANITIZERS_COMPILE_FLAGS "-fno-omit-frame-pointer" "-O1")
|
||||
|
||||
# Build the sanitizer flags list
|
||||
set(SANITIZER_TYPES)
|
||||
|
||||
if(enable_asan)
|
||||
list(APPEND SANITIZER_TYPES "address")
|
||||
elseif(enable_tsan)
|
||||
list(APPEND SANITIZER_TYPES "thread")
|
||||
endif()
|
||||
|
||||
if(enable_ubsan)
|
||||
# UB sanitizer flags
|
||||
list(APPEND SANITIZER_TYPES "undefined" "float-divide-by-zero")
|
||||
if(is_clang)
|
||||
# Clang supports additional UB checks. More info here
|
||||
# https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
|
||||
list(APPEND SANITIZER_TYPES "unsigned-integer-overflow")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Configure code model for GCC on amd64 Use large code model for ASAN to avoid relocation errors Use medium code model
|
||||
# for TSAN (large is not compatible with TSAN)
|
||||
set(SANITIZERS_RELOCATION_FLAGS)
|
||||
|
||||
# Compiler-specific configuration
|
||||
# GCC with sanitizers is incompatible with mold, gold, and lld linkers.
|
||||
# Namely, the instrumented binary exceeds size limits imposed by these linkers.
|
||||
if(is_gcc)
|
||||
# Disable mold, gold and lld linkers for GCC with sanitizers Use default linker (bfd/ld) which is more lenient with
|
||||
# mixed code models This is needed since the size of instrumented binary exceeds the limits set by mold, lld and
|
||||
# gold linkers
|
||||
set(use_mold OFF CACHE BOOL "Use mold linker" FORCE)
|
||||
set(use_gold OFF CACHE BOOL "Use gold linker" FORCE)
|
||||
set(use_lld OFF CACHE BOOL "Use lld linker" FORCE)
|
||||
@@ -140,80 +35,62 @@ if(is_gcc)
|
||||
STATUS
|
||||
" Disabled mold, gold, and lld linkers for GCC with sanitizers"
|
||||
)
|
||||
|
||||
# Suppress false positive warnings in GCC with stringop-overflow
|
||||
list(APPEND SANITIZERS_COMPILE_FLAGS "-Wno-stringop-overflow")
|
||||
|
||||
if(is_amd64 AND enable_asan)
|
||||
message(STATUS " Using large code model (-mcmodel=large)")
|
||||
list(APPEND SANITIZERS_COMPILE_FLAGS "-mcmodel=large")
|
||||
list(APPEND SANITIZERS_RELOCATION_FLAGS "-mcmodel=large")
|
||||
elseif(enable_tsan)
|
||||
# GCC doesn't support atomic_thread_fence with tsan. Suppress warnings.
|
||||
list(APPEND SANITIZERS_COMPILE_FLAGS "-Wno-tsan")
|
||||
message(STATUS " Using medium code model (-mcmodel=medium)")
|
||||
list(APPEND SANITIZERS_COMPILE_FLAGS "-mcmodel=medium")
|
||||
list(APPEND SANITIZERS_RELOCATION_FLAGS "-mcmodel=medium")
|
||||
endif()
|
||||
|
||||
# Join sanitizer flags with commas for -fsanitize option
|
||||
list(JOIN SANITIZER_TYPES "," SANITIZER_TYPES_STR)
|
||||
|
||||
# Add sanitizer to compile and link flags
|
||||
list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}")
|
||||
set(SANITIZERS_LINK_FLAGS
|
||||
"${SANITIZERS_RELOCATION_FLAGS}"
|
||||
"-fsanitize=${SANITIZER_TYPES_STR}"
|
||||
)
|
||||
elseif(is_clang)
|
||||
# Add ignorelist for Clang (GCC doesn't support this) Use CMAKE_SOURCE_DIR to get the path to the ignorelist
|
||||
set(IGNORELIST_PATH
|
||||
"${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt"
|
||||
)
|
||||
if(NOT EXISTS "${IGNORELIST_PATH}")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Sanitizer ignorelist not found: ${IGNORELIST_PATH}"
|
||||
)
|
||||
endif()
|
||||
|
||||
list(
|
||||
APPEND SANITIZERS_COMPILE_FLAGS
|
||||
"-fsanitize-ignorelist=${IGNORELIST_PATH}"
|
||||
)
|
||||
message(STATUS " Using sanitizer ignorelist: ${IGNORELIST_PATH}")
|
||||
|
||||
# Join sanitizer flags with commas for -fsanitize option
|
||||
list(JOIN SANITIZER_TYPES "," SANITIZER_TYPES_STR)
|
||||
|
||||
# Add sanitizer to compile and link flags
|
||||
list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}")
|
||||
set(SANITIZERS_LINK_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}")
|
||||
endif()
|
||||
|
||||
message(STATUS " Compile flags: ${SANITIZERS_COMPILE_FLAGS}")
|
||||
message(STATUS " Link flags: ${SANITIZERS_LINK_FLAGS}")
|
||||
# Flags arrive as space-separated strings; split into CMake lists before use
|
||||
separate_arguments(
|
||||
sanitizers_compiler_flags
|
||||
UNIX_COMMAND
|
||||
"${SANITIZERS_COMPILER_FLAGS}"
|
||||
)
|
||||
separate_arguments(
|
||||
sanitizers_linker_flags
|
||||
UNIX_COMMAND
|
||||
"${SANITIZERS_LINKER_FLAGS}"
|
||||
)
|
||||
|
||||
# Apply the sanitizer flags to the 'common' interface library This is the same library used by XrplCompiler.cmake
|
||||
target_compile_options(
|
||||
common
|
||||
INTERFACE
|
||||
$<$<COMPILE_LANGUAGE:CXX>:${SANITIZERS_COMPILE_FLAGS}>
|
||||
$<$<COMPILE_LANGUAGE:C>:${SANITIZERS_COMPILE_FLAGS}>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:${sanitizers_compiler_flags}>
|
||||
$<$<COMPILE_LANGUAGE:C>:${sanitizers_compiler_flags}>
|
||||
)
|
||||
target_link_options(common INTERFACE ${sanitizers_linker_flags})
|
||||
|
||||
# Apply linker flags
|
||||
target_link_options(common INTERFACE ${SANITIZERS_LINK_FLAGS})
|
||||
# This module appends -fsanitize-ignorelist=<path> for Clang builds.
|
||||
# The ignorelist path contains CMAKE_SOURCE_DIR, so it must be set here, rather than in the Conan profile.
|
||||
# GCC does not support -fsanitize-ignorelist.
|
||||
if(is_clang)
|
||||
set(ignorelist_path
|
||||
"${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt"
|
||||
)
|
||||
if(NOT EXISTS "${ignorelist_path}")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Sanitizer ignorelist not found: ${ignorelist_path}"
|
||||
)
|
||||
endif()
|
||||
target_compile_options(
|
||||
common
|
||||
INTERFACE
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-fsanitize-ignorelist=${ignorelist_path}>
|
||||
$<$<COMPILE_LANGUAGE:C>:-fsanitize-ignorelist=${ignorelist_path}>
|
||||
)
|
||||
message(STATUS " Ignorelist: ${ignorelist_path}")
|
||||
endif()
|
||||
|
||||
# Define SANITIZERS macro for BuildInfo.cpp
|
||||
set(sanitizers_list)
|
||||
if(enable_asan)
|
||||
if(SANITIZERS MATCHES "address")
|
||||
set(enable_asan ON)
|
||||
list(APPEND sanitizers_list "ASAN")
|
||||
endif()
|
||||
if(enable_tsan)
|
||||
if(SANITIZERS MATCHES "thread")
|
||||
set(enable_tsan ON)
|
||||
list(APPEND sanitizers_list "TSAN")
|
||||
endif()
|
||||
if(enable_ubsan)
|
||||
if(SANITIZERS MATCHES "undefinedbehavior")
|
||||
set(enable_ubsan ON)
|
||||
list(APPEND sanitizers_list "UBSAN")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -152,10 +152,10 @@ def parse_sfields_macro(sfields_path):
|
||||
|
||||
def create_field_list_parser():
|
||||
"""Create a pyparsing parser for field lists like '({...})'."""
|
||||
# A field identifier (e.g., sfDestination, soeREQUIRED, soeMPTSupported)
|
||||
# A field identifier (e.g., sfDestination, SoeRequired, SoeMptSupported)
|
||||
field_identifier = pp.Word(pp.alphas + "_", pp.alphanums + "_")
|
||||
|
||||
# A single field definition: {sfName, soeREQUIRED, ...}
|
||||
# A single field definition: {sfName, SoeRequired, ...}
|
||||
# Allow optional trailing comma inside the braces
|
||||
field_def = (
|
||||
pp.Suppress("{")
|
||||
@@ -185,8 +185,8 @@ def parse_field_list(fields_str):
|
||||
|
||||
Args:
|
||||
fields_str: A string like '({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported}
|
||||
{sfDestination, SoeRequired},
|
||||
{sfAmount, SoeRequired, SoeMptSupported}
|
||||
})'
|
||||
|
||||
Returns:
|
||||
@@ -205,7 +205,7 @@ def parse_field_list(fields_str):
|
||||
field_name = field_parts[0]
|
||||
requirement = field_parts[1]
|
||||
flags = list(field_parts[2:]) if len(field_parts) > 2 else []
|
||||
supports_mpt = "soeMPTSupported" in flags
|
||||
supports_mpt = "SoeMptSupported" in flags
|
||||
|
||||
fields.append(
|
||||
{
|
||||
|
||||
@@ -10,4 +10,4 @@ pcpp>=1.30
|
||||
pyparsing>=3.0.0
|
||||
|
||||
# Template engine - used to generate C++ code from templates
|
||||
Mako>=1.2.0
|
||||
Mako>=1.2.2
|
||||
|
||||
@@ -52,13 +52,13 @@ public:
|
||||
% if field.get('mpt_support'):
|
||||
* MPT Support: ${field['mpt_support']}
|
||||
% endif
|
||||
% if field['requirement'] == 'soeREQUIRED':
|
||||
% if field['requirement'] == 'SoeRequired':
|
||||
* @return The field value.
|
||||
% else:
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
% endif
|
||||
*/
|
||||
% if field['requirement'] == 'soeREQUIRED':
|
||||
% if field['requirement'] == 'SoeRequired':
|
||||
[[nodiscard]]
|
||||
${field['typeData']['return_type']}
|
||||
get${field['name'][2:]}() const
|
||||
@@ -94,13 +94,13 @@ public:
|
||||
* MPT Support: ${field['mpt_support']}
|
||||
% endif
|
||||
* @note This is an untyped field (${field.get('cppType', 'unknown')}).
|
||||
% if field['requirement'] == 'soeREQUIRED':
|
||||
% if field['requirement'] == 'SoeRequired':
|
||||
* @return The field value.
|
||||
% else:
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
% endif
|
||||
*/
|
||||
% if field['requirement'] == 'soeREQUIRED':
|
||||
% if field['requirement'] == 'SoeRequired':
|
||||
[[nodiscard]]
|
||||
${field['typeData']['return_type']}
|
||||
get${field['name'][2:]}() const
|
||||
@@ -133,13 +133,13 @@ public:
|
||||
};
|
||||
|
||||
<%
|
||||
required_fields = [f for f in fields if f['requirement'] == 'soeREQUIRED']
|
||||
required_fields = [f for f in fields if f['requirement'] == 'SoeRequired']
|
||||
%>\
|
||||
/**
|
||||
* @brief Builder for ${name} ledger entries.
|
||||
*
|
||||
* Provides a fluent interface for constructing ledger entries with method chaining.
|
||||
* Uses Json::Value internally for flexible ledger entry construction.
|
||||
* Uses STObject internally for flexible ledger entry construction.
|
||||
* Inherits common field setters from LedgerEntryBuilderBase.
|
||||
*/
|
||||
class ${name}Builder : public LedgerEntryBuilderBase<${name}Builder>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Auto-generated unit tests for ledger entry ${name}
|
||||
<%
|
||||
required_fields = [f for f in fields if f["requirement"] == "soeREQUIRED"]
|
||||
optional_fields = [f for f in fields if f["requirement"] != "soeREQUIRED"]
|
||||
required_fields = [f for f in fields if f["requirement"] == "SoeRequired"]
|
||||
optional_fields = [f for f in fields if f["requirement"] != "SoeRequired"]
|
||||
|
||||
def canonical_expr(field):
|
||||
return f"canonical_{field['stiSuffix']}()"
|
||||
|
||||
@@ -54,13 +54,13 @@ public:
|
||||
% if field.get('supports_mpt'):
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
% endif
|
||||
% if field['requirement'] == 'soeREQUIRED':
|
||||
% if field['requirement'] == 'SoeRequired':
|
||||
* @return The field value.
|
||||
% else:
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
% endif
|
||||
*/
|
||||
% if field['requirement'] == 'soeREQUIRED':
|
||||
% if field['requirement'] == 'SoeRequired':
|
||||
[[nodiscard]]
|
||||
${field['typeData']['return_type']}
|
||||
get${field['name'][2:]}() const
|
||||
@@ -97,13 +97,13 @@ public:
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
% endif
|
||||
* @note This is an untyped field.
|
||||
% if field['requirement'] == 'soeREQUIRED':
|
||||
% if field['requirement'] == 'SoeRequired':
|
||||
* @return The field value.
|
||||
% else:
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
% endif
|
||||
*/
|
||||
% if field['requirement'] == 'soeREQUIRED':
|
||||
% if field['requirement'] == 'SoeRequired':
|
||||
[[nodiscard]]
|
||||
${field['typeData']['return_type']}
|
||||
get${field['name'][2:]}() const
|
||||
@@ -136,13 +136,13 @@ public:
|
||||
};
|
||||
|
||||
<%
|
||||
required_fields = [f for f in fields if f['requirement'] == 'soeREQUIRED']
|
||||
required_fields = [f for f in fields if f['requirement'] == 'SoeRequired']
|
||||
%>\
|
||||
/**
|
||||
* @brief Builder for ${name} transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses Json::Value internally for flexible transaction construction.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class ${name}Builder : public TransactionBuilderBase<${name}Builder>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Auto-generated unit tests for transaction ${name}
|
||||
<%
|
||||
required_fields = [f for f in fields if f["requirement"] == "soeREQUIRED"]
|
||||
optional_fields = [f for f in fields if f["requirement"] != "soeREQUIRED"]
|
||||
required_fields = [f for f in fields if f["requirement"] == "SoeRequired"]
|
||||
optional_fields = [f for f in fields if f["requirement"] != "SoeRequired"]
|
||||
|
||||
def canonical_expr(field):
|
||||
return f"canonical_{field['stiSuffix']}()"
|
||||
@@ -33,7 +33,7 @@ TEST(Transactions${name}Tests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::secp256k1, generateSeed("test${name}"));
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("test${name}"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
@@ -101,7 +101,7 @@ TEST(Transactions${name}Tests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::secp256k1, generateSeed("test${name}FromTx"));
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("test${name}FromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
@@ -168,7 +168,7 @@ TEST(Transactions${name}Tests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::secp256k1, generateSeed("testWrongType"));
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
% if wrong_tx_include == "AccountSet":
|
||||
@@ -186,7 +186,7 @@ TEST(Transactions${name}Tests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
% if wrong_tx_include == "AccountSet":
|
||||
@@ -205,7 +205,7 @@ TEST(Transactions${name}Tests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::secp256k1, generateSeed("test${name}Nullopt"));
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("test${name}Nullopt"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
|
||||
24
conan.lock
24
conan.lock
@@ -1,40 +1,38 @@
|
||||
{
|
||||
"version": "0.5",
|
||||
"requires": [
|
||||
"zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809",
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
||||
"sqlite3/3.51.0#66aa11eabd0e34954c5c1c061ad44abe%1774467355.988",
|
||||
"spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1768312128.781",
|
||||
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1776096494.149",
|
||||
"soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",
|
||||
"secp256k1/0.7.1#481881709eb0bdd0185a12b912bbe8ad%1770910500.329",
|
||||
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1765850186.86",
|
||||
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1774398111.888",
|
||||
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12",
|
||||
"openssl/3.6.1#e6399de266349245a4542fc5f6c71552%1774458290.139",
|
||||
"openssl/3.6.2#4789bbf131b77d0515d15e094c8f697f%1778071755.506",
|
||||
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1775040983.408",
|
||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
|
||||
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
|
||||
"libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736",
|
||||
"jemalloc/5.3.0#c671e612af76700db5957c9857978a1c%1776700030.961",
|
||||
"libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1776147552.838",
|
||||
"jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228",
|
||||
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152",
|
||||
"grpc/1.78.1#b1a9e74b145cc471bed4dc64dc6eb2c1%1774467387.342",
|
||||
"fmt/12.1.0#50abab23274d56bb8f42c94b3b9a40c7%1763984116.926",
|
||||
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772",
|
||||
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1774439234.681",
|
||||
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837",
|
||||
"boost/1.90.0#d5e8defe7355494953be18524a7f135b%1769454080.269",
|
||||
"boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1778050991.9",
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196"
|
||||
],
|
||||
"build_requires": [
|
||||
"zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809",
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503",
|
||||
"strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1774447376.964",
|
||||
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12",
|
||||
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707",
|
||||
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
|
||||
"m4/1.4.19#5d7a4994e5875d76faf7acf3ed056036%1774365463.87",
|
||||
"m4/1.4.19#4523e4347b55cd26ae918bd5770cab9a%1778062762.471",
|
||||
"cmake/4.3.0#b939a42e98f593fb34d3a8c5cc860359%1774439249.183",
|
||||
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1774439233.447",
|
||||
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
|
||||
@@ -50,13 +48,13 @@
|
||||
"lz4/1.10.0"
|
||||
],
|
||||
"boost/[>=1.83.0 <1.91.0]": [
|
||||
"boost/1.90.0"
|
||||
"boost/1.91.0"
|
||||
],
|
||||
"sqlite3/[>=3.44 <4]": [
|
||||
"sqlite3/3.51.0"
|
||||
"sqlite3/3.53.0"
|
||||
],
|
||||
"boost/1.83.0": [
|
||||
"boost/1.90.0"
|
||||
"boost/1.91.0"
|
||||
],
|
||||
"lz4/[>=1.9.4 <2]": [
|
||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504"
|
||||
|
||||
@@ -3,3 +3,5 @@
|
||||
core:non_interactive=True
|
||||
core.download:parallel={{ os.cpu_count() }}
|
||||
core.upload:parallel={{ os.cpu_count() }}
|
||||
tools.files.download:retry=5
|
||||
tools.files.download:retry_wait=10
|
||||
|
||||
@@ -1 +1 @@
|
||||
include(sanitizers)
|
||||
include(sanitizers)
|
||||
|
||||
@@ -1,86 +1,122 @@
|
||||
include(default)
|
||||
{% set compiler, version, compiler_exe = detect_api.detect_default_compiler() %}
|
||||
{% set arch = detect_api.detect_arch() %}
|
||||
{% set sanitizers = os.getenv("SANITIZERS") %}
|
||||
|
||||
[conf]
|
||||
{% if sanitizers %}
|
||||
{% if compiler == "gcc" %}
|
||||
{% if "address" in sanitizers or "thread" in sanitizers or "undefinedbehavior" in sanitizers %}
|
||||
{% set sanitizer_list = [] %}
|
||||
{% set defines = [] %}
|
||||
{% set model_code = "" %}
|
||||
{% set extra_cxxflags = ["-fno-omit-frame-pointer", "-O1", "-Wno-stringop-overflow"] %}
|
||||
{% if not sanitizers %}
|
||||
{# Sanitizers not configured; no additional settings needed #}
|
||||
{% else %}
|
||||
|
||||
{% if "address" in sanitizers %}
|
||||
{% set _ = sanitizer_list.append("address") %}
|
||||
{% set model_code = "-mcmodel=large" %}
|
||||
{% set _ = defines.append("BOOST_USE_ASAN")%}
|
||||
{% set _ = defines.append("BOOST_USE_UCONTEXT")%}
|
||||
{% elif "thread" in sanitizers %}
|
||||
{% set _ = sanitizer_list.append("thread") %}
|
||||
{% set model_code = "-mcmodel=medium" %}
|
||||
{% set _ = extra_cxxflags.append("-Wno-tsan") %}
|
||||
{% set _ = defines.append("BOOST_USE_TSAN")%}
|
||||
{% set _ = defines.append("BOOST_USE_UCONTEXT")%}
|
||||
{% endif %}
|
||||
{% if compiler == "msvc" %}
|
||||
{{ "Sanitizers are not supported on Windows/MSVC. Please unset the SANITIZERS environment variable." }}
|
||||
{% endif %}
|
||||
|
||||
{% if "undefinedbehavior" in sanitizers %}
|
||||
{% set _ = sanitizer_list.append("undefined") %}
|
||||
{% set _ = sanitizer_list.append("float-divide-by-zero") %}
|
||||
{% endif %}
|
||||
{% set known_sanitizers = ["address", "thread", "undefinedbehavior"] %}
|
||||
{% set provided_sanitizers = [] %}
|
||||
{% for san in sanitizers.split(",") %}
|
||||
{% set san = san.strip() %}
|
||||
{% if san not in known_sanitizers %}
|
||||
{{ "Unknown sanitizer in SANITIZERS: " ~ san }}
|
||||
{% endif %}
|
||||
{% set _ = provided_sanitizers.append(san) %}
|
||||
{% endfor %}
|
||||
|
||||
{% set sanitizer_flags = "-fsanitize=" ~ ",".join(sanitizer_list) ~ " " ~ model_code %}
|
||||
{% set enable_asan = "address" in provided_sanitizers %}
|
||||
{% set enable_tsan = "thread" in provided_sanitizers %}
|
||||
{% set enable_ubsan = "undefinedbehavior" in provided_sanitizers %}
|
||||
|
||||
tools.build:cxxflags+=['{{sanitizer_flags}} {{" ".join(extra_cxxflags)}}']
|
||||
tools.build:sharedlinkflags+=['{{sanitizer_flags}}']
|
||||
tools.build:exelinkflags+=['{{sanitizer_flags}}']
|
||||
tools.build:defines+={{defines}}
|
||||
{% endif %}
|
||||
{% elif compiler == "apple-clang" or compiler == "clang" %}
|
||||
{% if "address" in sanitizers or "thread" in sanitizers or "undefinedbehavior" in sanitizers %}
|
||||
{% set sanitizer_list = [] %}
|
||||
{% set defines = [] %}
|
||||
{% set extra_cxxflags = ["-fno-omit-frame-pointer", "-O1"] %}
|
||||
{% if enable_asan and enable_tsan %}
|
||||
{{ "AddressSanitizer and ThreadSanitizer are incompatible and cannot be enabled simultaneously." }}
|
||||
{% endif %}
|
||||
|
||||
{% if "address" in sanitizers %}
|
||||
{% set _ = sanitizer_list.append("address") %}
|
||||
{% set _ = defines.append("BOOST_USE_ASAN")%}
|
||||
{% set _ = defines.append("BOOST_USE_UCONTEXT")%}
|
||||
{% elif "thread" in sanitizers %}
|
||||
{% set _ = sanitizer_list.append("thread") %}
|
||||
{% set _ = defines.append("BOOST_USE_TSAN")%}
|
||||
{% set _ = defines.append("BOOST_USE_UCONTEXT")%}
|
||||
{% endif %}
|
||||
{% set sanitizer_types = [] %}
|
||||
{% set defines = [] %}
|
||||
|
||||
{% if "undefinedbehavior" in sanitizers %}
|
||||
{% set _ = sanitizer_list.append("undefined") %}
|
||||
{% set _ = sanitizer_list.append("float-divide-by-zero") %}
|
||||
{% set _ = sanitizer_list.append("unsigned-integer-overflow") %}
|
||||
{% endif %}
|
||||
{% if enable_asan %}
|
||||
{% set _ = sanitizer_types.append("address") %}
|
||||
{% set _ = defines.append("BOOST_USE_ASAN") %}
|
||||
{% set _ = defines.append("BOOST_USE_UCONTEXT") %}
|
||||
{% elif enable_tsan %}
|
||||
{% set _ = sanitizer_types.append("thread") %}
|
||||
{% set _ = defines.append("BOOST_USE_TSAN") %}
|
||||
{% set _ = defines.append("BOOST_USE_UCONTEXT") %}
|
||||
{% endif %}
|
||||
|
||||
{% set sanitizer_flags = "-fsanitize=" ~ ",".join(sanitizer_list) %}
|
||||
|
||||
tools.build:cxxflags+=['{{sanitizer_flags}} {{" ".join(extra_cxxflags)}}']
|
||||
tools.build:sharedlinkflags+=['{{sanitizer_flags}}']
|
||||
tools.build:exelinkflags+=['{{sanitizer_flags}}']
|
||||
tools.build:defines+={{defines}}
|
||||
{% endif %}
|
||||
{% if enable_ubsan %}
|
||||
{% set _ = sanitizer_types.append("undefined") %}
|
||||
{% set _ = sanitizer_types.append("float-divide-by-zero") %}
|
||||
{# Clang supports additional UB checks beyond the GCC baseline #}
|
||||
{% if compiler == "clang" or compiler == "apple-clang" %}
|
||||
{% set _ = sanitizer_types.append("unsigned-integer-overflow") %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{# Frame pointer required for meaningful stack traces; -O1 for reasonable performance #}
|
||||
{% set compile_flags = ["-fno-omit-frame-pointer", "-O1"] %}
|
||||
|
||||
{% if compiler == "gcc" %}
|
||||
{# Suppress false positive warnings with GCC #}
|
||||
{% set _ = compile_flags.append("-Wno-stringop-overflow") %}
|
||||
|
||||
{% set relocation_flags = [] %}
|
||||
|
||||
{% if arch == "x86_64" and enable_asan %}
|
||||
{# Large code model prevents relocation errors in instrumented ASAN binaries #}
|
||||
{% set _ = compile_flags.append("-mcmodel=large") %}
|
||||
{% set _ = relocation_flags.append("-mcmodel=large") %}
|
||||
{% elif enable_tsan %}
|
||||
{# GCC doesn't support atomic_thread_fence with TSAN; suppress warnings #}
|
||||
{% set _ = compile_flags.append("-Wno-tsan") %}
|
||||
{% if arch == "x86_64" %}
|
||||
{# Medium code model for TSAN; large is incompatible #}
|
||||
{% set _ = compile_flags.append("-mcmodel=medium") %}
|
||||
{% set _ = relocation_flags.append("-mcmodel=medium") %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% set fsanitize = "-fsanitize=" ~ ",".join(sanitizer_types) %}
|
||||
{% set _ = compile_flags.append(fsanitize) %}
|
||||
{% set _ = relocation_flags.append(fsanitize) %}
|
||||
|
||||
{% set sanitizer_compiler_flags = " ".join(compile_flags) %}
|
||||
{% set sanitizer_linker_flags = " ".join(relocation_flags) %}
|
||||
{% elif compiler == "clang" or compiler == "apple-clang" %}
|
||||
{% set fsanitize = "-fsanitize=" ~ ",".join(sanitizer_types) %}
|
||||
{% set _ = compile_flags.append(fsanitize) %}
|
||||
|
||||
{% set sanitizer_compiler_flags = " ".join(compile_flags) %}
|
||||
{% set sanitizer_linker_flags = fsanitize %}
|
||||
{% endif %}
|
||||
|
||||
[conf]
|
||||
tools.build:defines+={{defines}}
|
||||
tools.build:cxxflags+=['{{sanitizer_compiler_flags}}']
|
||||
tools.build:sharedlinkflags+=['{{sanitizer_linker_flags}}']
|
||||
tools.build:exelinkflags+=['{{sanitizer_linker_flags}}']
|
||||
|
||||
tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"]
|
||||
|
||||
# &: means "apply only to the consumer/root package"
|
||||
&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{sanitizers}}", "SANITIZERS_COMPILER_FLAGS": "{{sanitizer_compiler_flags}}", "SANITIZERS_LINKER_FLAGS": "{{sanitizer_linker_flags}}"}
|
||||
|
||||
[options]
|
||||
{% if sanitizers %}
|
||||
{% if "address" in sanitizers %}
|
||||
# Build Boost.Context with ucontext backend (not fcontext) so that
|
||||
# ASAN fiber-switching annotations (__sanitizer_start/finish_switch_fiber)
|
||||
# are compiled into the library. fcontext (assembly) has no ASAN support.
|
||||
# define=BOOST_USE_ASAN=1 is critical: it must be defined when building
|
||||
# Boost.Context itself so the ucontext backend compiles in the ASAN annotations.
|
||||
boost/*:extra_b2_flags=context-impl=ucontext address-sanitizer=on define=BOOST_USE_ASAN=1
|
||||
boost/*:without_context=False
|
||||
# Boost stacktrace fails to build with some sanitizers
|
||||
boost/*:without_stacktrace=True
|
||||
{% endif %}
|
||||
{% if enable_asan %}
|
||||
# Build Boost.Context with ucontext backend (not fcontext) so that
|
||||
# ASAN fiber-switching annotations (__sanitizer_start/finish_switch_fiber)
|
||||
# are compiled into the library. fcontext (assembly) has no ASAN support.
|
||||
# define=BOOST_USE_ASAN=1 is critical: it must be defined when building
|
||||
# Boost.Context itself so the ucontext backend compiles in the ASAN annotations.
|
||||
boost/*:extra_b2_flags=context-impl=ucontext address-sanitizer=on define=BOOST_USE_ASAN=1
|
||||
boost/*:without_context=False
|
||||
# Boost stacktrace fails to build with some sanitizers
|
||||
boost/*:without_stacktrace=True
|
||||
{% elif enable_tsan %}
|
||||
# Build Boost.Context with ucontext backend for TSAN. fcontext (assembly)
|
||||
# has no TSAN annotations, so without this the BOOST_USE_TSAN/BOOST_USE_UCONTEXT
|
||||
# defines in [conf] would be ineffective.
|
||||
boost/*:extra_b2_flags=context-impl=ucontext thread-sanitizer=on define=BOOST_USE_TSAN=1
|
||||
boost/*:without_context=False
|
||||
boost/*:without_stacktrace=True
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
23
conanfile.py
23
conanfile.py
@@ -1,4 +1,3 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
@@ -30,13 +29,12 @@ class Xrpl(ConanFile):
|
||||
requires = [
|
||||
"ed25519/2015.03",
|
||||
"grpc/1.78.1",
|
||||
"libarchive/3.8.1",
|
||||
"libarchive/3.8.7",
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.6.1",
|
||||
"openssl/3.6.2",
|
||||
"secp256k1/0.7.1",
|
||||
"soci/4.0.3",
|
||||
"spdlog/1.17.0",
|
||||
"zlib/1.3.1",
|
||||
"zlib/1.3.2",
|
||||
]
|
||||
|
||||
test_requires = [
|
||||
@@ -58,6 +56,7 @@ class Xrpl(ConanFile):
|
||||
"tests": False,
|
||||
"unity": False,
|
||||
"xrpld": False,
|
||||
"boost/*:without_cobalt": True,
|
||||
"boost/*:without_context": False,
|
||||
"boost/*:without_coroutine": True,
|
||||
"boost/*:without_coroutine2": False,
|
||||
@@ -110,7 +109,6 @@ class Xrpl(ConanFile):
|
||||
"secp256k1/*:shared": False,
|
||||
"snappy/*:shared": False,
|
||||
"soci/*:shared": False,
|
||||
"spdlog/*:shared": False,
|
||||
"soci/*:with_sqlite3": True,
|
||||
"soci/*:with_boost": True,
|
||||
"xxhash/*:shared": False,
|
||||
@@ -131,20 +129,14 @@ class Xrpl(ConanFile):
|
||||
if self.settings.compiler in ["clang", "gcc"]:
|
||||
self.options["boost"].without_cobalt = True
|
||||
|
||||
# Check if environment variable exists
|
||||
if "SANITIZERS" in os.environ:
|
||||
sanitizers = os.environ["SANITIZERS"]
|
||||
if "address" in sanitizers.lower():
|
||||
self.default_options["fPIC"] = False
|
||||
|
||||
def requirements(self):
|
||||
self.requires("boost/1.90.0", force=True, transitive_headers=True)
|
||||
self.requires("boost/1.91.0", force=True, transitive_headers=True)
|
||||
self.requires("date/3.0.4", transitive_headers=True)
|
||||
self.requires("lz4/1.10.0", force=True)
|
||||
self.requires("protobuf/6.33.5", force=True)
|
||||
self.requires("sqlite3/3.51.0", force=True)
|
||||
self.requires("sqlite3/3.53.0", force=True)
|
||||
if self.options.jemalloc:
|
||||
self.requires("jemalloc/5.3.0")
|
||||
self.requires("jemalloc/5.3.1")
|
||||
if self.options.rocksdb:
|
||||
self.requires("rocksdb/10.5.1")
|
||||
self.requires("xxhash/0.8.3", transitive_headers=True)
|
||||
@@ -221,7 +213,6 @@ class Xrpl(ConanFile):
|
||||
"protobuf::libprotobuf",
|
||||
"soci::soci",
|
||||
"secp256k1::secp256k1",
|
||||
"spdlog::spdlog",
|
||||
"sqlite3::sqlite",
|
||||
"xxhash::xxhash",
|
||||
"zlib::zlib",
|
||||
|
||||
@@ -63,6 +63,7 @@ words:
|
||||
- Bougalis
|
||||
- Britto
|
||||
- Btrfs
|
||||
- Buildx
|
||||
- canonicality
|
||||
- changespq
|
||||
- checkme
|
||||
@@ -71,6 +72,7 @@ words:
|
||||
- citardauq
|
||||
- clawback
|
||||
- clawbacks
|
||||
- cmaketoolchain
|
||||
- coeffs
|
||||
- coldwallet
|
||||
- compr
|
||||
@@ -97,12 +99,15 @@ words:
|
||||
- desync
|
||||
- desynced
|
||||
- determ
|
||||
- disablerepo
|
||||
- distro
|
||||
- doxyfile
|
||||
- dxrpl
|
||||
- enabled
|
||||
- enablerepo
|
||||
- endmacro
|
||||
- exceptioned
|
||||
- EXPECT_STREQ
|
||||
- Falco
|
||||
- fcontext
|
||||
- finalizers
|
||||
@@ -114,6 +119,7 @@ words:
|
||||
- gcovr
|
||||
- ghead
|
||||
- Gnutella
|
||||
- godexsoft
|
||||
- gpgcheck
|
||||
- gpgkey
|
||||
- hotwallet
|
||||
@@ -151,6 +157,7 @@ words:
|
||||
- lseq
|
||||
- lsmf
|
||||
- ltype
|
||||
- mathbunnyru
|
||||
- mcmodel
|
||||
- MEMORYSTATUSEX
|
||||
- MPTAMM
|
||||
@@ -158,6 +165,7 @@ words:
|
||||
- Merkle
|
||||
- Metafuncton
|
||||
- misprediction
|
||||
- missingok
|
||||
- mptbalance
|
||||
- MPTDEX
|
||||
- mptflags
|
||||
@@ -189,7 +197,9 @@ words:
|
||||
- NOLINT
|
||||
- NOLINTNEXTLINE
|
||||
- nonxrp
|
||||
- noreplace
|
||||
- noripple
|
||||
- notifempty
|
||||
- nudb
|
||||
- nullptr
|
||||
- nunl
|
||||
@@ -209,6 +219,7 @@ words:
|
||||
- preauthorize
|
||||
- preauthorizes
|
||||
- preclaim
|
||||
- preun
|
||||
- protobuf
|
||||
- protos
|
||||
- ptrs
|
||||
@@ -243,12 +254,14 @@ words:
|
||||
- sfields
|
||||
- shamap
|
||||
- shamapitem
|
||||
- shlibs
|
||||
- sidechain
|
||||
- SIGGOOD
|
||||
- sle
|
||||
- sles
|
||||
- soci
|
||||
- socidb
|
||||
- SRPMS
|
||||
- sslws
|
||||
- statsd
|
||||
- STATSDCOLLECTOR
|
||||
@@ -276,8 +289,8 @@ words:
|
||||
- txn
|
||||
- txns
|
||||
- txs
|
||||
- UBSAN
|
||||
- ubsan
|
||||
- UBSAN
|
||||
- umant
|
||||
- unacquired
|
||||
- unambiguity
|
||||
@@ -314,7 +327,6 @@ words:
|
||||
- xbridge
|
||||
- xchain
|
||||
- ximinez
|
||||
- EXPECT_STREQ
|
||||
- XMACRO
|
||||
- xrpkuwait
|
||||
- xrpl
|
||||
@@ -322,3 +334,4 @@ words:
|
||||
- xrplf
|
||||
- xxhash
|
||||
- xxhasher
|
||||
- CGNAT
|
||||
|
||||
66
docker/nix.Dockerfile
Normal file
66
docker/nix.Dockerfile
Normal file
@@ -0,0 +1,66 @@
|
||||
ARG BASE_IMAGE=nixos/nix:latest
|
||||
|
||||
# Nix builder
|
||||
FROM nixos/nix:latest AS builder-source
|
||||
|
||||
RUN mkdir -p ~/.config/nix && \
|
||||
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
|
||||
|
||||
# Copy our source and setup our working dir.
|
||||
COPY nix/ci-env.nix /tmp/build/nix/ci-env.nix
|
||||
COPY nix/packages.nix /tmp/build/nix/packages.nix
|
||||
COPY nix/utils.nix /tmp/build/nix/utils.nix
|
||||
COPY flake.nix /tmp/build/
|
||||
COPY flake.lock /tmp/build/
|
||||
WORKDIR /tmp/build
|
||||
|
||||
FROM builder-source AS builder
|
||||
|
||||
# Build our Nix CI environment (all build tools in a single store path)
|
||||
RUN nix \
|
||||
--option filter-syscalls false \
|
||||
build
|
||||
|
||||
# Copy the Nix store closure into a directory. The Nix store closure is the
|
||||
# entire set of Nix store values that we need for our build.
|
||||
RUN mkdir /tmp/nix-store-closure && \
|
||||
cp -R $(nix-store -qR result/) /tmp/nix-store-closure
|
||||
|
||||
# Final image
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
# bash is not located at /bin/bash in nixos/nix, so we need to create a symlink to it.
|
||||
RUN if [ -d /nix ]; then \
|
||||
ln -s /root/.nix-profile/bin/bash /bin/bash; \
|
||||
fi
|
||||
|
||||
# Use Bash as the default shell for RUN commands, using the options
|
||||
# `set -o errexit -o pipefail`, and as the entrypoint.
|
||||
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
||||
ENTRYPOINT ["/bin/bash"]
|
||||
|
||||
# Copy /nix/store and the env symlink tree
|
||||
COPY --from=builder /tmp/nix-store-closure /nix/store
|
||||
COPY --from=builder /tmp/build/result /nix/ci-env
|
||||
|
||||
ENV PATH="/nix/ci-env/bin:$PATH"
|
||||
|
||||
RUN <<EOF
|
||||
ccache --version
|
||||
clang-format --version
|
||||
cmake --version
|
||||
conan --version
|
||||
g++ --version
|
||||
gcc --version
|
||||
gcovr --version
|
||||
git --version
|
||||
make --version
|
||||
mold --version
|
||||
ninja --version
|
||||
perl --version
|
||||
pkg-config --version
|
||||
pre-commit --version
|
||||
python3 --version
|
||||
run-clang-tidy --help
|
||||
vim --version
|
||||
EOF
|
||||
47
docs/build/sanitizers.md
vendored
47
docs/build/sanitizers.md
vendored
@@ -1,15 +1,17 @@
|
||||
# Sanitizer Configuration for Xrpld
|
||||
|
||||
This document explains how to properly configure and run sanitizers (AddressSanitizer, undefinedbehaviorSanitizer, ThreadSanitizer) with the xrpld project.
|
||||
This document explains how to properly configure and run sanitizers (`AddressSanitizer`, `UndefinedBehaviorSanitizer`, `ThreadSanitizer`) with the xrpld project.
|
||||
Corresponding suppression files are located in the `sanitizers/suppressions` directory.
|
||||
|
||||
> [!CAUTION]
|
||||
> Do not mix Address and Thread sanitizers - they are incompatible.
|
||||
> Also, we don't yet support MSVC sanitizers, so this is only for Clang/GCC builds.
|
||||
|
||||
- [Sanitizer Configuration for Xrpld](#sanitizer-configuration-for-xrpld)
|
||||
- [Building with Sanitizers](#building-with-sanitizers)
|
||||
- [Summary](#summary)
|
||||
- [Build steps:](#build-steps)
|
||||
- [Install dependencies](#install-dependencies)
|
||||
- [Call CMake](#call-cmake)
|
||||
- [Build](#build)
|
||||
- [Running Tests with Sanitizers](#running-tests-with-sanitizers)
|
||||
- [AddressSanitizer (ASAN)](#addresssanitizer-asan)
|
||||
- [ThreadSanitizer (TSan)](#threadsanitizer-tsan)
|
||||
@@ -33,9 +35,13 @@ Corresponding suppression files are located in the `sanitizers/suppressions` dir
|
||||
Follow the same instructions as mentioned in [BUILD.md](../../BUILD.md) but with the following changes:
|
||||
|
||||
1. Make sure you have a clean build directory.
|
||||
2. Set the `SANITIZERS` environment variable before calling conan install and cmake. Only set it once. Make sure both conan and cmake read the same values.
|
||||
2. Set the `SANITIZERS` environment variable before calling `conan install`. Only set it once.
|
||||
Example: `export SANITIZERS=address,undefinedbehavior`
|
||||
3. Optionally use `--profile:all sanitizers` with Conan to build dependencies with sanitizer instrumentation. [!NOTE]Building with sanitizer-instrumented dependencies is slower but produces fewer false positives.
|
||||
3. Use `--profile:all sanitizers` with Conan to build dependencies with sanitizer instrumentation.
|
||||
|
||||
> [!NOTE]
|
||||
> Building with sanitizer-instrumented dependencies is slower but produces fewer false positives.
|
||||
|
||||
4. Set `ASAN_OPTIONS`, `LSAN_OPTIONS`, `UBSAN_OPTIONS` and `TSAN_OPTIONS` environment variables to configure sanitizer behavior when running executables. [More details below](#running-tests-with-sanitizers).
|
||||
|
||||
---
|
||||
@@ -51,36 +57,13 @@ cd .build
|
||||
|
||||
#### Install dependencies
|
||||
|
||||
The `SANITIZERS` environment variable is used by both Conan and CMake.
|
||||
The `SANITIZERS` environment variable is used during `conan install` command.
|
||||
|
||||
```bash
|
||||
export SANITIZERS=address,undefinedbehavior
|
||||
# Standard build (without instrumenting dependencies)
|
||||
conan install .. --output-folder . --build missing --settings build_type=Debug
|
||||
|
||||
# Or with sanitizer-instrumented dependencies (takes longer but fewer false positives)
|
||||
conan install .. --output-folder . --profile:all sanitizers --build missing --settings build_type=Debug
|
||||
SANITIZERS=address,undefinedbehavior conan install .. --output-folder . --build missing --settings build_type=Debug --profile:all sanitizers
|
||||
```
|
||||
|
||||
[!CAUTION]
|
||||
Do not mix Address and Thread sanitizers - they are incompatible.
|
||||
|
||||
Since you already set the `SANITIZERS` environment variable when running Conan, same values will be read for the next part.
|
||||
|
||||
#### Call CMake
|
||||
|
||||
```bash
|
||||
cmake .. -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-Dtests=ON -Dxrpld=ON
|
||||
```
|
||||
|
||||
#### Build
|
||||
|
||||
```bash
|
||||
cmake --build . --parallel 4
|
||||
```
|
||||
Proceed with the rest of the build instructions as mentioned in [BUILD.md](../../BUILD.md).
|
||||
|
||||
## Running Tests with Sanitizers
|
||||
|
||||
@@ -98,7 +81,7 @@ export LSAN_OPTIONS="include=sanitizers/suppressions/runtime-lsan-options.txt:su
|
||||
|
||||
**Why `detect_container_overflow=0`?**
|
||||
|
||||
- Boost intrusive containers (used in `aged_unordered_container`) trigger false positives
|
||||
- Boost intrusive containers (used in `AgedUnorderedContainer`) trigger false positives
|
||||
- Boost context switching (used in `Workers.cpp`) confuses ASAN's stack tracking
|
||||
- Since we usually don't build Boost (because we don't want to instrument Boost and detect issues in Boost code) with ASAN but use Boost containers in ASAN instrumented xrpld code, it generates false positives.
|
||||
- Building dependencies with ASAN instrumentation reduces false positives. But we don't want to instrument dependencies like Boost with ASAN because it is slow (to compile as well as run tests) and not necessary.
|
||||
|
||||
@@ -477,7 +477,7 @@ struct Ledger
|
||||
// The parent ledger's close time
|
||||
NetClock::time_point parentCloseTime() const;
|
||||
|
||||
Json::Value getJson() const;
|
||||
json::Value getJson() const;
|
||||
|
||||
//... implementation specific
|
||||
};
|
||||
|
||||
26
flake.lock
generated
26
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1769461804,
|
||||
"narHash": "sha256-6h5sROT/3CTHvzPy9koKBmoCa2eJKh4fzQK8eYFEgl8=",
|
||||
"lastModified": 1777954456,
|
||||
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b579d443b37c9c5373044201ea77604e37e748c8",
|
||||
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -15,9 +15,27 @@
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs-glibc231": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1593520194,
|
||||
"narHash": "sha256-+TZW+2I7kLL9JglPNOagm1ywjf9ua0JYGoptq/dzVn0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9cd98386a38891d1074fc18036b842dc4416f562",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9cd98386a38891d1074fc18036b842dc4416f562",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-glibc231": "nixpkgs-glibc231"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
13
flake.nix
13
flake.nix
@@ -2,15 +2,24 @@
|
||||
description = "Nix related things for xrpld";
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
# nixpkgs snapshot (2020-06-30) that shipped glibc 2.31 as the primary
|
||||
# version — matches the system libc on Ubuntu 20.04 LTS. Imported
|
||||
# manually (flake = false) because this revision predates nixpkgs'
|
||||
# own flake.nix.
|
||||
nixpkgs-glibc231 = {
|
||||
url = "github:NixOS/nixpkgs/9cd98386a38891d1074fc18036b842dc4416f562";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ nixpkgs, ... }:
|
||||
{ nixpkgs, nixpkgs-glibc231, ... }:
|
||||
let
|
||||
forEachSystem = (import ./nix/utils.nix { inherit nixpkgs; }).forEachSystem;
|
||||
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-glibc231; };
|
||||
in
|
||||
{
|
||||
devShells = forEachSystem (import ./nix/devshell.nix);
|
||||
packages = forEachSystem (import ./nix/ci-env.nix);
|
||||
formatter = forEachSystem ({ pkgs, ... }: pkgs.nixfmt);
|
||||
};
|
||||
}
|
||||
|
||||
32
include/xrpl/basics/Archive.h.ai.json
Normal file
32
include/xrpl/basics/Archive.h.ai.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 13,
|
||||
"name": "src"
|
||||
},
|
||||
{
|
||||
"lineno": 13,
|
||||
"name": "dst"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Header file declaring a function to extract a tar archive compressed with lz4 using Boost Filesystem, within the xrpl namespace.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Archive.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"src",
|
||||
"dst"
|
||||
],
|
||||
"lineno": 13,
|
||||
"name": "extractTarLz4"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 4,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
31
include/xrpl/basics/Archive.h.ai.md
Normal file
31
include/xrpl/basics/Archive.h.ai.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# `Archive.h` — Tar/LZ4 Archive Extraction
|
||||
|
||||
This header declares a single utility function within the `xrpl` namespace: `extractTarLz4`. Its purpose is narrowly scoped — providing the XRPL node software with the ability to unpack `.tar.lz4` archives to a target directory at runtime. The most natural use case is ledger database bootstrapping, where a node downloads a pre-built snapshot of the ledger state rather than replaying the entire transaction history from genesis.
|
||||
|
||||
## The Interface
|
||||
|
||||
```cpp
|
||||
void extractTarLz4(
|
||||
boost::filesystem::path const& src,
|
||||
boost::filesystem::path const& dst);
|
||||
```
|
||||
|
||||
Both parameters are `boost::filesystem::path` rather than `std::string` or `std::filesystem::path`. This is consistent with the broader `xrpl/basics` module (see `FileUtilities.h`), which predates C++17's standard filesystem library and relies on Boost.Filesystem throughout. The function throws `std::runtime_error` on any failure — there is no return value to check or error code to inspect.
|
||||
|
||||
## Implementation Design
|
||||
|
||||
The implementation in `Archive.cpp` delegates all archive I/O to **libarchive**, a portable C library (`<archive.h>`, `<archive_entry.h>`). This is a deliberate choice over rolling a custom tar/lz4 parser: libarchive handles format detection, streaming decompression, and sparse file support in a well-tested, security-audited way.
|
||||
|
||||
Resource management for the two libarchive handles — a reader (`ar`) and a disk writer (`aw`) — is handled via `std::unique_ptr` with custom deleters that call `archive_read_free` and `archive_write_free` respectively. This is the only safe pattern here: libarchive resources must be released even when intermediate steps throw, and wrapping them in `unique_ptr` ensures cleanup happens automatically as the stack unwinds.
|
||||
|
||||
The reader is configured explicitly for the tar format and the lz4 filter (rather than using libarchive's auto-detection). This prevents the function from silently accepting other archive formats, keeping the interface contract tight. The file is opened with a 10240-byte block size, which matches the canonical recommendation in libarchive documentation.
|
||||
|
||||
The disk writer is configured with `ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS`, meaning extracted files faithfully preserve timestamps, permissions, access control lists, and BSD file flags from the archive. For a snapshot intended to be a drop-in replacement for a live ledger database directory, this fidelity matters: the consuming software may rely on mtime or permission bits being intact.
|
||||
|
||||
A non-obvious detail is the pathname rewriting on line 65: before writing each entry to disk, the function prepends `dst` to the entry's stored path using Boost.Filesystem's `/` operator. This is what places all extracted content under `dst` rather than at absolute paths embedded in the archive, and it prevents path traversal issues where a maliciously constructed archive might attempt to write files outside the intended directory tree.
|
||||
|
||||
## Error Handling
|
||||
|
||||
All errors are surfaced through `xrpl::Throw<std::runtime_error>`, defined in `contract.h`. Unlike a raw `throw`, `Throw` first calls `LogThrow` to capture a stack trace before the exception propagates. This means extraction failures produce actionable diagnostics in the node's log — important for diagnosing corrupted snapshots or filesystem problems during a bootstrap operation that might otherwise appear as a silent crash.
|
||||
|
||||
The function validates `src` is a regular file (not a directory or symlink) before opening it, providing a clear early error rather than letting libarchive fail with a less informative message.
|
||||
@@ -27,7 +27,7 @@ private:
|
||||
std::unordered_map<std::string, std::string> lookup_;
|
||||
std::vector<std::string> lines_;
|
||||
std::vector<std::string> values_;
|
||||
bool had_trailing_comments_ = false;
|
||||
bool hadTrailingComments_ = false;
|
||||
|
||||
using const_iterator = decltype(lookup_)::const_iterator;
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
explicit Section(std::string name = "");
|
||||
|
||||
/** Returns the name of this section. */
|
||||
std::string const&
|
||||
[[nodiscard]] std::string const&
|
||||
name() const
|
||||
{
|
||||
return name_;
|
||||
@@ -45,7 +45,7 @@ public:
|
||||
/** Returns all the lines in the section.
|
||||
This includes everything.
|
||||
*/
|
||||
std::vector<std::string> const&
|
||||
[[nodiscard]] std::vector<std::string> const&
|
||||
lines() const
|
||||
{
|
||||
return lines_;
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
/** Returns all the values in the section.
|
||||
Values are non-empty lines which are not key/value pairs.
|
||||
*/
|
||||
std::vector<std::string> const&
|
||||
[[nodiscard]] std::vector<std::string> const&
|
||||
values() const
|
||||
{
|
||||
return values_;
|
||||
@@ -82,7 +82,7 @@ public:
|
||||
* @return The retrieved value. A section with an empty legacy value returns
|
||||
an empty string.
|
||||
*/
|
||||
std::string
|
||||
[[nodiscard]] std::string
|
||||
legacy() const
|
||||
{
|
||||
if (lines_.empty())
|
||||
@@ -117,11 +117,11 @@ public:
|
||||
}
|
||||
|
||||
/** Returns `true` if a key with the given name exists. */
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
exists(std::string const& name) const;
|
||||
|
||||
template <class T = std::string>
|
||||
std::optional<T>
|
||||
[[nodiscard]] std::optional<T>
|
||||
get(std::string const& name) const
|
||||
{
|
||||
auto const iter = lookup_.find(name);
|
||||
@@ -132,8 +132,8 @@ public:
|
||||
|
||||
/// Returns a value if present, else another value.
|
||||
template <class T>
|
||||
T
|
||||
value_or(std::string const& name, T const& other) const
|
||||
[[nodiscard]] T
|
||||
valueOr(std::string const& name, T const& other) const
|
||||
{
|
||||
auto const v = get<T>(name);
|
||||
return v.has_value() ? *v : other;
|
||||
@@ -141,52 +141,52 @@ public:
|
||||
|
||||
// indicates if trailing comments were seen
|
||||
// during the appending of any lines/values
|
||||
bool
|
||||
had_trailing_comments() const
|
||||
[[nodiscard]] bool
|
||||
hadTrailingComments() const
|
||||
{
|
||||
return had_trailing_comments_;
|
||||
return hadTrailingComments_;
|
||||
}
|
||||
|
||||
friend std::ostream&
|
||||
operator<<(std::ostream&, Section const& section);
|
||||
|
||||
// Returns `true` if there are no key/value pairs.
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return lookup_.empty();
|
||||
}
|
||||
|
||||
// Returns the number of key/value pairs.
|
||||
std::size_t
|
||||
[[nodiscard]] std::size_t
|
||||
size() const
|
||||
{
|
||||
return lookup_.size();
|
||||
}
|
||||
|
||||
// For iteration of key/value pairs.
|
||||
const_iterator
|
||||
[[nodiscard]] const_iterator
|
||||
begin() const
|
||||
{
|
||||
return lookup_.cbegin();
|
||||
}
|
||||
|
||||
// For iteration of key/value pairs.
|
||||
const_iterator
|
||||
[[nodiscard]] const_iterator
|
||||
cbegin() const
|
||||
{
|
||||
return lookup_.cbegin();
|
||||
}
|
||||
|
||||
// For iteration of key/value pairs.
|
||||
const_iterator
|
||||
[[nodiscard]] const_iterator
|
||||
end() const
|
||||
{
|
||||
return lookup_.cend();
|
||||
}
|
||||
|
||||
// For iteration of key/value pairs.
|
||||
const_iterator
|
||||
[[nodiscard]] const_iterator
|
||||
cend() const
|
||||
{
|
||||
return lookup_.cend();
|
||||
@@ -206,7 +206,7 @@ private:
|
||||
|
||||
public:
|
||||
/** Returns `true` if a section with the given name exists. */
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
exists(std::string const& name) const;
|
||||
|
||||
/** Returns the section with the given name.
|
||||
@@ -216,7 +216,7 @@ public:
|
||||
Section&
|
||||
section(std::string const& name);
|
||||
|
||||
Section const&
|
||||
[[nodiscard]] Section const&
|
||||
section(std::string const& name) const;
|
||||
|
||||
Section const&
|
||||
@@ -264,7 +264,7 @@ public:
|
||||
* legacy value.
|
||||
* @return Contents of the legacy value.
|
||||
*/
|
||||
std::string
|
||||
[[nodiscard]] std::string
|
||||
legacy(std::string const& sectionName) const;
|
||||
|
||||
friend std::ostream&
|
||||
@@ -272,10 +272,10 @@ public:
|
||||
|
||||
// indicates if trailing comments were seen
|
||||
// in any loaded Sections
|
||||
bool
|
||||
had_trailing_comments() const
|
||||
[[nodiscard]] bool
|
||||
hadTrailingComments() const
|
||||
{
|
||||
return std::ranges::any_of(map_, [](auto s) { return s.second.had_trailing_comments(); });
|
||||
return std::ranges::any_of(map_, [](auto s) { return s.second.hadTrailingComments(); });
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -294,17 +294,17 @@ template <class T>
|
||||
bool
|
||||
set(T& target, std::string const& name, Section const& section)
|
||||
{
|
||||
bool found_and_valid = false;
|
||||
bool foundAndValid = false;
|
||||
try
|
||||
{
|
||||
auto const val = section.get<T>(name);
|
||||
if ((found_and_valid = val.has_value()))
|
||||
if ((foundAndValid = val.has_value()))
|
||||
target = *val;
|
||||
}
|
||||
catch (boost::bad_lexical_cast const&) // NOLINT(bugprone-empty-catch)
|
||||
{
|
||||
}
|
||||
return found_and_valid;
|
||||
return foundAndValid;
|
||||
}
|
||||
|
||||
/** Set a value from a configuration Section
|
||||
@@ -316,10 +316,10 @@ template <class T>
|
||||
bool
|
||||
set(T& target, T const& defaultValue, std::string const& name, Section const& section)
|
||||
{
|
||||
bool const found_and_valid = set<T>(target, name, section);
|
||||
if (!found_and_valid)
|
||||
bool const foundAndValid = set<T>(target, name, section);
|
||||
if (!foundAndValid)
|
||||
target = defaultValue;
|
||||
return found_and_valid;
|
||||
return foundAndValid;
|
||||
}
|
||||
|
||||
/** Retrieve a key/value pair from a section.
|
||||
@@ -333,7 +333,7 @@ get(Section const& section, std::string const& name, T const& defaultValue = T{}
|
||||
{
|
||||
try
|
||||
{
|
||||
return section.value_or<T>(name, defaultValue);
|
||||
return section.valueOr<T>(name, defaultValue);
|
||||
}
|
||||
catch (boost::bad_lexical_cast const&) // NOLINT(bugprone-empty-catch)
|
||||
{
|
||||
@@ -358,17 +358,17 @@ get(Section const& section, std::string const& name, char const* defaultValue)
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
get_if_exists(Section const& section, std::string const& name, T& v)
|
||||
getIfExists(Section const& section, std::string const& name, T& v)
|
||||
{
|
||||
return set<T>(v, name, section);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool
|
||||
get_if_exists<bool>(Section const& section, std::string const& name, bool& v)
|
||||
getIfExists<bool>(Section const& section, std::string const& name, bool& v)
|
||||
{
|
||||
int intVal = 0;
|
||||
auto stat = get_if_exists(section, name, intVal);
|
||||
auto stat = getIfExists(section, name, intVal);
|
||||
if (stat)
|
||||
v = bool(intVal);
|
||||
return stat;
|
||||
|
||||
324
include/xrpl/basics/BasicConfig.h.ai.json
Normal file
324
include/xrpl/basics/BasicConfig.h.ai.json
Normal file
@@ -0,0 +1,324 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 15,
|
||||
"name": "name"
|
||||
},
|
||||
{
|
||||
"lineno": 0,
|
||||
"name": "src"
|
||||
},
|
||||
{
|
||||
"lineno": 0,
|
||||
"name": "dst"
|
||||
},
|
||||
{
|
||||
"lineno": 45,
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"lineno": 66,
|
||||
"name": "key"
|
||||
},
|
||||
{
|
||||
"lineno": 72,
|
||||
"name": "lines"
|
||||
},
|
||||
{
|
||||
"lineno": 78,
|
||||
"name": "line"
|
||||
},
|
||||
{
|
||||
"lineno": 94,
|
||||
"name": "other"
|
||||
},
|
||||
{
|
||||
"lineno": 156,
|
||||
"name": "section"
|
||||
},
|
||||
{
|
||||
"lineno": 193,
|
||||
"name": "sectionName"
|
||||
},
|
||||
{
|
||||
"lineno": 211,
|
||||
"name": "ifs"
|
||||
},
|
||||
{
|
||||
"lineno": 220,
|
||||
"name": "target"
|
||||
},
|
||||
{
|
||||
"lineno": 235,
|
||||
"name": "defaultValue"
|
||||
},
|
||||
{
|
||||
"lineno": 272,
|
||||
"name": "v"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 13,
|
||||
"name": "Section"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 140,
|
||||
"name": "BasicConfig"
|
||||
}
|
||||
],
|
||||
"description": "Defines classes and utility functions for handling configuration sections and key/value pairs, including parsing, storing, and retrieving configuration data for the xrpl project.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/BasicConfig.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 23,
|
||||
"name": "Section::name"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 30,
|
||||
"name": "Section::lines"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 37,
|
||||
"name": "Section::values"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"lineno": 44,
|
||||
"name": "Section::legacy"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 53,
|
||||
"name": "Section::legacy"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"key",
|
||||
"value"
|
||||
],
|
||||
"lineno": 65,
|
||||
"name": "Section::set"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"lines"
|
||||
],
|
||||
"lineno": 71,
|
||||
"name": "Section::append"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"line"
|
||||
],
|
||||
"lineno": 77,
|
||||
"name": "Section::append"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 82,
|
||||
"name": "Section::exists"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 85,
|
||||
"name": "Section::get"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name",
|
||||
"other"
|
||||
],
|
||||
"lineno": 93,
|
||||
"name": "Section::value_or"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 101,
|
||||
"name": "Section::had_trailing_comments"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 110,
|
||||
"name": "Section::empty"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 115,
|
||||
"name": "Section::size"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 120,
|
||||
"name": "Section::begin"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 125,
|
||||
"name": "Section::cbegin"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 130,
|
||||
"name": "Section::end"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 135,
|
||||
"name": "Section::cend"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 151,
|
||||
"name": "BasicConfig::exists"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 155,
|
||||
"name": "BasicConfig::section"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 158,
|
||||
"name": "BasicConfig::section"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 161,
|
||||
"name": "BasicConfig::operator[]"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 165,
|
||||
"name": "BasicConfig::operator[]"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"key",
|
||||
"value"
|
||||
],
|
||||
"lineno": 171,
|
||||
"name": "BasicConfig::overwrite"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section"
|
||||
],
|
||||
"lineno": 176,
|
||||
"name": "BasicConfig::deprecatedClearSection"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"value"
|
||||
],
|
||||
"lineno": 183,
|
||||
"name": "BasicConfig::legacy"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"sectionName"
|
||||
],
|
||||
"lineno": 192,
|
||||
"name": "BasicConfig::legacy"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 201,
|
||||
"name": "BasicConfig::had_trailing_comments"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"ifs"
|
||||
],
|
||||
"lineno": 210,
|
||||
"name": "BasicConfig::build"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"target",
|
||||
"name",
|
||||
"section"
|
||||
],
|
||||
"lineno": 219,
|
||||
"name": "set"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"target",
|
||||
"defaultValue",
|
||||
"name",
|
||||
"section"
|
||||
],
|
||||
"lineno": 234,
|
||||
"name": "set"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"name",
|
||||
"defaultValue"
|
||||
],
|
||||
"lineno": 247,
|
||||
"name": "get"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"name",
|
||||
"defaultValue"
|
||||
],
|
||||
"lineno": 260,
|
||||
"name": "get"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"name",
|
||||
"v"
|
||||
],
|
||||
"lineno": 271,
|
||||
"name": "get_if_exists"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"name",
|
||||
"v"
|
||||
],
|
||||
"lineno": 277,
|
||||
"name": "get_if_exists<bool>"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 10,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
44
include/xrpl/basics/BasicConfig.h.ai.md
Normal file
44
include/xrpl/basics/BasicConfig.h.ai.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# `BasicConfig.h` — INI-Style Configuration Substrate
|
||||
|
||||
`BasicConfig.h` defines the foundational data model for the XRPL node's configuration system. It sits at the bottom of a two-layer design: this file provides the in-memory representation and query interface for section-based configuration data, while the concrete `Config` class (in `src/xrpld/core/Config.h`) inherits from `BasicConfig` and adds filesystem loading, application-specific typed fields, and validator management. The header comment on `Config` explicitly labels that derived class as deprecated, signaling that `BasicConfig`'s style — decentralized, per-module parsing — is the intended long-term direction.
|
||||
|
||||
## Data Model: Two Representations in One `Section`
|
||||
|
||||
The `Section` class maintains three parallel containers for the same underlying config content:
|
||||
|
||||
- `lookup_` — an `unordered_map<string, string>` for `key = value` pairs, used for named lookups
|
||||
- `values_` — a `vector<string>` of non-key-value lines (bare tokens like IP addresses or file paths)
|
||||
- `lines_` — a `vector<string>` containing every non-empty, non-comment line in canonical form
|
||||
|
||||
This triple storage isn't redundancy — it reflects the two distinct ways config sections are used in practice. Sections like `[server]` contain key=value pairs consumed by name; sections like `[validators]` contain bare values (one per line) iterated as a list. The `lines()` accessor preserves insertion order, which matters for list-type sections where positional meaning exists.
|
||||
|
||||
The `append()` method is where parsing happens. It applies a Boost regex matching `^key=value` to each incoming line. Lines that match go into `lookup_` via `set()`; non-matching lines go into `values_`. Both go into `lines_`. The same method also handles inline comment stripping: `#` characters are treated as comment delimiters unless escaped with `\`. The escape character is consumed when found (`val.erase(comment - 1, 1)`), allowing literal `#` characters in values. This detail is tracked via `had_trailing_comments_`, which bubbles up through `BasicConfig::had_trailing_comments()` via `std::any_of` — presumably to emit a deprecation warning to operators about ambiguous config syntax.
|
||||
|
||||
## The "Legacy" Pattern
|
||||
|
||||
Some older config sections hold a single freeform value rather than key-value pairs — for example `[node_db]` in its pre-structured form. The `legacy()` getter/setter pair accommodates this by treating the first entry of `lines_` as the canonical value. Reading a `Section` as legacy on a multi-line section intentionally throws `std::runtime_error` via `Throw<>()`, enforcing that this access path is only valid for single-line sections. This prevents silent misreads where code expecting one value silently gets only the first of many.
|
||||
|
||||
`BasicConfig` also exposes `legacy()` at the aggregate level, forwarding to the named section's `legacy()`. This provides `config.legacy("section_name")` as a convenience for the many legacy callsites in `Config.cpp`.
|
||||
|
||||
## `BasicConfig`: Container and Access Protocol
|
||||
|
||||
`BasicConfig` holds an `unordered_map<string, Section>`, keyed by section name. The critical behavioral difference between the const and non-const `section()` overloads reflects a deliberate design choice:
|
||||
|
||||
- Non-const `section()` calls `map_.emplace(name, name)` — it auto-creates an empty section on first access. This allows callers to unconditionally call `config["new_section"].set(...)` without precondition checks.
|
||||
- Const `section()` returns a reference to a `static Section const none("")` sentinel when the section doesn't exist. This avoids exceptions during read-only configuration queries and makes `operator[]` safe to call on a const `BasicConfig` even for absent sections.
|
||||
|
||||
The `overwrite()` method is specifically for command-line argument injection, layering CLI-provided values over whatever the config file contains. `deprecatedClearSection()` (name signals intent) wipes a section's content by replacing its `Section` object wholesale — used historically to clear sections before reloading.
|
||||
|
||||
The `build()` method is `protected`, not `public`. It consumes an `IniFileSections` (a `unordered_map<string, vector<string>>`), which is the raw pre-parsed form produced by `parseIniFile()` in `Config.cpp`. Subclasses call `build()` after obtaining this intermediate representation, keeping the file I/O and INI parsing out of `BasicConfig` itself.
|
||||
|
||||
## Free Function Query Layer
|
||||
|
||||
The file exports three sets of free functions designed for module-level configuration consumption:
|
||||
|
||||
`set(target, name, section)` reads a named key, casts it via `boost::lexical_cast<T>`, and assigns to `target` only on success — leaving `target` unchanged on missing key or bad cast. The two-argument variant adds an explicit default value applied on failure. Both return `bool` indicating whether the config file actually specified the value, which is important for distinguishing "user set this to the default" from "user didn't set this."
|
||||
|
||||
`get(section, name, defaultValue)` is a value-returning variant; it catches `bad_lexical_cast` and falls back to the default silently. An overload handles `char const*` defaults to avoid awkward template deduction with string literals.
|
||||
|
||||
`get_if_exists<bool>` is explicitly specialized to read boolean config values as integers (`0` or `1`) rather than as the string tokens `"true"` or `"false"`. This matches the XRPL config file convention where booleans are expressed numerically, and avoids `lexical_cast<bool>` which in Boost accepts `"true"` but not `"1"` depending on locale.
|
||||
|
||||
Together these three free functions provide a consistent, exception-safe pattern that modules throughout the codebase use to pull typed values from their respective config sections without having to handle parse failures individually.
|
||||
14
include/xrpl/basics/Blob.h.ai.json
Normal file
14
include/xrpl/basics/Blob.h.ai.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"args": [],
|
||||
"classes": [],
|
||||
"description": "Defines a type alias 'Blob' for storing linear binary data as a vector of unsigned char within the xrpl namespace.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Blob.h",
|
||||
"functions": [],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 4,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
17
include/xrpl/basics/Blob.h.ai.md
Normal file
17
include/xrpl/basics/Blob.h.ai.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# `include/xrpl/basics/Blob.h`
|
||||
|
||||
`Blob.h` introduces a single named type alias used throughout the XRPL codebase for owning, mutable binary data:
|
||||
|
||||
```cpp
|
||||
using Blob = std::vector<unsigned char>;
|
||||
```
|
||||
|
||||
Its role is to give raw byte sequences a meaningful, searchable name rather than leaving `std::vector<unsigned char>` scattered as an anonymous type across the protocol and serialization layers. `Blob` appears as the internal storage buffer inside `Serializer` (`mData`), as the return type of serialization helpers, and in `StringUtilities` for hex encoding and SQL blob literals.
|
||||
|
||||
`Blob` sits at one corner of the three-type binary data model in `xrpl::basics`:
|
||||
|
||||
- **`Blob`** (`std::vector<unsigned char>`) — mutable, dynamically resizable, owns its memory. The right choice when data is built up incrementally, as in `Serializer`.
|
||||
- **`Buffer`** — fixed-size block allocated with `unique_ptr<uint8_t[]>`, no capacity overhead, suitable when size is known upfront and resizing is not required.
|
||||
- **`Slice`** — a non-owning, read-only `(pointer, length)` view. Cheap to copy and pass; `makeSlice()` factory overloads accept both `Blob` and `Buffer` seamlessly.
|
||||
|
||||
The choice of `unsigned char` rather than `char` is deliberate: it avoids signed/unsigned arithmetic warnings when working with raw binary values and aligns with the `uint8_t` element type used by `Slice` and `Buffer`. Because `Blob` is simply a `std::vector`, callers get the full standard iterator interface, `push_back`, `resize`, and range-insert without any additional wrapper API.
|
||||
@@ -101,13 +101,13 @@ public:
|
||||
}
|
||||
|
||||
/** Returns the number of bytes in the buffer. */
|
||||
std::size_t
|
||||
[[nodiscard]] std::size_t
|
||||
size() const noexcept
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
empty() const noexcept
|
||||
{
|
||||
return 0 == size_;
|
||||
@@ -125,7 +125,7 @@ public:
|
||||
to a single byte, to facilitate pointer arithmetic.
|
||||
*/
|
||||
/** @{ */
|
||||
std::uint8_t const*
|
||||
[[nodiscard]] std::uint8_t const*
|
||||
data() const noexcept
|
||||
{
|
||||
return p_.get();
|
||||
@@ -169,25 +169,25 @@ public:
|
||||
return alloc(n);
|
||||
}
|
||||
|
||||
const_iterator
|
||||
[[nodiscard]] const_iterator
|
||||
begin() const noexcept
|
||||
{
|
||||
return p_.get();
|
||||
}
|
||||
|
||||
const_iterator
|
||||
[[nodiscard]] const_iterator
|
||||
cbegin() const noexcept
|
||||
{
|
||||
return p_.get();
|
||||
}
|
||||
|
||||
const_iterator
|
||||
[[nodiscard]] const_iterator
|
||||
end() const noexcept
|
||||
{
|
||||
return p_.get() + size_;
|
||||
}
|
||||
|
||||
const_iterator
|
||||
[[nodiscard]] const_iterator
|
||||
cend() const noexcept
|
||||
{
|
||||
return p_.get() + size_;
|
||||
|
||||
42
include/xrpl/basics/Buffer.h.ai.json
Normal file
42
include/xrpl/basics/Buffer.h.ai.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"args": [],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"size",
|
||||
"data",
|
||||
"other",
|
||||
"s"
|
||||
],
|
||||
"lineno": 10,
|
||||
"name": "Buffer"
|
||||
}
|
||||
],
|
||||
"description": "Defines a Buffer class for managing dynamic byte arrays, similar to std::vector<char> but optimized for use as a BufferFactory, including copy/move semantics, assignment from slices, and comparison operators.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Buffer.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"lhs",
|
||||
"rhs"
|
||||
],
|
||||
"lineno": 120,
|
||||
"name": "operator=="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"lhs",
|
||||
"rhs"
|
||||
],
|
||||
"lineno": 130,
|
||||
"name": "operator!="
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 7,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
35
include/xrpl/basics/Buffer.h.ai.md
Normal file
35
include/xrpl/basics/Buffer.h.ai.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# `include/xrpl/basics/Buffer.h`
|
||||
|
||||
## Role in the System
|
||||
|
||||
`Buffer` is the XRPL codebase's canonical owning byte container. It occupies a distinct position alongside two other byte-handling types: `Slice`, which is a non-owning, immutable view into existing memory, and `Blob` (a typedef for `std::vector<unsigned char>`), which is a general-purpose growable sequence. `Buffer` fills the gap between these: it owns its memory exclusively, is mutable, but makes no provision for incremental growth. When you need to allocate a block of bytes, write into it once, and pass it around by move, `Buffer` is the right tool.
|
||||
|
||||
The class also satisfies an informal `BufferFactory` concept used by compression utilities — a callable that accepts a size and returns a `void*` to writable memory. This dual role as both a container and an allocator-callback is the most distinctive design choice in the file.
|
||||
|
||||
## Ownership and Internal Layout
|
||||
|
||||
The backing store is a `std::unique_ptr<std::uint8_t[]>`, giving the class clear exclusive ownership with automatic deallocation. The invariant enforced throughout is that an empty buffer (`size_ == 0`) always holds a null pointer — never a zero-byte allocation. This is visible in the size constructor: `new std::uint8_t[size]` is called only when `size` is non-zero, and `alloc()` resets to `nullptr` if `n == 0`. The test suite verifies this invariant explicitly via its `sane()` helper, which asserts `data() == nullptr` iff `empty()`. Treating null as the canonical empty state avoids any ambiguity at the call site and makes zero-initialization checks safe without checking both pointer and size.
|
||||
|
||||
## The `alloc()` Pattern — Discard, Don't Resize
|
||||
|
||||
The central API difference from `std::vector` is `alloc(std::size_t n)`, which reallocates the buffer to exactly `n` bytes and discards any existing content. Unlike `vector::resize()`, there is no attempt to preserve data. This is intentional: the primary workload for `Buffer` is receiving output from operations like decompression, where the caller pre-computes the required size and wants a fresh block to write into. Reallocation is skipped entirely if the requested size equals the current size, avoiding a pointless free/alloc cycle when the same `Buffer` is reused across calls of equal output length.
|
||||
|
||||
The `operator()(std::size_t n)` overload simply delegates to `alloc()` and returns a `void*`, satisfying the `BufferFactory` concept expected by `lz4Compress` in `CompressionAlgorithms.h`. That template function calls `bf(outCapacity)` to obtain the destination buffer — passing a `Buffer` object directly fills both roles (allocation and storage) in a single object.
|
||||
|
||||
## Slice Integration
|
||||
|
||||
`Buffer` is tightly coupled to `Slice`. It provides an implicit conversion `operator Slice() const noexcept`, so any `Buffer` can be passed wherever a `Slice` is expected without an explicit cast. The reverse — constructing a `Buffer` from a `Slice` — is marked `explicit`, preventing accidental copies of view-only data.
|
||||
|
||||
The `operator=(Slice)` assignment requires particular attention: before copying, it checks via `XRPL_ASSERT` that the source slice does not overlap with the `Buffer`'s own storage. The danger is that `alloc()` frees the old memory first, and if the incoming `Slice` pointed into that memory, the subsequent `memcpy` would be a use-after-free. The assertion guards against this specific self-overlapping scenario. Note that `operator=(Buffer const&)` uses a different path through `alloc()` + `memcpy`, which naturally handles self-assignment because `alloc()` is a no-op when sizes match — the existing pointer is reused and then `memcpy`-d over itself (which is defined behavior for `memcpy` with identical source and destination).
|
||||
|
||||
## Move Semantics
|
||||
|
||||
Both move constructor and move assignment are `noexcept`, a static guarantee the test suite verifies with `static_assert`. This ensures `Buffer` can be held in standard containers like `std::vector` without triggering copies on reallocation. After a move, the source is left in a valid empty state: `p_` is null (via `unique_ptr` move semantics) and `size_` is explicitly reset to zero.
|
||||
|
||||
## Comparison and Iteration
|
||||
|
||||
Equality comparison is implemented as a free function using `std::memcmp` after a size check. The class exposes only `const_iterator` (raw `uint8_t const*` pointers), meaning range-for loops and standard algorithms can consume the buffer's contents read-only. Mutable iteration is available only through `data()`, keeping the interface honest about the distinction between reading and writing into the buffer.
|
||||
|
||||
## Contrast with `Blob`
|
||||
|
||||
`Blob` (`std::vector<unsigned char>`) is still used extensively in the codebase for cases where the byte sequence grows incrementally, such as serialization output. `Buffer` is preferred when the size is known upfront, ownership transfer by move is the primary operation, or the `BufferFactory` pattern is required — for example, storing the output of an LZ4 decompression call without needing the capacity/size distinction that `vector` maintains internally.
|
||||
38
include/xrpl/basics/ByteUtilities.h.ai.json
Normal file
38
include/xrpl/basics/ByteUtilities.h.ai.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"lineno": 13,
|
||||
"name": "value"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Provides constexpr utility functions to convert values to kilobytes and megabytes within the xrpl namespace.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/ByteUtilities.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"lineno": 6,
|
||||
"name": "kilobytes"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"lineno": 13,
|
||||
"name": "megabytes"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 3,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
26
include/xrpl/basics/ByteUtilities.h.ai.md
Normal file
26
include/xrpl/basics/ByteUtilities.h.ai.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# `ByteUtilities.h` — Compile-Time Byte-Size Helpers
|
||||
|
||||
`ByteUtilities.h` is a minimal, header-only utility in `xrpl/basics` that provides two `constexpr` template functions — `kilobytes()` and `megabytes()` — for expressing byte-count constants in human-readable units at compile time. The file exists purely to eliminate magic numbers from sites that configure buffer sizes, memory limits, and slab allocator parameters throughout the XRPL codebase.
|
||||
|
||||
## The Functions
|
||||
|
||||
`kilobytes(value)` multiplies its argument by 1024. `megabytes(value)` composes that twice — it calls `kilobytes(kilobytes(value))` — which gives the correct factor of 1,048,576 (2²⁰) without any separate literal. Both functions are templated on `T`, so they work with any integral or arithmetic type and return the same type that the arithmetic produces, letting the caller's type context drive the result without an explicit cast. Both are `constexpr` and `noexcept`, meaning the computation happens entirely at compile time and has no runtime overhead whatsoever.
|
||||
|
||||
The `static_assert` lines immediately below the definitions act as inline tests: they verify `kilobytes(2) == 2048` and `megabytes(3) == 3145728` during every compilation, preventing any silent regression if the implementation were ever accidentally changed.
|
||||
|
||||
## Design Rationale
|
||||
|
||||
The template design over a fixed `size_t` signature is deliberate. Call sites like `megabytes(std::size_t(60))` in `SHAMapItem.h` need to produce `std::size_t` results for slab allocator configuration, while other uses such as `megabytes(256)` in `RPCCall.cpp` are happy with `int`-width results for comparison. By letting `auto` return the natural result of the arithmetic, the functions avoid both unwanted narrowing conversions and unwanted widening that could paper over a type mismatch.
|
||||
|
||||
The composition `kilobytes(kilobytes(value))` for megabytes is a small but telling choice: it reuses the already-tested primitive rather than independently writing `value * 1024 * 1024`, keeping the chain of trust short and making the relationship between units self-documenting.
|
||||
|
||||
## Usage Across the Codebase
|
||||
|
||||
The functions appear at exactly the kinds of boundaries where misreading a magnitude would have serious consequences:
|
||||
|
||||
- **Overlay message cap**: `src/xrpld/overlay/Message.h` defines `constexpr std::size_t maximumMessageSize = megabytes(64)`, bounding the maximum peer-to-peer message size to 64 MiB.
|
||||
- **RPC reply limit**: `src/xrpld/rpc/detail/RPCCall.cpp` defines `constexpr auto RPC_REPLY_MAX_BYTES = megabytes(256)` to guard against unbounded JSON responses.
|
||||
- **Ledger and open-view buffers**: `include/xrpl/ledger/OpenView.h` and `include/xrpl/ledger/detail/RawStateTable.h` both set `initialBufferSize = kilobytes(256)` for their serialisation scratch buffers.
|
||||
- **ShaMap slab allocator**: `SHAMapItem.h` uses `megabytes()` to express the per-size-class allocation limits for the slab allocator pools (60 MB, 46 MB, etc.), and `TaggedPointer.ipp` uses `kilobytes(512)` for the slab block granularity.
|
||||
|
||||
The consistent use of these helpers rather than raw literals means that anyone reading any of those files immediately understands the intended scale without mental arithmetic, and the compiler catches any integer overflow that a bare literal might hide at the point of definition.
|
||||
93
include/xrpl/basics/CompressionAlgorithms.h.ai.json
Normal file
93
include/xrpl/basics/CompressionAlgorithms.h.ai.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 18,
|
||||
"name": "in"
|
||||
},
|
||||
{
|
||||
"lineno": 18,
|
||||
"name": "inSize"
|
||||
},
|
||||
{
|
||||
"lineno": 18,
|
||||
"name": "bf"
|
||||
},
|
||||
{
|
||||
"lineno": 41,
|
||||
"name": "in"
|
||||
},
|
||||
{
|
||||
"lineno": 41,
|
||||
"name": "inSizeUnchecked"
|
||||
},
|
||||
{
|
||||
"lineno": 41,
|
||||
"name": "decompressed"
|
||||
},
|
||||
{
|
||||
"lineno": 41,
|
||||
"name": "decompressedSizeUnchecked"
|
||||
},
|
||||
{
|
||||
"lineno": 62,
|
||||
"name": "in"
|
||||
},
|
||||
{
|
||||
"lineno": 62,
|
||||
"name": "inSize"
|
||||
},
|
||||
{
|
||||
"lineno": 62,
|
||||
"name": "decompressed"
|
||||
},
|
||||
{
|
||||
"lineno": 62,
|
||||
"name": "decompressedSize"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Provides LZ4 block compression and decompression utilities, including template and inline functions for compressing and decompressing data buffers and streams within the xrpl::compression_algorithms namespace.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/CompressionAlgorithms.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"in",
|
||||
"inSize",
|
||||
"bf"
|
||||
],
|
||||
"lineno": 18,
|
||||
"name": "lz4Compress"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"in",
|
||||
"inSizeUnchecked",
|
||||
"decompressed",
|
||||
"decompressedSizeUnchecked"
|
||||
],
|
||||
"lineno": 41,
|
||||
"name": "lz4Decompress"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"in",
|
||||
"inSize",
|
||||
"decompressed",
|
||||
"decompressedSize"
|
||||
],
|
||||
"lineno": 62,
|
||||
"name": "lz4Decompress"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 9,
|
||||
"name": "xrpl"
|
||||
},
|
||||
{
|
||||
"lineno": 11,
|
||||
"name": "compression_algorithms"
|
||||
}
|
||||
]
|
||||
}
|
||||
53
include/xrpl/basics/CompressionAlgorithms.h.ai.md
Normal file
53
include/xrpl/basics/CompressionAlgorithms.h.ai.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# `CompressionAlgorithms.h` — LZ4 Block Compression Primitives
|
||||
|
||||
This header lives in `include/xrpl/basics/` and provides the low-level LZ4 compression and decompression routines used by the XRPL peer overlay network. It sits one abstraction layer below `src/xrpld/overlay/Compression.h`, which adds algorithm-selection logic and error suppression on top of what this file exposes.
|
||||
|
||||
## Architectural Role
|
||||
|
||||
When XRPL nodes exchange P2P messages they can optionally compress the payload before transmission. The overlay layer negotiates compression during the connection handshake and then routes compressed messages through the functions defined here. `CompressionAlgorithms.h` isolates the raw LZ4 calls — the `int`-based C API hazards, buffer management, and stream chunking — from the policy-level decisions that live in `Compression.h`.
|
||||
|
||||
The functions are entirely in the `xrpl::compression_algorithms` namespace. There are no classes, no state, no singletons — just three free functions.
|
||||
|
||||
## `lz4Compress` — Template with BufferFactory
|
||||
|
||||
```cpp
|
||||
template <typename BufferFactory>
|
||||
std::size_t lz4Compress(void const* in, std::size_t inSize, BufferFactory&& bf)
|
||||
```
|
||||
|
||||
The design choice to accept a `BufferFactory` callable rather than returning a `std::vector` is deliberate and important. The caller knows its allocation context: in the overlay code it may be writing into a Protobuf `CodedOutputStream` region or a pooled buffer. The factory receives the worst-case compressed size from `LZ4_compressBound` and returns a raw pointer; the template accepts any callable that satisfies this contract without virtual dispatch overhead.
|
||||
|
||||
The sole pre-condition check guards against input larger than `UINT32_MAX`. LZ4's block API uses `int` internally, so exceeding that limit would silently truncate the size argument. The function throws via `Throw<std::runtime_error>`, which logs a call stack through `contract.h` before throwing — consistent with XRPL's "crash loudly with context" philosophy for invariant violations.
|
||||
|
||||
## `lz4Decompress` — Raw Buffer Overload
|
||||
|
||||
```cpp
|
||||
inline std::size_t lz4Decompress(
|
||||
std::uint8_t const* in, std::size_t inSizeUnchecked,
|
||||
std::uint8_t* decompressed, std::size_t decompressedSizeUnchecked)
|
||||
```
|
||||
|
||||
The `Unchecked` naming in the parameters is the code's way of signalling that the `size_t` → `int` narrowing has not yet been validated. The function immediately casts both sizes to `int` and checks for `<= 0`. This catches two distinct failure modes: a genuinely zero-length buffer, and a `size_t` value large enough that the narrowing wrap produces a non-positive `int`. Separating these checks with distinct error messages makes debugging easier.
|
||||
|
||||
`LZ4_decompress_safe` is used rather than the faster `LZ4_decompress_fast`. The safe variant takes the output buffer capacity as a bound and will not write past it even if the compressed data is malformed — essential when the input arrives from an untrusted peer on the network.
|
||||
|
||||
The function enforces an exact-size postcondition: if `LZ4_decompress_safe` returns anything other than the expected `decompressedSize` it throws. This reflects the fact that, in the overlay protocol, the original message size is transmitted in the message header; any mismatch means either corruption or a peer bug.
|
||||
|
||||
## `lz4Decompress` — Streaming ZeroCopyInputStream Overload
|
||||
|
||||
```cpp
|
||||
template <typename InputStream>
|
||||
std::size_t lz4Decompress(
|
||||
InputStream& in, std::size_t inSize,
|
||||
std::uint8_t* decompressed, std::size_t decompressedSize)
|
||||
```
|
||||
|
||||
This overload works with Protobuf-style `ZeroCopyInputStream` objects that expose data as a series of chunks rather than a single contiguous buffer. The key optimization is the fast path: if the very first chunk returned by `in.Next()` is at least `inSize` bytes long, the function uses that chunk's pointer directly and avoids any allocation. In practice, compressed P2P messages typically arrive in a single TCP read buffer, so this path is taken most of the time.
|
||||
|
||||
When the data spans multiple chunks, the function lazily allocates a `std::vector<std::uint8_t>` of exactly `inSize` bytes (note the `compressed.resize(inSize)` is only reached on the second iteration) and copies chunks into it until the full compressed message is assembled. After reading, any bytes that were consumed from the stream beyond `inSize` are returned via `in.BackUp()`, preserving the stream cursor for the next message in the framing protocol.
|
||||
|
||||
The final validation before delegating to the raw overload checks that the amount actually read matches what was requested. This guards against a stream that ends early — e.g., a truncated TCP connection or a framing bug where the declared size doesn't match the available data.
|
||||
|
||||
## Relationship to `Compression.h`
|
||||
|
||||
The overlay's `Compression.h` wraps these two functions inside `compress()` and `decompress()` functions that add an `Algorithm` enum parameter (currently `Algorithm::LZ4 = 0x90` or `Algorithm::None`). Those wrappers catch all exceptions from the functions here and return `0` on failure, converting the throw-on-error contract into a return-zero-on-error contract. The distinction is intentional: the raw primitives throw so that callers who want structured error handling can use them; the overlay wrapper normalises failures to a `0` return value to simplify the state machine in the peer message processing loop.
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
using Entry = std::pair<std::string, int>;
|
||||
using List = std::vector<Entry>;
|
||||
|
||||
List
|
||||
[[nodiscard]] List
|
||||
getCounts(int minimumThreshold) const;
|
||||
|
||||
public:
|
||||
@@ -38,11 +38,11 @@ public:
|
||||
|
||||
do
|
||||
{
|
||||
head = instance.m_head.load();
|
||||
head = instance.head_.load();
|
||||
next_ = head;
|
||||
} while (instance.m_head.exchange(this) != head);
|
||||
} while (instance.head_.exchange(this) != head);
|
||||
|
||||
++instance.m_count;
|
||||
++instance.count_;
|
||||
}
|
||||
|
||||
~Counter() noexcept = default;
|
||||
@@ -59,19 +59,19 @@ public:
|
||||
return --count_;
|
||||
}
|
||||
|
||||
int
|
||||
[[nodiscard]] int
|
||||
getCount() const noexcept
|
||||
{
|
||||
return count_.load();
|
||||
}
|
||||
|
||||
Counter*
|
||||
[[nodiscard]] Counter*
|
||||
getNext() const noexcept
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
[[nodiscard]] std::string const&
|
||||
getName() const noexcept
|
||||
{
|
||||
return name_;
|
||||
@@ -88,8 +88,8 @@ private:
|
||||
~CountedObjects() noexcept = default;
|
||||
|
||||
private:
|
||||
std::atomic<int> m_count;
|
||||
std::atomic<Counter*> m_head;
|
||||
std::atomic<int> count_;
|
||||
std::atomic<Counter*> head_;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -108,8 +108,8 @@ private:
|
||||
static auto&
|
||||
getCounter() noexcept
|
||||
{
|
||||
static CountedObjects::Counter c{beast::type_name<Object>()};
|
||||
return c;
|
||||
static CountedObjects::Counter kC{beast::typeName<Object>()};
|
||||
return kC;
|
||||
}
|
||||
|
||||
CountedObject() noexcept
|
||||
|
||||
88
include/xrpl/basics/CountedObject.h.ai.json
Normal file
88
include/xrpl/basics/CountedObject.h.ai.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 23,
|
||||
"name": "name"
|
||||
},
|
||||
{
|
||||
"lineno": 16,
|
||||
"name": "minimumThreshold"
|
||||
},
|
||||
{
|
||||
"lineno": 65,
|
||||
"name": "Object"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 7,
|
||||
"name": "CountedObjects"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 22,
|
||||
"name": "Counter"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 65,
|
||||
"name": "CountedObject"
|
||||
}
|
||||
],
|
||||
"description": "Provides a mechanism to count and report the number of instances of various object types at runtime, using a lock-free linked list and atomic counters. Includes a base class for automatic instance counting.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/CountedObject.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 10,
|
||||
"name": "getInstance"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"minimumThreshold"
|
||||
],
|
||||
"lineno": 16,
|
||||
"name": "getCounts"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 36,
|
||||
"name": "increment"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 41,
|
||||
"name": "decrement"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 46,
|
||||
"name": "getCount"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 51,
|
||||
"name": "getNext"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 56,
|
||||
"name": "getName"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 71,
|
||||
"name": "getCounter"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
55
include/xrpl/basics/CountedObject.h.ai.md
Normal file
55
include/xrpl/basics/CountedObject.h.ai.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# `include/xrpl/basics/CountedObject.h`
|
||||
|
||||
## Purpose
|
||||
|
||||
This header provides a zero-per-instance-overhead mechanism for counting live objects of any given type throughout the rippled process lifetime. It exists for operational diagnostics: the `get_counts` admin RPC command interrogates `CountedObjects` to report how many instances of each tracked type are currently alive, helping operators identify memory growth, cache saturation, or unexpected object accumulation.
|
||||
|
||||
## Design Pattern — CRTP Instance Counting
|
||||
|
||||
The design uses the Curiously Recurring Template Pattern (CRTP). A class opts into counting by inheriting `CountedObject<Derived>`:
|
||||
|
||||
```cpp
|
||||
class SHAMapItem : public CountedObject<SHAMapItem> { ... };
|
||||
class NodeObject : public CountedObject<NodeObject> { ... };
|
||||
class Job : public CountedObject<Job> { ... };
|
||||
```
|
||||
|
||||
Across the codebase, roughly two dozen types follow this pattern — `STPathElement`, `STPath`, `InfoSub`, `HashRouter::Entry`, `Book`, `CanonicalTXSet`, and many more. Adding the base class is the entire integration cost; no other instrumentation is required.
|
||||
|
||||
The key insight that makes this zero-per-instance overhead is that `CountedObject<T>::getCounter()` returns a **function-local static** `Counter` object — one per template instantiation, not one per live instance. The only per-instance cost is two atomic increments (constructor and destructor) touching a shared counter.
|
||||
|
||||
## Three-Layer Architecture
|
||||
|
||||
**`CountedObject<T>`** (template base class) — the public-facing layer. Its default constructor, copy constructor, and destructor call `getCounter().increment()` / `decrement()` respectively. The copy constructor is explicitly defined to increment because a copy produces a new live object; the assignment operator is `= default` because assigning between two existing objects doesn't change the total number of live instances. There is no explicit move constructor, so moves fall back to the copy constructor, which correctly increments for the new object while the source's destructor later decrements for the old one.
|
||||
|
||||
**`CountedObjects::Counter`** (inner class) — the per-type bookkeeping node. Each `Counter` holds its type name (obtained via `beast::type_name<T>()`, which uses `typeid` plus GCC/Clang ABI demangling for a human-readable string), an `std::atomic<int>` live count, and a raw `Counter*` pointer to the next node in an intrusive singly-linked list.
|
||||
|
||||
**`CountedObjects`** (singleton) — the global registry. It owns the head of the lock-free linked list and a count of registered counter types.
|
||||
|
||||
## Lock-Free Registration
|
||||
|
||||
`Counter` objects self-register when they are first constructed — which happens at first use of any given type, during static initialization of `getCounter()`'s local static. Registration must be thread-safe without a mutex, because many types can be instantiated concurrently at startup:
|
||||
|
||||
```cpp
|
||||
Counter* head = nullptr;
|
||||
do {
|
||||
head = instance.m_head.load();
|
||||
next_ = head;
|
||||
} while (instance.m_head.exchange(this) != head);
|
||||
```
|
||||
|
||||
This is a classic CAS (compare-and-swap) insertion loop: load the current head, set `next_` to it, then atomically exchange the head with `this`. If the head changed between the load and the exchange, retry. Because `Counter` objects are permanent (static lifetime), they are never removed from the list, so traversal during `getCounts()` never encounters a dangling pointer regardless of whether other registrations are happening concurrently.
|
||||
|
||||
## `getCounts()` and the Reporting Path
|
||||
|
||||
`CountedObjects::getCounts(int minimumThreshold)` traverses the linked list and collects `(name, count)` pairs for any type whose live count is at or above the threshold. It pre-reserves the result vector using `m_count.load()` as a hint (the comment in the implementation acknowledges this can be temporarily under-counted under concurrency — it is only an optimization). The results are sorted alphabetically before return.
|
||||
|
||||
The `get_counts` admin RPC handler (`GetCounts.cpp`) calls this with a configurable `min_count` (defaulting to 10) and serializes the results into a JSON object, mixing them with cache statistics, database sizes, write load, and uptime. Object counts appear as top-level keys named by the demangled C++ type.
|
||||
|
||||
## Concurrency Properties
|
||||
|
||||
All per-type counts use `std::atomic<int>` with default sequential consistency, so `increment()` and `decrement()` are safe from any thread. The linked-list head pointer `m_head` is also `std::atomic<Counter*>`. There are no mutexes anywhere in this file. The only non-atomic operation is reading `Counter::next_` during traversal in `getCounts()`, which is safe because `next_` is written exactly once at construction time and never modified thereafter.
|
||||
|
||||
## Why Not Alternatives
|
||||
|
||||
A virtual-function approach (e.g., a pure virtual `typeName()` method) would require each instance to carry a vtable pointer and would not trivially aggregate counts across all instances of the same type without additional infrastructure. A manual registry with `std::map` would need a mutex. The CRTP-plus-static-counter approach achieves type safety, automatic demangled names, lock-free operation, and zero per-instance storage — at the cost of slightly surprising copy/move semantics that operators must understand when subclassing.
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
/**
|
||||
@param now Start time of DecayingSample.
|
||||
*/
|
||||
explicit DecayingSample(time_point now) : m_value(value_type()), m_when(now)
|
||||
explicit DecayingSample(time_point now) : value_(value_type()), when_(now)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ public:
|
||||
add(value_type value, time_point now)
|
||||
{
|
||||
decay(now);
|
||||
m_value += value;
|
||||
return m_value / Window;
|
||||
value_ += value;
|
||||
return value_ / Window;
|
||||
}
|
||||
|
||||
/** Retrieve the current value in normalized units.
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
value(time_point now)
|
||||
{
|
||||
decay(now);
|
||||
return m_value / Window;
|
||||
return value_ / Window;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -50,36 +50,38 @@ private:
|
||||
void
|
||||
decay(time_point now)
|
||||
{
|
||||
if (now == m_when)
|
||||
if (now == when_)
|
||||
return;
|
||||
|
||||
if (m_value != value_type())
|
||||
if (value_ != value_type())
|
||||
{
|
||||
std::size_t elapsed =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(now - m_when).count();
|
||||
std::chrono::duration_cast<std::chrono::seconds>(now - when_).count();
|
||||
|
||||
// A span larger than four times the window decays the
|
||||
// value to an insignificant amount so just reset it.
|
||||
//
|
||||
if (elapsed > 4 * Window)
|
||||
{
|
||||
m_value = value_type();
|
||||
value_ = value_type();
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((elapsed--) != 0u)
|
||||
m_value -= (m_value + Window - 1) / Window;
|
||||
for (; elapsed > 0; --elapsed)
|
||||
{
|
||||
value_ -= (value_ + Window - 1) / Window;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_when = now;
|
||||
when_ = now;
|
||||
}
|
||||
|
||||
// Current value in exponential units
|
||||
value_type m_value;
|
||||
value_type value_;
|
||||
|
||||
// Last time the aging function was applied
|
||||
time_point m_when;
|
||||
time_point when_;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
115
include/xrpl/basics/DecayingSample.h.ai.json
Normal file
115
include/xrpl/basics/DecayingSample.h.ai.json
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 18,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 26,
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"lineno": 26,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 34,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 41,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 61,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 74,
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"lineno": 74,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 79,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 86,
|
||||
"name": "now"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"time_point now"
|
||||
],
|
||||
"lineno": 10,
|
||||
"name": "DecayingSample"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"time_point now"
|
||||
],
|
||||
"lineno": 61,
|
||||
"name": "DecayWindow"
|
||||
}
|
||||
],
|
||||
"description": "Provides two template classes for sampling functions using exponential decay: DecayingSample (with a fixed window) and DecayWindow (with a half-life), useful for tracking decaying averages or statistics over time.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/DecayingSample.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"value",
|
||||
"now"
|
||||
],
|
||||
"lineno": 26,
|
||||
"name": "DecayingSample::add"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"now"
|
||||
],
|
||||
"lineno": 34,
|
||||
"name": "DecayingSample::value"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"now"
|
||||
],
|
||||
"lineno": 41,
|
||||
"name": "DecayingSample::decay"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"value",
|
||||
"now"
|
||||
],
|
||||
"lineno": 74,
|
||||
"name": "DecayWindow::add"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"now"
|
||||
],
|
||||
"lineno": 79,
|
||||
"name": "DecayWindow::value"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"now"
|
||||
],
|
||||
"lineno": 86,
|
||||
"name": "DecayWindow::decay"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 4,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
39
include/xrpl/basics/DecayingSample.h.ai.md
Normal file
39
include/xrpl/basics/DecayingSample.h.ai.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# `DecayingSample.h` — Exponential Decay Accumulators
|
||||
|
||||
This header provides two small template classes that maintain a running accumulation of values that automatically decay over time. Both are used throughout the XRPL node to answer the question "how much activity has happened recently?" without needing to store timestamped histories — the decay does the windowing implicitly.
|
||||
|
||||
## `DecayingSample<Window, Clock>`
|
||||
|
||||
`DecayingSample` maintains an integer accumulator that decays by approximately `1/Window` of its current value each second, producing a rate estimate normalized over the window length. It drives the resource manager's per-peer charge tracking: `Entry.h` declares `local_balance` as `DecayingSample<decayWindowSeconds, clock_type>` where `decayWindowSeconds = 32` (a power of two, per a comment in `Tuning.h`, so the division can be optimized to a bit-shift by the compiler).
|
||||
|
||||
The core decay step is deliberately integer arithmetic with ceiling division:
|
||||
|
||||
```cpp
|
||||
m_value -= (m_value + Window - 1) / Window;
|
||||
```
|
||||
|
||||
This subtracts at least 1 when `m_value` is positive, so the value cannot stall at a non-zero integer indefinitely — a safety property important for rate limiting. Adding `Window - 1` before dividing implements ceiling division, meaning the decay rounds up rather than down. The practical effect is the balance decays slightly faster than the mathematically ideal `m_value *= (1 - 1/Window)^elapsed`, which is a conservative choice for load balancing: erring toward under-charging rather than over-charging.
|
||||
|
||||
The `decay()` fast-path cuts off long idle periods: if more than `4 * Window` seconds have elapsed since the last update (which would leave the value at less than ~2% of its original magnitude), `m_value` is simply zeroed. This prevents the per-second loop from iterating hundreds of times on a reconnecting peer.
|
||||
|
||||
`add()` ages the accumulator first, then adds the new sample, and returns `m_value / Window` — the normalized balance representing average load per second across the window. `value()` does the same without adding anything. Both methods demand a `time_point now` from the caller rather than reading a clock themselves; this makes the class testable and clock-agnostic.
|
||||
|
||||
## `DecayWindow<HalfLife, Clock>`
|
||||
|
||||
`DecayWindow` takes a different approach: it stores a `double` and applies the mathematically exact exponential half-life formula:
|
||||
|
||||
```cpp
|
||||
value_ *= std::pow(2.0, -elapsed / HalfLife);
|
||||
```
|
||||
|
||||
After exactly `HalfLife` seconds of inactivity, the accumulated value halves. After two half-lives it quarters, and so on. Unlike `DecayingSample`, which loops through whole seconds, `DecayWindow` casts the elapsed duration to `duration<double>`, giving it sub-second precision — appropriate when the caller's clock has higher resolution or when calls are frequent.
|
||||
|
||||
`InboundLedgers.cpp` uses this class as `DecayWindow<30, clock_type> fetchRate_` to measure the rate at which ledgers are being fetched from peers. Each fetch fires `fetchRate_.add(1, now)`. The `fetchRate()` accessor returns `60 * fetchRate_.value(now)`, converting the per-second average to a per-minute rate for reporting.
|
||||
|
||||
The `static_assert(HalfLife > 0)` guards against a zero divisor in `std::pow`, which would produce undefined floating-point behavior.
|
||||
|
||||
## Design Rationale: Two Classes Rather Than One
|
||||
|
||||
The two classes reflect different use cases that have incompatible requirements. `DecayingSample` works with integer `value_type` (derived from the clock's duration representation), which matters for the resource manager where charges are counted in discrete units and the result feeds integer comparison thresholds. Integer arithmetic also avoids floating-point instability in tight loops. `DecayWindow` accepts `double` inputs and uses `std::pow`, accepting the floating-point cost in exchange for smooth decay curves and sub-second accuracy — the right tradeoff when measuring continuous rates rather than discrete charges.
|
||||
|
||||
Neither class is thread-safe on its own; callers are responsible for synchronization. `InboundLedgersImp` wraps `fetchRate_` with `fetchRateMutex_`, and the resource `Entry` is similarly protected by the table's lock.
|
||||
@@ -16,9 +16,9 @@ namespace xrpl {
|
||||
*/
|
||||
|
||||
// Exception thrown by an invalid access to Expected.
|
||||
struct bad_expected_access : public std::runtime_error
|
||||
struct BadExpectedAccess : public std::runtime_error
|
||||
{
|
||||
bad_expected_access() : runtime_error("bad expected access")
|
||||
BadExpectedAccess() : runtime_error("bad expected access")
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -26,30 +26,33 @@ struct bad_expected_access : public std::runtime_error
|
||||
namespace detail {
|
||||
|
||||
// Custom policy for Expected. Always throw on an invalid access.
|
||||
struct throw_policy : public boost::outcome_v2::policy::base
|
||||
struct ThrowPolicy : public boost::outcome_v2::policy::base
|
||||
{
|
||||
template <class Impl>
|
||||
static constexpr void
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
wide_value_check(Impl&& self)
|
||||
{
|
||||
if (!base::_has_value(std::forward<Impl>(self)))
|
||||
Throw<bad_expected_access>();
|
||||
Throw<BadExpectedAccess>();
|
||||
}
|
||||
|
||||
template <class Impl>
|
||||
static constexpr void
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
wide_error_check(Impl&& self)
|
||||
{
|
||||
if (!base::_has_error(std::forward<Impl>(self)))
|
||||
Throw<bad_expected_access>();
|
||||
Throw<BadExpectedAccess>();
|
||||
}
|
||||
|
||||
template <class Impl>
|
||||
static constexpr void
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
wide_exception_check(Impl&& self)
|
||||
{
|
||||
if (!base::_has_exception(std::forward<Impl>(self)))
|
||||
Throw<bad_expected_access>();
|
||||
Throw<BadExpectedAccess>();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -73,7 +76,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
constexpr E const&
|
||||
[[nodiscard]] constexpr E const&
|
||||
value() const&
|
||||
{
|
||||
return val_;
|
||||
@@ -91,7 +94,7 @@ public:
|
||||
return std::move(val_);
|
||||
}
|
||||
|
||||
constexpr E const&&
|
||||
[[nodiscard]] constexpr E const&&
|
||||
value() const&&
|
||||
{
|
||||
return std::move(val_);
|
||||
@@ -107,9 +110,9 @@ Unexpected(E (&)[N]) -> Unexpected<E const*>;
|
||||
|
||||
// Definition of Expected. All of the machinery comes from boost::result.
|
||||
template <class T, class E>
|
||||
class [[nodiscard]] Expected : private boost::outcome_v2::result<T, E, detail::throw_policy>
|
||||
class [[nodiscard]] Expected : private boost::outcome_v2::result<T, E, detail::ThrowPolicy>
|
||||
{
|
||||
using Base = boost::outcome_v2::result<T, E, detail::throw_policy>;
|
||||
using Base = boost::outcome_v2::result<T, E, detail::ThrowPolicy>;
|
||||
|
||||
public:
|
||||
template <typename U>
|
||||
@@ -125,13 +128,14 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
constexpr bool
|
||||
[[nodiscard]] constexpr bool
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
has_value() const
|
||||
{
|
||||
return Base::has_value();
|
||||
}
|
||||
|
||||
constexpr T const&
|
||||
[[nodiscard]] constexpr T const&
|
||||
value() const
|
||||
{
|
||||
return Base::value();
|
||||
@@ -143,18 +147,24 @@ public:
|
||||
return Base::value();
|
||||
}
|
||||
|
||||
constexpr E const&
|
||||
error() const
|
||||
[[nodiscard]] constexpr E const&
|
||||
error() const&
|
||||
{
|
||||
return Base::error();
|
||||
}
|
||||
|
||||
constexpr E&
|
||||
error()
|
||||
[[nodiscard]] constexpr E&
|
||||
error() &
|
||||
{
|
||||
return Base::error();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr E&&
|
||||
error() &&
|
||||
{
|
||||
return std::move(Base::error());
|
||||
}
|
||||
|
||||
constexpr explicit
|
||||
operator bool() const
|
||||
{
|
||||
@@ -193,9 +203,9 @@ public:
|
||||
// (without a value) or the reason for the failure.
|
||||
template <class E>
|
||||
class [[nodiscard]]
|
||||
Expected<void, E> : private boost::outcome_v2::result<void, E, detail::throw_policy>
|
||||
Expected<void, E> : private boost::outcome_v2::result<void, E, detail::ThrowPolicy>
|
||||
{
|
||||
using Base = boost::outcome_v2::result<void, E, detail::throw_policy>;
|
||||
using Base = boost::outcome_v2::result<void, E, detail::ThrowPolicy>;
|
||||
|
||||
public:
|
||||
// The default constructor makes a successful Expected<void, E>.
|
||||
@@ -210,18 +220,24 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
constexpr E const&
|
||||
error() const
|
||||
[[nodiscard]] constexpr E const&
|
||||
error() const&
|
||||
{
|
||||
return Base::error();
|
||||
}
|
||||
|
||||
constexpr E&
|
||||
error()
|
||||
[[nodiscard]] constexpr E&
|
||||
error() &
|
||||
{
|
||||
return Base::error();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr E&&
|
||||
error() &&
|
||||
{
|
||||
return std::move(Base::error());
|
||||
}
|
||||
|
||||
constexpr explicit
|
||||
operator bool() const
|
||||
{
|
||||
|
||||
109
include/xrpl/basics/Expected.h.ai.json
Normal file
109
include/xrpl/basics/Expected.h.ai.json
Normal file
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 29,
|
||||
"name": "Impl"
|
||||
},
|
||||
{
|
||||
"lineno": 36,
|
||||
"name": "Impl"
|
||||
},
|
||||
{
|
||||
"lineno": 43,
|
||||
"name": "Impl"
|
||||
},
|
||||
{
|
||||
"lineno": 53,
|
||||
"name": "E"
|
||||
},
|
||||
{
|
||||
"lineno": 80,
|
||||
"name": "U"
|
||||
},
|
||||
{
|
||||
"lineno": 87,
|
||||
"name": "U"
|
||||
},
|
||||
{
|
||||
"lineno": 128,
|
||||
"name": "U"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 16,
|
||||
"name": "bad_expected_access"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 27,
|
||||
"name": "throw_policy"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"E const& e",
|
||||
"E&& e"
|
||||
],
|
||||
"lineno": 53,
|
||||
"name": "Unexpected"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"U&& r",
|
||||
"Unexpected<U> e"
|
||||
],
|
||||
"lineno": 77,
|
||||
"name": "Expected"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Expected()",
|
||||
"Unexpected<U> e"
|
||||
],
|
||||
"lineno": 120,
|
||||
"name": "Expected<void, E>"
|
||||
}
|
||||
],
|
||||
"description": "This file provides an approximation of std::expected (proposed for C++23) using boost::outcome_v2::result, including custom error handling and policies for expected/unexpected result types.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Expected.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 18,
|
||||
"name": "bad_expected_access"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Impl&& self"
|
||||
],
|
||||
"lineno": 29,
|
||||
"name": "wide_value_check"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Impl&& self"
|
||||
],
|
||||
"lineno": 36,
|
||||
"name": "wide_error_check"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Impl&& self"
|
||||
],
|
||||
"lineno": 43,
|
||||
"name": "wide_exception_check"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 7,
|
||||
"name": "xrpl"
|
||||
},
|
||||
{
|
||||
"lineno": 25,
|
||||
"name": "detail"
|
||||
}
|
||||
]
|
||||
}
|
||||
41
include/xrpl/basics/Expected.h.ai.md
Normal file
41
include/xrpl/basics/Expected.h.ai.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# `include/xrpl/basics/Expected.h`
|
||||
|
||||
## Role and Motivation
|
||||
|
||||
This header provides `xrpl::Expected<T, E>`, a polyfill for the `std::expected<T, E>` type proposed for C++23 (P0323R10). At the time this code was written, `std::expected` was not yet available, so the implementation delegates all storage and state management to `boost::outcome_v2::result<T, E, Policy>` while exposing an API that closely mirrors the eventual standard — making a future migration straightforward.
|
||||
|
||||
`Expected<T, E>` represents a value that is *either* a success of type `T` or an error of type `E`. Unlike `std::optional`, which only signals absence, `Expected` carries diagnostic information about *why* a result is missing. Unlike exceptions, it forces callers to explicitly inspect the outcome. The `[[nodiscard]]` attribute on both the primary template and the `void` specialization guarantees at compile time that callers cannot silently drop a return value — a critical safety property in a financial ledger where ignored error returns could mean silent transaction corruption.
|
||||
|
||||
## Components
|
||||
|
||||
### `bad_expected_access`
|
||||
|
||||
A thin `std::runtime_error` subclass thrown whenever code tries to read the wrong half of an `Expected` — e.g., calling `value()` on an error-holding instance. It carries no additional data because the error value itself is available via `error()` and the point of failure is immediately clear from the stack trace. By inheriting from `std::runtime_error`, it integrates naturally with XRPL's existing exception hierarchy.
|
||||
|
||||
### `detail::throw_policy`
|
||||
|
||||
Boost.Outcome's policy mechanism controls what happens when the invariants of a `result` are violated. The default boost policy may assert or exhibit undefined behavior depending on build configuration; `throw_policy` replaces that with deterministic exception throwing. All three "wide" check entry points — `wide_value_check`, `wide_error_check`, and `wide_exception_check` — delegate to `Throw<bad_expected_access>()` (from `contract.h`) rather than a bare `throw`, so the violation is also logged before the exception propagates, consistent with XRPL's programming-by-contract philosophy.
|
||||
|
||||
### `Unexpected<E>`
|
||||
|
||||
A wrapper type that acts as an explicit tag for the error path. A function returning `Expected<T, E>` constructs the error branch by returning `Unexpected<E>(err)`, not a bare `E`. This prevents the implicit construction ambiguity that would arise if both `T` and `E` were, for example, `std::string`. The class provides all four value-category overloads of `value()` (lvalue/rvalue × const/non-const) for perfect forwarding into `Expected`'s constructor.
|
||||
|
||||
The deduction guide `Unexpected(E (&)[N]) -> Unexpected<E const*>` makes it ergonomic to pass string literals: `Unexpected("bad input")` deduces to `Unexpected<char const*>` rather than to a fixed-length array type, avoiding obscure template errors.
|
||||
|
||||
### `Expected<T, E>` (primary template)
|
||||
|
||||
Privately inherits from `boost::outcome_v2::result<T, E, detail::throw_policy>`. Private inheritance is intentional — it exposes only the `std::expected`-shaped API and hides the broader Outcome API (which includes channel-specific accessors and other facilities that would pollute the interface). The two constructors use `requires std::convertible_to` constraints so that implicit narrowing is rejected at compile time.
|
||||
|
||||
`operator bool`, `operator*`, and `operator->` map onto `has_value()` and `value()`, matching the pointer-like ergonomics of the standard proposal. Accessing `operator*` or `operator->` on an error-holding `Expected` triggers `throw_policy::wide_value_check`, which throws `bad_expected_access`. Similarly, calling `error()` on a value-holding instance triggers `wide_error_check`.
|
||||
|
||||
### `Expected<void, E>` (partial specialization)
|
||||
|
||||
Functions that either succeed (producing no value) or fail with a diagnostic use this specialization. Its default constructor calls `boost::outcome_v2::success()` to produce a successful instance — matching the proposed `std::expected<void, E>{}` default construction semantics. This is the pattern used in `STTx::checkSign()` and related signature-verification methods, which return `Expected<void, std::string>`: on success the caller simply checks `operator bool`; on failure the error string explains what went wrong.
|
||||
|
||||
## Usage Patterns in the Codebase
|
||||
|
||||
`tokens.h` defines a convenience alias `B58Result<T> = Expected<T, std::error_code>` for Base58Check encoding/decoding operations, where the error is a standard system error code. `base_uint.h` uses `Expected<decltype(data_), ParseResult>` for a `noexcept` hex-parsing path, capturing a per-character parse failure without throwing. `STTx.h` uses `Expected<void, std::string>` for all signature-check entry points — a natural fit because signature validation either passes silently or produces a human-readable error message.
|
||||
|
||||
## Design Trade-offs
|
||||
|
||||
Choosing `boost::outcome` over a hand-rolled type means the storage layout, move semantics, and triviality propagation are handled by a well-tested library, reducing the risk of subtle UB in low-level storage operations. The cost is a dependency on Boost and some mismatch between Outcome's three-state model (value / error / exception pointer) and `std::expected`'s two-state model; the `wide_exception_check` override in `throw_policy` handles the third state consistently by also throwing `bad_expected_access`, even though `Expected` itself never stores an exception pointer in practice. When C++23 `std::expected` becomes universally available, the migration path is clear: the public API is already a subset of the standard interface.
|
||||
58
include/xrpl/basics/FileUtilities.h.ai.json
Normal file
58
include/xrpl/basics/FileUtilities.h.ai.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 9,
|
||||
"name": "ec"
|
||||
},
|
||||
{
|
||||
"lineno": 10,
|
||||
"name": "sourcePath"
|
||||
},
|
||||
{
|
||||
"lineno": 11,
|
||||
"name": "maxSize"
|
||||
},
|
||||
{
|
||||
"lineno": 15,
|
||||
"name": "ec"
|
||||
},
|
||||
{
|
||||
"lineno": 16,
|
||||
"name": "destPath"
|
||||
},
|
||||
{
|
||||
"lineno": 17,
|
||||
"name": "contents"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Provides utility functions for reading from and writing to files using Boost filesystem, with error handling and optional size limit.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/FileUtilities.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"ec",
|
||||
"sourcePath",
|
||||
"maxSize"
|
||||
],
|
||||
"lineno": 8,
|
||||
"name": "getFileContents"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"ec",
|
||||
"destPath",
|
||||
"contents"
|
||||
],
|
||||
"lineno": 14,
|
||||
"name": "writeFileContents"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
33
include/xrpl/basics/FileUtilities.h.ai.md
Normal file
33
include/xrpl/basics/FileUtilities.h.ai.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# `include/xrpl/basics/FileUtilities.h`
|
||||
|
||||
This header declares two thin file I/O utilities — `getFileContents` and `writeFileContents` — that form the XRPL codebase's standard interface for synchronous file access. The design problem they solve is not the I/O itself, but the error-handling contract: the broader rippled codebase avoids exceptions in many subsystems and instead relies on `boost::system::error_code` for structured, non-throwing error propagation. These two functions provide a consistent, exception-free surface for the handful of places in the node that must read or write files.
|
||||
|
||||
## Interface Design
|
||||
|
||||
Both functions follow the same convention: an `error_code&` output parameter is the first argument, populated on failure while the function returns an empty result (or returns nothing for the write case). This is the classic Boost.Asio-style error-out-parameter pattern, chosen over exception-throwing I/O because the callers — configuration loading, validator list file reads, test scaffolding — operate in contexts where an error is a recoverable condition requiring a structured diagnostic path rather than a stack unwind.
|
||||
|
||||
`getFileContents` takes a `boost::filesystem::path` and an optional `std::size_t` upper bound. The `std::optional<std::size_t> maxSize` parameter is the key safety valve: it allows callers to cap memory usage before any bytes are read into a `std::string`. When absent, the file is read in full. When present, the function checks the on-disk file size before opening the stream and returns `file_too_large` immediately if the limit is exceeded. This pre-check is cheap and prevents unbounded allocation when reading untrusted or potentially large files.
|
||||
|
||||
`writeFileContents` takes a `boost::filesystem::path` and the string to write. It opens with `std::ios::out | std::ios::trunc`, guaranteeing the destination file is replaced atomically from the content's perspective — no partial appends. The function does not check or create intermediate directories; the caller is responsible for ensuring the destination path exists.
|
||||
|
||||
## Implementation Notes (from the `.cpp`)
|
||||
|
||||
`getFileContents` calls `boost::filesystem::canonical()` before doing anything else. This resolves symlinks and relative components into an absolute, normalized path, ensuring the subsequent `file_size()` check and stream open operate on the same physical file. Calling `canonical()` with the `ec` overload also intercepts path resolution errors (non-existent file, permission denied) through the same error-code channel rather than a filesystem exception.
|
||||
|
||||
After path resolution, the implementation reads the file via a `std::istreambuf_iterator` range construction directly into a `std::string`. This is idiomatic C++ for slurping a whole file but has a subtle implication: for text-mode streams on some platforms, newline translation may occur. The stream is opened in `std::ios::in` (text mode), consistent with the intended use cases — TOML/JSON configuration and validator list JSON — where the content is human-readable text rather than binary data.
|
||||
|
||||
Error checking is done at three points: path resolution failure, pre-open size check, and post-read `fileStream.bad()`. The `bad()` check (not `fail()`) specifically catches I/O errors during reading, not logical stream state issues, which is the correct guard for a hardware or OS-level read failure mid-stream.
|
||||
|
||||
## Callers in Context
|
||||
|
||||
The three primary call sites reveal the intended use scope:
|
||||
|
||||
- `src/xrpld/core/detail/Config.cpp` uses `getFileContents` twice: once to load the main configuration file and once to load the validators file specified within that config. These are startup-time reads on the main thread, where a missing file is a fatal misconfiguration.
|
||||
- `src/xrpld/app/misc/detail/WorkFile.h` uses `getFileContents` with a hard cap of `megabytes(1)` to read validator list files fetched from the network. The 1 MB cap is a deliberate denial-of-service defense against a maliciously large or corrupted file consuming unbounded memory.
|
||||
- `src/xrpld/app/misc/detail/ValidatorList.cpp` uses `writeFileContents` to persist the current validator list as styled JSON after an update.
|
||||
|
||||
The `maxSize` parameter's real motivation is visible in the `WorkFile` usage: without it, a 4 GB file at a validator list URL would allocate 4 GB of heap before the caller could inspect the error. The pre-check using `file_size()` is a TOCTOU (time-of-check/time-of-use) race in theory, but in practice the files involved are either local config files or freshly downloaded files in a controlled temp location, making the race window negligible.
|
||||
|
||||
## Relationship to the `basics` Module
|
||||
|
||||
Within `include/xrpl/basics/`, this header occupies the narrowest role: it is a leaf utility with no dependencies on other XRPL types. It depends only on Boost.Filesystem and `<optional>`, making it safe to include anywhere in the stack without pulling in heavier XRPL headers. The `ByteUtilities.h` header (which provides `kilobytes()` and `megabytes()`) is the natural companion when callers need to express size limits in readable units, as the test suite and `WorkFile` both demonstrate.
|
||||
@@ -159,16 +159,16 @@ public:
|
||||
reset();
|
||||
|
||||
/** Get the raw pointer */
|
||||
T*
|
||||
[[nodiscard]] T*
|
||||
get() const;
|
||||
|
||||
/** Return the strong count */
|
||||
std::size_t
|
||||
use_count() const;
|
||||
[[nodiscard]] std::size_t
|
||||
useCount() const;
|
||||
|
||||
template <class TT, class... Args>
|
||||
friend SharedIntrusive<TT>
|
||||
make_SharedIntrusive(Args&&... args);
|
||||
makeSharedIntrusive(Args&&... args);
|
||||
|
||||
template <class TT>
|
||||
friend class SharedIntrusive;
|
||||
@@ -181,7 +181,7 @@ public:
|
||||
|
||||
private:
|
||||
/** Return the raw pointer held by this object. */
|
||||
T*
|
||||
[[nodiscard]] T*
|
||||
unsafeGetRawPtr() const;
|
||||
|
||||
/** Exchange the current raw pointer held by this object with the given
|
||||
@@ -260,7 +260,7 @@ public:
|
||||
lock() const;
|
||||
|
||||
/** Return true if the strong count is zero. */
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
expired() const;
|
||||
|
||||
/** Set the pointer to null and decrement the weak count.
|
||||
@@ -339,7 +339,7 @@ public:
|
||||
don't lock the weak pointer. Use the `lock` method if that's what's
|
||||
needed)
|
||||
*/
|
||||
SharedIntrusive<T>
|
||||
[[nodiscard]] SharedIntrusive<T>
|
||||
getStrong() const;
|
||||
|
||||
/** Return true if this is a strong pointer and the strong pointer is
|
||||
@@ -357,31 +357,31 @@ public:
|
||||
/** If this is a strong pointer, return the raw pointer. Otherwise
|
||||
return null.
|
||||
*/
|
||||
T*
|
||||
[[nodiscard]] T*
|
||||
get() const;
|
||||
|
||||
/** If this is a strong pointer, return the strong count. Otherwise
|
||||
* return 0
|
||||
*/
|
||||
std::size_t
|
||||
use_count() const;
|
||||
[[nodiscard]] std::size_t
|
||||
useCount() const;
|
||||
|
||||
/** Return true if there is a non-zero strong count. */
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
expired() const;
|
||||
|
||||
/** If this is a strong pointer, return the strong pointer. Otherwise
|
||||
attempt to lock the weak pointer.
|
||||
*/
|
||||
SharedIntrusive<T>
|
||||
[[nodiscard]] SharedIntrusive<T>
|
||||
lock() const;
|
||||
|
||||
/** Return true is this represents a strong pointer. */
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
isStrong() const;
|
||||
|
||||
/** Return true is this represents a weak pointer. */
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
isWeak() const;
|
||||
|
||||
/** If this is a weak pointer, attempt to convert it to a strong
|
||||
@@ -406,16 +406,16 @@ private:
|
||||
// pointer. The low bit must be masked to zero when converting back to a
|
||||
// pointer. If the low bit is '1', this is a weak pointer.
|
||||
std::uintptr_t tp_{0};
|
||||
static constexpr std::uintptr_t tagMask = 1;
|
||||
static constexpr std::uintptr_t ptrMask = ~tagMask;
|
||||
static constexpr std::uintptr_t kTagMask = 1;
|
||||
static constexpr std::uintptr_t kPtrMask = ~kTagMask;
|
||||
|
||||
private:
|
||||
/** Return the raw pointer held by this object.
|
||||
*/
|
||||
T*
|
||||
[[nodiscard]] T*
|
||||
unsafeGetRawPtr() const;
|
||||
|
||||
enum class RefStrength { strong, weak };
|
||||
enum class RefStrength { Strong, Weak };
|
||||
/** Set the raw pointer and tag bit directly.
|
||||
*/
|
||||
void
|
||||
@@ -442,7 +442,7 @@ private:
|
||||
*/
|
||||
template <class TT, class... Args>
|
||||
SharedIntrusive<TT>
|
||||
make_SharedIntrusive(Args&&... args)
|
||||
makeSharedIntrusive(Args&&... args)
|
||||
{
|
||||
auto p = new TT(std::forward<Args>(args)...);
|
||||
|
||||
@@ -469,21 +469,21 @@ using SharedWeakUnionPtr = SharedWeakUnion<T>;
|
||||
|
||||
template <class T, class... A>
|
||||
SharedPtr<T>
|
||||
make_shared(A&&... args)
|
||||
makeShared(A&&... args)
|
||||
{
|
||||
return make_SharedIntrusive<T>(std::forward<A>(args)...);
|
||||
return makeSharedIntrusive<T>(std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
template <class T, class TT>
|
||||
SharedPtr<T>
|
||||
static_pointer_cast(TT const& v)
|
||||
staticPointerCast(TT const& v)
|
||||
{
|
||||
return SharedPtr<T>(StaticCastTagSharedIntrusive{}, v);
|
||||
}
|
||||
|
||||
template <class T, class TT>
|
||||
SharedPtr<T>
|
||||
dynamic_pointer_cast(TT const& v)
|
||||
dynamicPointerCast(TT const& v)
|
||||
{
|
||||
return SharedPtr<T>(DynamicCastTagSharedIntrusive{}, v);
|
||||
}
|
||||
|
||||
115
include/xrpl/basics/IntrusivePointer.h.ai.json
Normal file
115
include/xrpl/basics/IntrusivePointer.h.ai.json
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 61,
|
||||
"name": "T* p"
|
||||
},
|
||||
{
|
||||
"lineno": 61,
|
||||
"name": "TAdoptTag"
|
||||
},
|
||||
{
|
||||
"lineno": 63,
|
||||
"name": "SharedIntrusive const& rhs"
|
||||
},
|
||||
{
|
||||
"lineno": 67,
|
||||
"name": "SharedIntrusive<TT> const& rhs"
|
||||
},
|
||||
{
|
||||
"lineno": 70,
|
||||
"name": "SharedIntrusive&& rhs"
|
||||
},
|
||||
{
|
||||
"lineno": 74,
|
||||
"name": "SharedIntrusive<TT>&& rhs"
|
||||
},
|
||||
{
|
||||
"lineno": 196,
|
||||
"name": "TT"
|
||||
},
|
||||
{
|
||||
"lineno": 196,
|
||||
"name": "Args&&... args"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 11,
|
||||
"name": "StaticCastTagSharedIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 19,
|
||||
"name": "DynamicCastTagSharedIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 27,
|
||||
"name": "SharedIntrusiveAdoptIncrementStrongTag"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 34,
|
||||
"name": "SharedIntrusiveAdoptNoIncrementTag"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* p, TAdoptTag",
|
||||
"SharedIntrusive const& rhs",
|
||||
"SharedIntrusive<TT> const& rhs",
|
||||
"SharedIntrusive&& rhs",
|
||||
"SharedIntrusive<TT>&& rhs",
|
||||
"StaticCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs",
|
||||
"StaticCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs",
|
||||
"DynamicCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs",
|
||||
"DynamicCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs"
|
||||
],
|
||||
"lineno": 54,
|
||||
"name": "SharedIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"WeakIntrusive const& rhs",
|
||||
"WeakIntrusive&& rhs",
|
||||
"SharedIntrusive<T> const& rhs",
|
||||
"SharedIntrusive<T> const&& rhs"
|
||||
],
|
||||
"lineno": 151,
|
||||
"name": "WeakIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedWeakUnion const& rhs",
|
||||
"SharedIntrusive<TT> const& rhs",
|
||||
"SharedWeakUnion&& rhs",
|
||||
"SharedIntrusive<TT>&& rhs"
|
||||
],
|
||||
"lineno": 210,
|
||||
"name": "SharedWeakUnion"
|
||||
}
|
||||
],
|
||||
"description": "This file implements shared and weak intrusive pointer classes (SharedIntrusive, WeakIntrusive, SharedWeakUnion) for reference-counted memory management, including utilities for casting and pointer creation, primarily for use in the XRPL codebase.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/IntrusivePointer.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"Args&&... args"
|
||||
],
|
||||
"lineno": 196,
|
||||
"name": "make_SharedIntrusive"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 7,
|
||||
"name": "xrpl"
|
||||
},
|
||||
{
|
||||
"lineno": 222,
|
||||
"name": "intr_ptr"
|
||||
}
|
||||
]
|
||||
}
|
||||
52
include/xrpl/basics/IntrusivePointer.h.ai.md
Normal file
52
include/xrpl/basics/IntrusivePointer.h.ai.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# `include/xrpl/basics/IntrusivePointer.h`
|
||||
|
||||
This header defines XRPL's custom intrusive smart pointer system: `SharedIntrusive<T>`, `WeakIntrusive<T>`, and `SharedWeakUnion<T>`. The system was designed specifically for `SHAMapInnerNode` — the inner nodes of the radix-16 Merkle trie at the heart of ledger state — but is general enough to serve other reference-counted types. The driver for building this rather than using `std::shared_ptr` is a lifecycle feature called the *partial destructor*, combined with a memory-efficient combined strong/weak pointer variant.
|
||||
|
||||
## Why Not `std::shared_ptr`?
|
||||
|
||||
The file's own comment names the key difference clearly. With `std::shared_ptr` created via `make_shared`, the control block (which contains both the strong and weak counts) lives alongside the object in a single allocation. That allocation is not reclaimed until both the strong *and* weak counts hit zero. So if something holds a `std::weak_ptr` to an inner node, the node's full allocation — including its 16 child pointers — stays live even after the last `shared_ptr` drops. For the SHAMap this is expensive: each inner node can hold up to 16 child `SharedIntrusive` pointers. The partial destructor mechanism exists specifically to release those children as soon as the strong count falls to zero, leaving only a shell waiting for the weak count to drain.
|
||||
|
||||
## Reference Count Layout
|
||||
|
||||
The actual counters live in `IntrusiveRefCounts` (`IntrusiveRefCounts.h`), which must be a base class of any type `T` used with these pointers. A single `std::atomic<uint32_t>` field encodes four things:
|
||||
|
||||
- **Bits 0–15**: strong count (up to 65535 owners)
|
||||
- **Bits 16–29**: weak count (14 bits, up to 16383 weak holders)
|
||||
- **Bit 30**: `partialDestroyStarted` flag
|
||||
- **Bit 31**: `partialDestroyFinished` flag
|
||||
|
||||
Packing counts and flags into one atomic integer means `releaseStrongRef()` can atomically decrement the count *and* set the `partialDestroyStarted` flag in a single CAS loop, avoiding a TOCTOU window where two threads could both decide to trigger partial destruction. The two flags are required to safely sequence concurrent partial- and full-destruction: the last weak pointer release spins on `atomic::wait()` if the partial destructor has started but not yet finished, preventing `delete` from racing with `partialDestructor()`.
|
||||
|
||||
## `SharedIntrusive<T>` — The Strong Pointer
|
||||
|
||||
`SharedIntrusive<T>` holds a raw `T* ptr_` whose lifetime is controlled by an intrusive strong count on `*ptr_`. Copy construction calls `ptr_->addStrongRef()`; move construction steals via `unsafeExchange(nullptr)` without touching the count. When the last strong holder releases (`unsafeReleaseAndStore(nullptr)` called from destructor or `reset()`), `releaseStrongRef()` returns one of three `ReleaseStrongRefAction` values:
|
||||
|
||||
- `noop` — other strong holders remain
|
||||
- `destroy` — both counts are zero; `delete prev`
|
||||
- `partialDestroy` — weak holders remain; call `prev->partialDestructor()` then `partialDestructorFinished(&prev)`
|
||||
|
||||
The call to `partialDestructorFinished` is the responsibility of the smart pointer class, not the pointee's `partialDestructor()`. This deliberate separation — noted in comments — forces every new `partialDestructor` implementation to explicitly arrange that call, making it harder to accidentally omit the step that wakes waiting threads.
|
||||
|
||||
The `unsafe*` private methods (`unsafeGetRawPtr`, `unsafeSetRawPtr`, `unsafeExchange`, `unsafeReleaseAndStore`) are named with the "unsafe" prefix not because they are dangerous in isolation, but as an architectural seam: the comment explicitly anticipates a future patch where `ptr_` might become `std::atomic<T*>`, and isolating all direct pointer access through these methods makes such a change localized.
|
||||
|
||||
## Adopt Tags and `make_SharedIntrusive`
|
||||
|
||||
Two tag types — `SharedIntrusiveAdoptIncrementStrongTag` and `SharedIntrusiveAdoptNoIncrementTag` — control whether adopting a raw pointer bumps the strong count. `make_SharedIntrusive<TT>()` allocates a new object with `new TT(...)` and wraps it with `NoIncrement`. This is correct because `IntrusiveRefCounts` initializes its atomic field to `strongDelta` (= 1), meaning the object is born with a strong count of one; incrementing again would be a double-count. The `static_assert` in `make_SharedIntrusive` verifies that the adopting constructor is `noexcept`, since a throw after the raw `new` but before the pointer is wrapped would leak the allocation.
|
||||
|
||||
## Cast Tags
|
||||
|
||||
`StaticCastTagSharedIntrusive` and `DynamicCastTagSharedIntrusive` are dispatch tags for cast-constructors, enabling the `intr_ptr::static_pointer_cast<T>()` and `intr_ptr::dynamic_pointer_cast<T>()` free functions. The move variant of the dynamic-cast constructor handles failure carefully: it uses `unsafeExchange` to steal the pointer from `rhs`, attempts `dynamic_cast`, and if it fails, exchanges the pointer back into `rhs` so ownership is not lost.
|
||||
|
||||
## `WeakIntrusive<T>` — The Weak Pointer
|
||||
|
||||
`WeakIntrusive<T>` mirrors the weak semantics of `std::weak_ptr`. Copy construction calls `ptr_->addWeakRef()`; the destructor calls `unsafeReleaseNoStore()` which invokes `releaseWeakRef()`. The interesting method is `lock()`: it calls `checkoutStrongRefFromWeak()`, a CAS loop that increments the strong count only if it is already non-zero. If the strong count has already hit zero the lock fails and an empty `SharedIntrusive` is returned. Note that copy assignment from a `WeakIntrusive` is deleted — the comment explains this was omitted to simplify the implementation since no current use case required it.
|
||||
|
||||
## `SharedWeakUnion<T>` — The Tagged Pointer
|
||||
|
||||
`SharedWeakUnion<T>` is the most architecturally unusual piece. It stores both the pointer value and a strong/weak discriminator inside a single `uintptr_t` field `tp_` by using pointer tagging: if the low bit is `1`, the pointer represents a weak reference; if it is `0`, a strong reference. This works because `alignof(T) >= 2` is statically asserted, guaranteeing the low bit of any valid `T*` is always zero.
|
||||
|
||||
The practical value is for tagged caches, where a cache slot should hold a strong pointer when the object is actively needed but can downgrade to a weak pointer to allow eviction without cache churn. `convertToStrong()` and `convertToWeak()` perform in-place promotion and demotion: `convertToStrong()` atomically promotes a weak checkout to a strong reference using `checkoutStrongRefFromWeak()` then releases the weak count; `convertToWeak()` uses the atomic `addWeakReleaseStrongRef()` operation to swap one strong count for one weak count in a single CAS loop, handling the `partialDestroy` case that arises if this was the very last strong pointer. The `lock()` method unifies weak and strong paths: if already strong, increment and return; if weak, attempt a checkout.
|
||||
|
||||
## `intr_ptr` Namespace
|
||||
|
||||
The nested `intr_ptr` namespace provides `std::shared_ptr`-style vocabulary aliases — `SharedPtr<T>`, `WeakPtr<T>`, `SharedWeakUnionPtr<T>`, `make_shared<T>()`, `static_pointer_cast<T>()`, `dynamic_pointer_cast<T>()` — used throughout the SHAMap subsystem. `SHAMapInnerNode` stores its 16 children as `intr_ptr::SharedPtr<SHAMapTreeNode>` and exposes `partialDestructor()` to reset them when the last strong holder drops.
|
||||
@@ -43,14 +43,16 @@ SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT> const& rhs)
|
||||
}
|
||||
|
||||
template <class T>
|
||||
SharedIntrusive<T>::SharedIntrusive(SharedIntrusive&& rhs) : ptr_{rhs.unsafeExchange(nullptr)}
|
||||
SharedIntrusive<T>::SharedIntrusive(SharedIntrusive&& rhs)
|
||||
: ptr_{std::move(rhs).unsafeExchange(nullptr)}
|
||||
{
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <class TT>
|
||||
requires std::convertible_to<TT*, T*>
|
||||
SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT>&& rhs) : ptr_{rhs.unsafeExchange(nullptr)}
|
||||
SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT>&& rhs)
|
||||
: ptr_{std::move(rhs).unsafeExchange(nullptr)}
|
||||
{
|
||||
}
|
||||
template <class T>
|
||||
@@ -93,7 +95,7 @@ SharedIntrusive<T>::operator=(SharedIntrusive&& rhs)
|
||||
if (this == &rhs)
|
||||
return *this;
|
||||
|
||||
unsafeReleaseAndStore(rhs.unsafeExchange(nullptr));
|
||||
unsafeReleaseAndStore(std::move(rhs).unsafeExchange(nullptr));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -105,7 +107,7 @@ SharedIntrusive<T>::operator=(SharedIntrusive<TT>&& rhs)
|
||||
{
|
||||
static_assert(!std::is_same_v<T, TT>, "This overload should not be instantiated for T == TT");
|
||||
|
||||
unsafeReleaseAndStore(rhs.unsafeExchange(nullptr));
|
||||
unsafeReleaseAndStore(std::move(rhs).unsafeExchange(nullptr));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -157,7 +159,7 @@ SharedIntrusive<T>::SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusiv
|
||||
template <class T>
|
||||
template <class TT>
|
||||
SharedIntrusive<T>::SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs)
|
||||
: ptr_{static_cast<T*>(rhs.unsafeExchange(nullptr))}
|
||||
: ptr_{static_cast<T*>(std::move(rhs).unsafeExchange(nullptr))}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -184,8 +186,10 @@ SharedIntrusive<T>::SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusi
|
||||
{
|
||||
ptr_ = dynamic_cast<T*>(toSet);
|
||||
if (!ptr_)
|
||||
{
|
||||
// need to set the pointer back or will leak
|
||||
rhs.unsafeExchange(toSet);
|
||||
std::move(rhs).unsafeExchange(toSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,10 +230,10 @@ SharedIntrusive<T>::get() const
|
||||
|
||||
template <class T>
|
||||
std::size_t
|
||||
SharedIntrusive<T>::use_count() const
|
||||
SharedIntrusive<T>::useCount() const
|
||||
{
|
||||
if (auto p = unsafeGetRawPtr())
|
||||
return p->use_count();
|
||||
return p->useCount();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -266,12 +270,12 @@ SharedIntrusive<T>::unsafeReleaseAndStore(T* next)
|
||||
auto action = prev->releaseStrongRef();
|
||||
switch (action)
|
||||
{
|
||||
case noop:
|
||||
case NoOp:
|
||||
break;
|
||||
case destroy:
|
||||
case Destroy:
|
||||
delete prev;
|
||||
break;
|
||||
case partialDestroy:
|
||||
case PartialDestroy:
|
||||
prev->partialDestructor();
|
||||
partialDestructorFinished(&prev);
|
||||
// prev is null and may no longer be used
|
||||
@@ -345,7 +349,7 @@ template <class T>
|
||||
bool
|
||||
WeakIntrusive<T>::expired() const
|
||||
{
|
||||
return (!ptr_ || ptr_->expired());
|
||||
return ((ptr_ == nullptr) || ptr_->expired());
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -360,16 +364,16 @@ template <class T>
|
||||
void
|
||||
WeakIntrusive<T>::unsafeReleaseNoStore()
|
||||
{
|
||||
if (!ptr_)
|
||||
if (ptr_ == nullptr)
|
||||
return;
|
||||
|
||||
using enum ReleaseWeakRefAction;
|
||||
auto action = ptr_->releaseWeakRef();
|
||||
switch (action)
|
||||
{
|
||||
case noop:
|
||||
case NoOp:
|
||||
break;
|
||||
case destroy:
|
||||
case Destroy:
|
||||
delete ptr_;
|
||||
break;
|
||||
}
|
||||
@@ -385,9 +389,13 @@ SharedWeakUnion<T>::SharedWeakUnion(SharedWeakUnion const& rhs) : tp_{rhs.tp_}
|
||||
return;
|
||||
|
||||
if (rhs.isStrong())
|
||||
{
|
||||
p->addStrongRef();
|
||||
}
|
||||
else
|
||||
{
|
||||
p->addWeakRef();
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -398,7 +406,7 @@ SharedWeakUnion<T>::SharedWeakUnion(SharedIntrusive<TT> const& rhs)
|
||||
auto p = rhs.unsafeGetRawPtr();
|
||||
if (p)
|
||||
p->addStrongRef();
|
||||
unsafeSetRawPtr(p, RefStrength::strong);
|
||||
unsafeSetRawPtr(p, RefStrength::Strong);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -414,8 +422,8 @@ SharedWeakUnion<T>::SharedWeakUnion(SharedIntrusive<TT>&& rhs)
|
||||
{
|
||||
auto p = rhs.unsafeGetRawPtr();
|
||||
if (p)
|
||||
unsafeSetRawPtr(p, RefStrength::strong);
|
||||
rhs.unsafeSetRawPtr(nullptr);
|
||||
unsafeSetRawPtr(p, RefStrength::Strong);
|
||||
std::move(rhs).unsafeSetRawPtr(nullptr);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -431,12 +439,12 @@ SharedWeakUnion<T>::operator=(SharedWeakUnion const& rhs)
|
||||
if (rhs.isStrong())
|
||||
{
|
||||
p->addStrongRef();
|
||||
unsafeSetRawPtr(p, RefStrength::strong);
|
||||
unsafeSetRawPtr(p, RefStrength::Strong);
|
||||
}
|
||||
else
|
||||
{
|
||||
p->addWeakRef();
|
||||
unsafeSetRawPtr(p, RefStrength::weak);
|
||||
unsafeSetRawPtr(p, RefStrength::Weak);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -456,7 +464,7 @@ SharedWeakUnion<T>::operator=(SharedIntrusive<TT> const& rhs)
|
||||
auto p = rhs.unsafeGetRawPtr();
|
||||
if (p)
|
||||
p->addStrongRef();
|
||||
unsafeSetRawPtr(p, RefStrength::strong);
|
||||
unsafeSetRawPtr(p, RefStrength::Strong);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -467,8 +475,8 @@ SharedWeakUnion<T>&
|
||||
SharedWeakUnion<T>::operator=(SharedIntrusive<TT>&& rhs)
|
||||
{
|
||||
unsafeReleaseNoStore();
|
||||
unsafeSetRawPtr(rhs.unsafeGetRawPtr(), RefStrength::strong);
|
||||
rhs.unsafeSetRawPtr(nullptr);
|
||||
unsafeSetRawPtr(rhs.unsafeGetRawPtr(), RefStrength::Strong);
|
||||
std::move(rhs).unsafeSetRawPtr(nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -517,10 +525,10 @@ SharedWeakUnion<T>::get() const
|
||||
|
||||
template <class T>
|
||||
std::size_t
|
||||
SharedWeakUnion<T>::use_count() const
|
||||
SharedWeakUnion<T>::useCount() const
|
||||
{
|
||||
if (auto p = get())
|
||||
return p->use_count();
|
||||
return p->useCount();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -559,14 +567,14 @@ template <class T>
|
||||
bool
|
||||
SharedWeakUnion<T>::isStrong() const
|
||||
{
|
||||
return !(tp_ & tagMask);
|
||||
return (tp_ & kTagMask) == 0u;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
SharedWeakUnion<T>::isWeak() const
|
||||
{
|
||||
return tp_ & tagMask;
|
||||
return (tp_ & kTagMask) != 0u;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -581,10 +589,10 @@ SharedWeakUnion<T>::convertToStrong()
|
||||
{
|
||||
[[maybe_unused]] auto action = p->releaseWeakRef();
|
||||
XRPL_ASSERT(
|
||||
(action == ReleaseWeakRefAction::noop),
|
||||
(action == ReleaseWeakRefAction::NoOp),
|
||||
"xrpl::SharedWeakUnion::convertToStrong : "
|
||||
"action is noop");
|
||||
unsafeSetRawPtr(p, RefStrength::strong);
|
||||
unsafeSetRawPtr(p, RefStrength::Strong);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -605,9 +613,9 @@ SharedWeakUnion<T>::convertToWeak()
|
||||
auto action = p->addWeakReleaseStrongRef();
|
||||
switch (action)
|
||||
{
|
||||
case noop:
|
||||
case NoOp:
|
||||
break;
|
||||
case destroy:
|
||||
case Destroy:
|
||||
// We just added a weak ref. How could we destroy?
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
@@ -617,7 +625,7 @@ SharedWeakUnion<T>::convertToWeak()
|
||||
unsafeSetRawPtr(nullptr);
|
||||
return true; // Should never happen
|
||||
// LCOV_EXCL_STOP
|
||||
case partialDestroy:
|
||||
case PartialDestroy:
|
||||
// This is a weird case. We just converted the last strong
|
||||
// pointer to a weak pointer.
|
||||
p->partialDestructor();
|
||||
@@ -625,7 +633,7 @@ SharedWeakUnion<T>::convertToWeak()
|
||||
// p is null and may no longer be used
|
||||
break;
|
||||
}
|
||||
unsafeSetRawPtr(p, RefStrength::weak);
|
||||
unsafeSetRawPtr(p, RefStrength::Weak);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -633,7 +641,7 @@ template <class T>
|
||||
T*
|
||||
SharedWeakUnion<T>::unsafeGetRawPtr() const
|
||||
{
|
||||
return reinterpret_cast<T*>(tp_ & ptrMask);
|
||||
return reinterpret_cast<T*>(tp_ & kPtrMask);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -641,8 +649,8 @@ void
|
||||
SharedWeakUnion<T>::unsafeSetRawPtr(T* p, RefStrength rs)
|
||||
{
|
||||
tp_ = reinterpret_cast<std::uintptr_t>(p);
|
||||
if (tp_ && rs == RefStrength::weak)
|
||||
tp_ |= tagMask;
|
||||
if (tp_ && rs == RefStrength::Weak)
|
||||
tp_ |= kTagMask;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -666,12 +674,12 @@ SharedWeakUnion<T>::unsafeReleaseNoStore()
|
||||
auto strongAction = p->releaseStrongRef();
|
||||
switch (strongAction)
|
||||
{
|
||||
case noop:
|
||||
case NoOp:
|
||||
break;
|
||||
case destroy:
|
||||
case Destroy:
|
||||
delete p;
|
||||
break;
|
||||
case partialDestroy:
|
||||
case PartialDestroy:
|
||||
p->partialDestructor();
|
||||
partialDestructorFinished(&p);
|
||||
// p is null and may no longer be used
|
||||
@@ -684,9 +692,9 @@ SharedWeakUnion<T>::unsafeReleaseNoStore()
|
||||
auto weakAction = p->releaseWeakRef();
|
||||
switch (weakAction)
|
||||
{
|
||||
case noop:
|
||||
case NoOp:
|
||||
break;
|
||||
case destroy:
|
||||
case Destroy:
|
||||
delete p;
|
||||
break;
|
||||
}
|
||||
|
||||
698
include/xrpl/basics/IntrusivePointer.ipp.ai.json
Normal file
698
include/xrpl/basics/IntrusivePointer.ipp.ai.json
Normal file
@@ -0,0 +1,698 @@
|
||||
{
|
||||
"args": [],
|
||||
"classes": [],
|
||||
"code_paths": [
|
||||
{
|
||||
"call_chain": [
|
||||
"SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag)",
|
||||
"if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>)",
|
||||
"if (p)",
|
||||
"p->addStrongRef()"
|
||||
],
|
||||
"entry_point": "SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag)",
|
||||
"purpose": "Constructs a SharedIntrusive from a raw pointer, optionally incrementing the strong ref count if adopting.",
|
||||
"validation_points": [
|
||||
"if (p) // Validates pointer before addStrongRef"
|
||||
]
|
||||
},
|
||||
{
|
||||
"call_chain": [
|
||||
"SharedIntrusive<T>::SharedIntrusive(SharedIntrusive const& rhs)",
|
||||
"rhs.unsafeGetRawPtr()",
|
||||
"if (p)",
|
||||
"p->addStrongRef()"
|
||||
],
|
||||
"entry_point": "SharedIntrusive<T>::SharedIntrusive(SharedIntrusive const& rhs)",
|
||||
"purpose": "Copy constructor, increments strong ref if pointer is not null.",
|
||||
"validation_points": [
|
||||
"if (p) // Validates pointer before addStrongRef"
|
||||
]
|
||||
},
|
||||
{
|
||||
"call_chain": [
|
||||
"SharedIntrusive<T>::operator=(SharedIntrusive const& rhs)",
|
||||
"if (this == &rhs)",
|
||||
"rhs.unsafeGetRawPtr()",
|
||||
"if (p)",
|
||||
"p->addStrongRef()",
|
||||
"unsafeReleaseAndStore(p)"
|
||||
],
|
||||
"entry_point": "SharedIntrusive<T>::operator=(SharedIntrusive const& rhs)",
|
||||
"purpose": "Assignment operator, handles self-assignment, increments ref if needed, releases old pointer.",
|
||||
"validation_points": [
|
||||
"if (this == &rhs) // Self-assignment check",
|
||||
"if (p) // Validates pointer before addStrongRef"
|
||||
]
|
||||
},
|
||||
{
|
||||
"call_chain": [
|
||||
"SharedIntrusive<T>::operator=(SharedIntrusive<TT> const& rhs)",
|
||||
"if constexpr (std::is_same_v<T, TT>)",
|
||||
"if (this == &rhs)",
|
||||
"rhs.unsafeGetRawPtr()",
|
||||
"if (p)",
|
||||
"p->addStrongRef()",
|
||||
"unsafeReleaseAndStore(p)"
|
||||
],
|
||||
"entry_point": "SharedIntrusive<T>::operator=(SharedIntrusive<TT> const& rhs)",
|
||||
"purpose": "Assignment from convertible type, handles self-assignment, increments ref if needed, releases old pointer.",
|
||||
"validation_points": [
|
||||
"if (this == &rhs) // Self-assignment check (if T == TT)",
|
||||
"if (p) // Validates pointer before addStrongRef"
|
||||
]
|
||||
},
|
||||
{
|
||||
"call_chain": [
|
||||
"SharedIntrusive<T>::adopt(T* p)",
|
||||
"if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>)",
|
||||
"if (p)",
|
||||
"p->addStrongRef()",
|
||||
"unsafeReleaseAndStore(p)"
|
||||
],
|
||||
"entry_point": "SharedIntrusive<T>::adopt(T* p)",
|
||||
"purpose": "Adopts a raw pointer, optionally increments ref count, releases old pointer.",
|
||||
"validation_points": [
|
||||
"if (p) // Validates pointer before addStrongRef"
|
||||
]
|
||||
}
|
||||
],
|
||||
"data_flows": [
|
||||
{
|
||||
"field": "ptr_",
|
||||
"flow": [
|
||||
"T* p (input or from rhs)",
|
||||
"if (p) validation",
|
||||
"p->addStrongRef() (if validated)",
|
||||
"ptr_ = p"
|
||||
],
|
||||
"origin": "Constructor argument (T* p) or rhs.unsafeGetRawPtr()",
|
||||
"transformations": [
|
||||
"Pointer is checked for null",
|
||||
"Reference count incremented if not null",
|
||||
"Stored in ptr_"
|
||||
],
|
||||
"validated_at": "if (p) before addStrongRef"
|
||||
},
|
||||
{
|
||||
"field": "rhs (SharedIntrusive const& rhs or SharedIntrusive<TT> const& rhs)",
|
||||
"flow": [
|
||||
"rhs (input)",
|
||||
"if (this == &rhs) validation (self-assignment)",
|
||||
"rhs.unsafeGetRawPtr()",
|
||||
"if (p) validation",
|
||||
"p->addStrongRef()",
|
||||
"unsafeReleaseAndStore(p)"
|
||||
],
|
||||
"origin": "Function argument",
|
||||
"transformations": [
|
||||
"Self-assignment check",
|
||||
"Pointer extracted",
|
||||
"Reference count incremented if not null",
|
||||
"Old pointer released and replaced"
|
||||
],
|
||||
"validated_at": "if (this == &rhs), if (p)"
|
||||
},
|
||||
{
|
||||
"field": "T* p (adopt)",
|
||||
"flow": [
|
||||
"T* p (input)",
|
||||
"if (p) validation",
|
||||
"p->addStrongRef() (if validated)",
|
||||
"unsafeReleaseAndStore(p)"
|
||||
],
|
||||
"origin": "adopt(T* p) argument",
|
||||
"transformations": [
|
||||
"Pointer is checked for null",
|
||||
"Reference count incremented if not null",
|
||||
"Old pointer released and replaced"
|
||||
],
|
||||
"validated_at": "if (p)"
|
||||
}
|
||||
],
|
||||
"description": "Implements the definitions for intrusive smart pointer types (SharedIntrusive, WeakIntrusive, SharedWeakUnion) used for reference-counted memory management in the xrpl namespace.",
|
||||
"false_positive_patterns": [
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"input_validation"
|
||||
],
|
||||
"confidence": 0.9,
|
||||
"detection_keywords": [
|
||||
"template type parameters (via static_assert and if constexpr)",
|
||||
"validation",
|
||||
"missing",
|
||||
"check"
|
||||
],
|
||||
"evidence": "Field template type parameters (via static_assert and if constexpr) validated by C++ type system, manual null checks, static_assert",
|
||||
"issue_pattern": "Missing validation for template type parameters (via static_assert and if constexpr)",
|
||||
"why_false_positive": "C++ type system, manual null checks, static_assert validates template type parameters (via static_assert and if constexpr) automatically"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"input_validation"
|
||||
],
|
||||
"confidence": 0.9,
|
||||
"detection_keywords": [
|
||||
"pointer nullness (via if (p))",
|
||||
"validation",
|
||||
"missing",
|
||||
"check"
|
||||
],
|
||||
"evidence": "Field pointer nullness (via if (p)) validated by C++ type system, manual null checks, static_assert",
|
||||
"issue_pattern": "Missing validation for pointer nullness (via if (p))",
|
||||
"why_false_positive": "C++ type system, manual null checks, static_assert validates pointer nullness (via if (p)) automatically"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"p (pointer to T)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (p) at SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept",
|
||||
"issue_pattern": "Missing empty string validation for p (pointer to T)",
|
||||
"why_false_positive": "if (p) validates p (pointer to T) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"rhs (SharedIntrusive const& rhs)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (p) at SharedIntrusive<T>::SharedIntrusive(SharedIntrusive const& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for rhs (SharedIntrusive const& rhs)",
|
||||
"why_false_positive": "if (p) validates rhs (SharedIntrusive const& rhs) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"rhs (SharedIntrusive<TT> const& rhs)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (p) at SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT> const& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for rhs (SharedIntrusive<TT> const& rhs)",
|
||||
"why_false_positive": "if (p) validates rhs (SharedIntrusive<TT> const& rhs) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"this and rhs (self-assignment)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (this == &rhs) at SharedIntrusive<T>::operator=(SharedIntrusive const& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for this and rhs (self-assignment)",
|
||||
"why_false_positive": "if (this == &rhs) validates this and rhs (self-assignment) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"this and rhs (self-assignment)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (this == &rhs) at SharedIntrusive<T>::operator=(SharedIntrusive<TT> const& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for this and rhs (self-assignment)",
|
||||
"why_false_positive": "if (this == &rhs) validates this and rhs (self-assignment) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"this and rhs (self-assignment)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (this == &rhs) at SharedIntrusive<T>::operator=(SharedIntrusive&& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for this and rhs (self-assignment)",
|
||||
"why_false_positive": "if (this == &rhs) validates this and rhs (self-assignment) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"TT (template type parameter)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "static_assert(!std::is_same_v<T, TT>, ...) at SharedIntrusive<T>::operator=(SharedIntrusive<TT>&& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for TT (template type parameter)",
|
||||
"why_false_positive": "static_assert(!std::is_same_v<T, TT>, ...) validates TT (template type parameter) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"type_safety"
|
||||
],
|
||||
"confidence": 0.85,
|
||||
"detection_keywords": [
|
||||
"TT (template type parameter)",
|
||||
"type",
|
||||
"validation",
|
||||
"check"
|
||||
],
|
||||
"evidence": "static_assert(!std::is_same_v<T, TT>, ...) at SharedIntrusive<T>::operator=(SharedIntrusive<TT>&& rhs)",
|
||||
"issue_pattern": "Missing type validation for TT (template type parameter)",
|
||||
"why_false_positive": "static_assert(!std::is_same_v<T, TT>, ...) validates TT (template type parameter) type"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"TAdoptTag (template type parameter)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>) at SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept and adopt(T* p)",
|
||||
"issue_pattern": "Missing empty string validation for TAdoptTag (template type parameter)",
|
||||
"why_false_positive": "if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>) validates TAdoptTag (template type parameter) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"type_safety"
|
||||
],
|
||||
"confidence": 0.85,
|
||||
"detection_keywords": [
|
||||
"TAdoptTag (template type parameter)",
|
||||
"type",
|
||||
"validation",
|
||||
"check"
|
||||
],
|
||||
"evidence": "if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>) at SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept and adopt(T* p)",
|
||||
"issue_pattern": "Missing type validation for TAdoptTag (template type parameter)",
|
||||
"why_false_positive": "if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>) validates TAdoptTag (template type parameter) type"
|
||||
}
|
||||
],
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/IntrusivePointer.ipp",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive const& rhs"
|
||||
],
|
||||
"lineno": 61,
|
||||
"name": "SharedIntrusive<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive<TT> const& rhs"
|
||||
],
|
||||
"lineno": 80,
|
||||
"name": "SharedIntrusive<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive&& rhs"
|
||||
],
|
||||
"lineno": 99,
|
||||
"name": "SharedIntrusive<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive<TT>&& rhs"
|
||||
],
|
||||
"lineno": 110,
|
||||
"name": "SharedIntrusive<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"std::nullptr_t"
|
||||
],
|
||||
"lineno": 120,
|
||||
"name": "SharedIntrusive<T>::operator!="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"std::nullptr_t"
|
||||
],
|
||||
"lineno": 126,
|
||||
"name": "SharedIntrusive<T>::operator=="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* p"
|
||||
],
|
||||
"lineno": 132,
|
||||
"name": "SharedIntrusive<T>::adopt"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 142,
|
||||
"name": "SharedIntrusive<T>::~SharedIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 170,
|
||||
"name": "SharedIntrusive<T>::operator*"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 176,
|
||||
"name": "SharedIntrusive<T>::operator->"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 182,
|
||||
"name": "SharedIntrusive<T>::operator bool"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 188,
|
||||
"name": "SharedIntrusive<T>::reset"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 193,
|
||||
"name": "SharedIntrusive<T>::get"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 198,
|
||||
"name": "SharedIntrusive<T>::use_count"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 206,
|
||||
"name": "SharedIntrusive<T>::unsafeGetRawPtr"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* p"
|
||||
],
|
||||
"lineno": 211,
|
||||
"name": "SharedIntrusive<T>::unsafeSetRawPtr"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* p"
|
||||
],
|
||||
"lineno": 216,
|
||||
"name": "SharedIntrusive<T>::unsafeExchange"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* next"
|
||||
],
|
||||
"lineno": 221,
|
||||
"name": "SharedIntrusive<T>::unsafeReleaseAndStore"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive<TT> const& rhs"
|
||||
],
|
||||
"lineno": 265,
|
||||
"name": "WeakIntrusive<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* ptr"
|
||||
],
|
||||
"lineno": 274,
|
||||
"name": "WeakIntrusive<T>::adopt"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 280,
|
||||
"name": "WeakIntrusive<T>::~WeakIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 285,
|
||||
"name": "WeakIntrusive<T>::lock"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 294,
|
||||
"name": "WeakIntrusive<T>::expired"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 299,
|
||||
"name": "WeakIntrusive<T>::reset"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 304,
|
||||
"name": "WeakIntrusive<T>::unsafeReleaseNoStore"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedWeakUnion const& rhs"
|
||||
],
|
||||
"lineno": 353,
|
||||
"name": "SharedWeakUnion<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive<TT> const& rhs"
|
||||
],
|
||||
"lineno": 380,
|
||||
"name": "SharedWeakUnion<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive<TT>&& rhs"
|
||||
],
|
||||
"lineno": 391,
|
||||
"name": "SharedWeakUnion<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 399,
|
||||
"name": "SharedWeakUnion<T>::~SharedWeakUnion"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 404,
|
||||
"name": "SharedWeakUnion<T>::getStrong"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 415,
|
||||
"name": "SharedWeakUnion<T>::operator bool"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 420,
|
||||
"name": "SharedWeakUnion<T>::reset"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 425,
|
||||
"name": "SharedWeakUnion<T>::get"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 430,
|
||||
"name": "SharedWeakUnion<T>::use_count"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 437,
|
||||
"name": "SharedWeakUnion<T>::expired"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 442,
|
||||
"name": "SharedWeakUnion<T>::lock"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 463,
|
||||
"name": "SharedWeakUnion<T>::isStrong"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 468,
|
||||
"name": "SharedWeakUnion<T>::isWeak"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 473,
|
||||
"name": "SharedWeakUnion<T>::convertToStrong"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 491,
|
||||
"name": "SharedWeakUnion<T>::convertToWeak"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 517,
|
||||
"name": "SharedWeakUnion<T>::unsafeGetRawPtr"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* p",
|
||||
"RefStrength rs"
|
||||
],
|
||||
"lineno": 522,
|
||||
"name": "SharedWeakUnion<T>::unsafeSetRawPtr"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"std::nullptr_t"
|
||||
],
|
||||
"lineno": 528,
|
||||
"name": "SharedWeakUnion<T>::unsafeSetRawPtr"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 532,
|
||||
"name": "SharedWeakUnion<T>::unsafeReleaseNoStore"
|
||||
}
|
||||
],
|
||||
"language": "cpp",
|
||||
"language_patterns": {
|
||||
"exception_patterns": [],
|
||||
"namespace_accessors": [],
|
||||
"raii_usage": [],
|
||||
"smart_pointers": [],
|
||||
"template_validation": []
|
||||
},
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
],
|
||||
"test_coverage_notes": "The code is a low-level utility for intrusive reference counting. Typical tests would be in files like IntrusivePointer_test.cpp or SharedIntrusive_test.cpp, likely under the 'test' or 'unittest' directories. Tests should cover: construction from raw pointer (null and non-null), copy/move construction, assignment (including self-assignment), adopt(), and destruction. Gaps may exist in testing edge cases such as self-assignment, null pointer handling, and cross-type assignment. No direct evidence of test files is present in this snippet, so coverage should be verified in the codebase.",
|
||||
"validation_architecture": {
|
||||
"auto_validated_fields": [
|
||||
"template type parameters (via static_assert and if constexpr)",
|
||||
"pointer nullness (via if (p))"
|
||||
],
|
||||
"framework": "C++ type system, manual null checks, static_assert",
|
||||
"validation_layer": "business_logic"
|
||||
},
|
||||
"validations": [
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (conditional logic only)",
|
||||
"field": "p (pointer to T)",
|
||||
"location": "SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept",
|
||||
"validated_by": "if (p)",
|
||||
"validates": [
|
||||
"Checks if pointer p is not null before calling p->addStrongRef()"
|
||||
],
|
||||
"validation_type": "null check"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (conditional logic only)",
|
||||
"field": "rhs (SharedIntrusive const& rhs)",
|
||||
"location": "SharedIntrusive<T>::SharedIntrusive(SharedIntrusive const& rhs)",
|
||||
"validated_by": "if (p)",
|
||||
"validates": [
|
||||
"Checks if pointer p (from rhs.unsafeGetRawPtr()) is not null before calling p->addStrongRef()"
|
||||
],
|
||||
"validation_type": "null check"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (conditional logic only)",
|
||||
"field": "rhs (SharedIntrusive<TT> const& rhs)",
|
||||
"location": "SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT> const& rhs)",
|
||||
"validated_by": "if (p)",
|
||||
"validates": [
|
||||
"Checks if pointer p (from rhs.unsafeGetRawPtr()) is not null before calling p->addStrongRef()"
|
||||
],
|
||||
"validation_type": "null check"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (early return)",
|
||||
"field": "this and rhs (self-assignment)",
|
||||
"location": "SharedIntrusive<T>::operator=(SharedIntrusive const& rhs)",
|
||||
"validated_by": "if (this == &rhs)",
|
||||
"validates": [
|
||||
"Checks for self-assignment to avoid unnecessary operations"
|
||||
],
|
||||
"validation_type": "business_logic"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (early return)",
|
||||
"field": "this and rhs (self-assignment)",
|
||||
"location": "SharedIntrusive<T>::operator=(SharedIntrusive<TT> const& rhs)",
|
||||
"validated_by": "if (this == &rhs)",
|
||||
"validates": [
|
||||
"Checks for self-assignment to avoid unnecessary operations (only if T == TT)"
|
||||
],
|
||||
"validation_type": "business_logic"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (early return)",
|
||||
"field": "this and rhs (self-assignment)",
|
||||
"location": "SharedIntrusive<T>::operator=(SharedIntrusive&& rhs)",
|
||||
"validated_by": "if (this == &rhs)",
|
||||
"validates": [
|
||||
"Checks for self-assignment to avoid unnecessary operations"
|
||||
],
|
||||
"validation_type": "business_logic"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "static_assert failure (compile-time error)",
|
||||
"field": "TT (template type parameter)",
|
||||
"location": "SharedIntrusive<T>::operator=(SharedIntrusive<TT>&& rhs)",
|
||||
"validated_by": "static_assert(!std::is_same_v<T, TT>, ...)",
|
||||
"validates": [
|
||||
"Ensures that this overload is not instantiated for T == TT"
|
||||
],
|
||||
"validation_type": "type"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (compile-time conditional)",
|
||||
"field": "TAdoptTag (template type parameter)",
|
||||
"location": "SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept and adopt(T* p)",
|
||||
"validated_by": "if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>)",
|
||||
"validates": [
|
||||
"Ensures that addStrongRef() is only called if TAdoptTag is SharedIntrusiveAdoptIncrementStrongTag"
|
||||
],
|
||||
"validation_type": "type"
|
||||
}
|
||||
]
|
||||
}
|
||||
64
include/xrpl/basics/IntrusivePointer.ipp.ai.md
Normal file
64
include/xrpl/basics/IntrusivePointer.ipp.ai.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# `IntrusivePointer.ipp` — Intrusive Smart Pointer Method Definitions
|
||||
|
||||
This file provides the out-of-line template method bodies for three intrusive smart pointer classes declared in `IntrusivePointer.h`: `SharedIntrusive<T>`, `WeakIntrusive<T>`, and `SharedWeakUnion<T>`. As is conventional for C++ template implementations that must be visible to all translation units, the definitions live in a `.ipp` file that `IntrusivePointer.h` includes, rather than in a `.cpp` file.
|
||||
|
||||
## Why Not `std::shared_ptr`?
|
||||
|
||||
The comment on `SharedIntrusive` in the header explains the core motivation: the XRPL codebase needs a smart pointer that can release an object's *data* when the last strong reference drops, while deferring the *memory* release until the last weak reference also drops. `std::shared_ptr` guarantees that the destructor runs at strong-count zero, but an implementation using `make_shared` keeps the entire memory block alive until weak-count zero. More importantly, `std::shared_ptr` provides no hook between "last strong gone" and "last weak gone."
|
||||
|
||||
The intrusive design solves this by embedding reference counts directly in the pointee via `IntrusiveRefCounts` (a base struct the controlled type must inherit). When the strong count reaches zero, the pointer machinery calls a user-defined `partialDestructor()` — which can, for instance, reset `SHAMapInnerNode`'s child-pointer array to free the expensive working data — while the object shell continues to exist as long as any weak pointer holds on. Only when the weak count also reaches zero does `delete` run.
|
||||
|
||||
## `SharedIntrusive<T>` — The Strong Pointer
|
||||
|
||||
Construction and assignment follow a consistent pattern: acquire the new ref before releasing the old. The copy constructor uses a lambda initializer to atomically call `addStrongRef()` before storing the pointer in `ptr_`, ensuring the count is correct even if the lambda result is used to initialize a field directly:
|
||||
|
||||
```cpp
|
||||
ptr_{[&] {
|
||||
auto p = rhs.unsafeGetRawPtr();
|
||||
if (p) p->addStrongRef();
|
||||
return p;
|
||||
}()}
|
||||
```
|
||||
|
||||
Move construction is cheaper: it calls `unsafeExchange(nullptr)` on the source to steal the pointer without touching ref counts. The `static_assert` in the heterogeneous move-assignment operator enforces at compile time that the same-type case is handled by the homogeneous overload, preventing this overload from being instantiated for `T == TT`.
|
||||
|
||||
### The `TAdoptTag` Pattern
|
||||
|
||||
The `adopt(T* p)` method and the raw-pointer constructor both take a `TAdoptTag` template parameter constrained by the `CAdoptTag` concept. Two tags exist: `SharedIntrusiveAdoptIncrementStrongTag` increments the strong count (for absorbing a raw pointer that hasn't had its count bumped yet), and `SharedIntrusiveAdoptNoIncrementTag` adopts the pointer without incrementing. `make_SharedIntrusive` allocates via `new T` — `IntrusiveRefCounts` initializes the strong count to 1 — and then adopts with `SharedIntrusiveAdoptNoIncrementTag` to avoid a double-count. The `noexcept` guarantee on that constructor path is enforced with a `static_assert` inside `make_SharedIntrusive` to prevent memory leaks if construction were to throw after allocation.
|
||||
|
||||
### `unsafeReleaseAndStore` — The Core Destruction Path
|
||||
|
||||
Every operation that replaces the stored pointer funnels through `unsafeReleaseAndStore(T* next)`. It atomically swaps the new pointer in via `std::exchange`, then calls `releaseStrongRef()` on the evicted pointer. The return value is a `ReleaseStrongRefAction` enum with three values:
|
||||
|
||||
- `noop` — other strong pointers remain; do nothing.
|
||||
- `destroy` — no strong or weak pointers remain; call `delete`.
|
||||
- `partialDestroy` — the weak count is non-zero; call `partialDestructor()` then `partialDestructorFinished()`.
|
||||
|
||||
The `partialDestructorFinished` template friend function sets the `partialDestroyFinishedMask` bit atomically on `IntrusiveRefCounts::refCounts`, and if the weak count has already reached zero it calls `notify_one()` to wake any thread that is waiting in `releaseWeakRef()` for the partial destructor to complete before running the full destructor.
|
||||
|
||||
### Cast Constructors
|
||||
|
||||
`SharedIntrusive` supports both `static_cast` and `dynamic_cast` construction from a `SharedIntrusive<TT>`. For the move variant of `dynamic_cast`, there is a subtle correctness invariant: if `dynamic_cast<T*>` returns null (the cast fails), the source pointer is restored via `rhs.unsafeExchange(toSet)` to prevent the controlled object from leaking. A code comment notes that the `unsafeExchange` structure is also kept in anticipation of a future atomic pointer mode.
|
||||
|
||||
## `WeakIntrusive<T>` — The Weak Pointer
|
||||
|
||||
`WeakIntrusive` manages a non-owning reference via `addWeakRef()` and `releaseWeakRef()`. Two deliberate omissions in the interface are worth noting:
|
||||
|
||||
- Copy assignment from another `WeakIntrusive` is `delete`d. The header comment explains this is because there are currently no use cases, and omitting it simplifies implementation. It can be reintroduced if needed.
|
||||
- There is no move constructor from `SharedIntrusive<T>&&`. Moving a strong pointer into a weak pointer would require decrementing the strong count and adding a weak count, making it *more* expensive than copying the raw pointer and adding a weak ref. The deleted overload prevents this surprising hidden cost.
|
||||
|
||||
`lock()` calls `checkoutStrongRefFromWeak()` on the raw pointer, which uses a CAS loop to atomically increment the strong count only if it is currently non-zero. On success, the new `SharedIntrusive` is constructed with `SharedIntrusiveAdoptNoIncrementTag` — the checkout already performed the increment, so a second increment must not occur.
|
||||
|
||||
## `SharedWeakUnion<T>` — The Tagged-Pointer Union
|
||||
|
||||
`SharedWeakUnion` packs both a strong and weak reference into the space of a single pointer word. It stores the pointer as a `std::uintptr_t` called `tp_`, uses the low bit as a tag (1 = weak, 0 = strong), and recovers the raw pointer by masking with `ptrMask = ~1`. A `static_assert` on `alignof(T) >= 2` enforces that the actual pointer will never set the low bit, keeping the encoding sound.
|
||||
|
||||
`unsafeGetRawPtr()` applies the mask; `unsafeSetRawPtr(T*, RefStrength)` stores the pointer and conditionally ORs in the tag bit. `isStrong()` / `isWeak()` read the tag bit directly.
|
||||
|
||||
`convertToStrong()` and `convertToWeak()` allow in-place reference strength switching. `convertToWeak()` uses `addWeakReleaseStrongRef()` — an atomic operation on `IntrusiveRefCounts` that adds a weak delta and subtracts a strong delta in one CAS loop — to avoid a window where the strong count is zero but the weak count hasn't been incremented yet. If the result is `partialDestroy`, `convertToWeak` handles the two-phase partial destruction, including the `partialDestructorFinished` call that clears the pointer variable.
|
||||
|
||||
`get()` returns the raw pointer only if the union holds a strong reference; calling `get()` on a weak-tagged union returns null. `lock()` unifies both cases: if already strong, it bumps the strong count and returns; if weak, it attempts `checkoutStrongRefFromWeak()` and adopts without increment on success.
|
||||
|
||||
## Naming Convention for Primitives
|
||||
|
||||
All methods prefixed with `unsafe` are private and skip reference counting entirely. They manipulate the raw pointer field directly. This naming convention serves two purposes: it makes the separation between raw pointer mechanics and safe counted semantics immediately visible during code review, and the header comments note that these wrappers exist in anticipation of a future patch to support atomic pointer storage (which would require replacing `std::exchange` with `std::atomic::exchange` inside these one-line helpers).
|
||||
@@ -18,7 +18,7 @@ namespace xrpl {
|
||||
destroy: Run the destructor. This action will occur when either the strong
|
||||
count or weak count is decremented and the other count is also zero.
|
||||
*/
|
||||
enum class ReleaseStrongRefAction { noop, partialDestroy, destroy };
|
||||
enum class ReleaseStrongRefAction { NoOp, PartialDestroy, Destroy };
|
||||
|
||||
/** Action to perform when releasing a weak pointer.
|
||||
|
||||
@@ -28,7 +28,7 @@ enum class ReleaseStrongRefAction { noop, partialDestroy, destroy };
|
||||
destroy: Run the destructor. This action will occur when either the strong
|
||||
count or weak count is decremented and the other count is also zero.
|
||||
*/
|
||||
enum class ReleaseWeakRefAction { noop, destroy };
|
||||
enum class ReleaseWeakRefAction { NoOp, Destroy };
|
||||
|
||||
/** Implement the strong count, weak count, and bit flags for an intrusive
|
||||
pointer.
|
||||
@@ -71,7 +71,7 @@ struct IntrusiveRefCounts
|
||||
expired() const noexcept;
|
||||
|
||||
std::size_t
|
||||
use_count() const noexcept;
|
||||
useCount() const noexcept;
|
||||
|
||||
// This function MUST be called after a partial destructor finishes running.
|
||||
// Calling this function may cause other threads to delete the object
|
||||
@@ -98,11 +98,11 @@ private:
|
||||
// enough for strong pointers and 14 bit counts are enough for weak
|
||||
// pointers. Use type aliases to make it easy to switch types.
|
||||
using CountType = std::uint16_t;
|
||||
static constexpr size_t StrongCountNumBits = sizeof(CountType) * 8;
|
||||
static constexpr size_t WeakCountNumBits = StrongCountNumBits - 2;
|
||||
static constexpr size_t kStrongCountNumBits = sizeof(CountType) * 8;
|
||||
static constexpr size_t kWeakCountNumBits = kStrongCountNumBits - 2;
|
||||
using FieldType = std::uint32_t;
|
||||
static constexpr size_t FieldTypeBits = sizeof(FieldType) * 8;
|
||||
static constexpr FieldType one = 1;
|
||||
static constexpr size_t kFieldTypeBits = sizeof(FieldType) * 8;
|
||||
static constexpr FieldType kOne = 1;
|
||||
|
||||
/** `refCounts` consists of four fields that are treated atomically:
|
||||
|
||||
@@ -137,21 +137,21 @@ private:
|
||||
|
||||
*/
|
||||
|
||||
mutable std::atomic<FieldType> refCounts{strongDelta};
|
||||
mutable std::atomic<FieldType> refCounts_{kStrongDelta};
|
||||
|
||||
/** Amount to change the strong count when adding or releasing a reference
|
||||
|
||||
Note: The strong count is stored in the low `StrongCountNumBits` bits
|
||||
of refCounts
|
||||
*/
|
||||
static constexpr FieldType strongDelta = 1;
|
||||
static constexpr FieldType kStrongDelta = 1;
|
||||
|
||||
/** Amount to change the weak count when adding or releasing a reference
|
||||
|
||||
Note: The weak count is stored in the high `WeakCountNumBits` bits of
|
||||
refCounts
|
||||
*/
|
||||
static constexpr FieldType weakDelta = (one << StrongCountNumBits);
|
||||
static constexpr FieldType kWeakDelta = (kOne << kStrongCountNumBits);
|
||||
|
||||
/** Flag that is set when the partialDestroy function has started running
|
||||
(or is about to start running).
|
||||
@@ -159,33 +159,33 @@ private:
|
||||
See description of the `refCounts` field for a fuller description of
|
||||
this field.
|
||||
*/
|
||||
static constexpr FieldType partialDestroyStartedMask = (one << (FieldTypeBits - 1));
|
||||
static constexpr FieldType kPartialDestroyStartedMask = (kOne << (kFieldTypeBits - 1));
|
||||
|
||||
/** Flag that is set when the partialDestroy function has finished running
|
||||
|
||||
See description of the `refCounts` field for a fuller description of
|
||||
this field.
|
||||
*/
|
||||
static constexpr FieldType partialDestroyFinishedMask = (one << (FieldTypeBits - 2));
|
||||
static constexpr FieldType kPartialDestroyFinishedMask = (kOne << (kFieldTypeBits - 2));
|
||||
|
||||
/** Mask that will zero out all the `count` bits and leave the tag bits
|
||||
unchanged.
|
||||
*/
|
||||
static constexpr FieldType tagMask = partialDestroyStartedMask | partialDestroyFinishedMask;
|
||||
static constexpr FieldType kTagMask = kPartialDestroyStartedMask | kPartialDestroyFinishedMask;
|
||||
|
||||
/** Mask that will zero out the `tag` bits and leave the count bits
|
||||
unchanged.
|
||||
*/
|
||||
static constexpr FieldType valueMask = ~tagMask;
|
||||
static constexpr FieldType kValueMask = ~kTagMask;
|
||||
|
||||
/** Mask that will zero out everything except the strong count.
|
||||
*/
|
||||
static constexpr FieldType strongMask = ((one << StrongCountNumBits) - 1) & valueMask;
|
||||
static constexpr FieldType kStrongMask = ((kOne << kStrongCountNumBits) - 1) & kValueMask;
|
||||
|
||||
/** Mask that will zero out everything except the weak count.
|
||||
*/
|
||||
static constexpr FieldType weakMask =
|
||||
(((one << WeakCountNumBits) - 1) << StrongCountNumBits) & valueMask;
|
||||
static constexpr FieldType kWeakMask =
|
||||
(((kOne << kWeakCountNumBits) - 1) << kStrongCountNumBits) & kValueMask;
|
||||
|
||||
/** Unpack the count and tag fields from the packed atomic integer form. */
|
||||
struct RefCountPair
|
||||
@@ -207,32 +207,32 @@ private:
|
||||
RefCountPair(CountType s, CountType w) noexcept;
|
||||
|
||||
/** Convert back to the packed integer form. */
|
||||
FieldType
|
||||
[[nodiscard]] FieldType
|
||||
combinedValue() const noexcept;
|
||||
|
||||
static constexpr CountType maxStrongValue =
|
||||
static_cast<CountType>((one << StrongCountNumBits) - 1);
|
||||
static constexpr CountType maxWeakValue =
|
||||
static_cast<CountType>((one << WeakCountNumBits) - 1);
|
||||
static constexpr CountType kMaxStrongValue =
|
||||
static_cast<CountType>((kOne << kStrongCountNumBits) - 1);
|
||||
static constexpr CountType kMaxWeakValue =
|
||||
static_cast<CountType>((kOne << kWeakCountNumBits) - 1);
|
||||
/** Put an extra margin to detect when running up against limits.
|
||||
This is only used in debug code, and is useful if we reduce the
|
||||
number of bits in the strong and weak counts (to 16 and 14 bits).
|
||||
*/
|
||||
static constexpr CountType checkStrongMaxValue = maxStrongValue - 32;
|
||||
static constexpr CountType checkWeakMaxValue = maxWeakValue - 32;
|
||||
static constexpr CountType kCheckStrongMaxValue = kMaxStrongValue - 32;
|
||||
static constexpr CountType kCheckWeakMaxValue = kMaxWeakValue - 32;
|
||||
};
|
||||
};
|
||||
|
||||
inline void
|
||||
IntrusiveRefCounts::addStrongRef() const noexcept
|
||||
{
|
||||
refCounts.fetch_add(strongDelta, std::memory_order_acq_rel);
|
||||
refCounts_.fetch_add(kStrongDelta, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
inline void
|
||||
IntrusiveRefCounts::addWeakRef() const noexcept
|
||||
{
|
||||
refCounts.fetch_add(weakDelta, std::memory_order_acq_rel);
|
||||
refCounts_.fetch_add(kWeakDelta, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
inline ReleaseStrongRefAction
|
||||
@@ -246,36 +246,36 @@ IntrusiveRefCounts::releaseStrongRef() const
|
||||
// conditional `fetch_or`. This loop will almost always run once.
|
||||
|
||||
using enum ReleaseStrongRefAction;
|
||||
auto prevIntVal = refCounts.load(std::memory_order_acquire);
|
||||
auto prevIntVal = refCounts_.load(std::memory_order_acquire);
|
||||
while (true)
|
||||
{
|
||||
RefCountPair const prevVal{prevIntVal};
|
||||
XRPL_ASSERT(
|
||||
(prevVal.strong >= strongDelta),
|
||||
(prevVal.strong >= kStrongDelta),
|
||||
"xrpl::IntrusiveRefCounts::releaseStrongRef : previous ref "
|
||||
"higher than new");
|
||||
auto nextIntVal = prevIntVal - strongDelta;
|
||||
ReleaseStrongRefAction action = noop;
|
||||
auto nextIntVal = prevIntVal - kStrongDelta;
|
||||
ReleaseStrongRefAction action = NoOp;
|
||||
if (prevVal.strong == 1)
|
||||
{
|
||||
if (prevVal.weak == 0)
|
||||
{
|
||||
action = destroy;
|
||||
action = Destroy;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextIntVal |= partialDestroyStartedMask;
|
||||
action = partialDestroy;
|
||||
nextIntVal |= kPartialDestroyStartedMask;
|
||||
action = PartialDestroy;
|
||||
}
|
||||
}
|
||||
|
||||
if (refCounts.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel))
|
||||
if (refCounts_.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel))
|
||||
{
|
||||
// Can't be in partial destroy because only decrementing the strong
|
||||
// count to zero can start a partial destroy, and that can't happen
|
||||
// twice.
|
||||
XRPL_ASSERT(
|
||||
(action == noop) || !(prevIntVal & partialDestroyStartedMask),
|
||||
(action == NoOp) || !(prevIntVal & kPartialDestroyStartedMask),
|
||||
"xrpl::IntrusiveRefCounts::releaseStrongRef : not in partial "
|
||||
"destroy");
|
||||
return action;
|
||||
@@ -288,9 +288,9 @@ IntrusiveRefCounts::addWeakReleaseStrongRef() const
|
||||
{
|
||||
using enum ReleaseStrongRefAction;
|
||||
|
||||
static_assert(weakDelta > strongDelta);
|
||||
auto constexpr delta = weakDelta - strongDelta;
|
||||
auto prevIntVal = refCounts.load(std::memory_order_acquire);
|
||||
static_assert(kWeakDelta > kStrongDelta);
|
||||
static constexpr auto kDelta = kWeakDelta - kStrongDelta;
|
||||
auto prevIntVal = refCounts_.load(std::memory_order_acquire);
|
||||
// This loop will almost always run once. The loop is needed to atomically
|
||||
// change the counts and flags (the count could be atomically changed, but
|
||||
// the flags depend on the current value of the counts).
|
||||
@@ -311,24 +311,24 @@ IntrusiveRefCounts::addWeakReleaseStrongRef() const
|
||||
"xrpl::IntrusiveRefCounts::addWeakReleaseStrongRef : not in "
|
||||
"partial destroy");
|
||||
|
||||
auto nextIntVal = prevIntVal + delta;
|
||||
ReleaseStrongRefAction action = noop;
|
||||
auto nextIntVal = prevIntVal + kDelta;
|
||||
ReleaseStrongRefAction action = NoOp;
|
||||
if (prevVal.strong == 1)
|
||||
{
|
||||
if (prevVal.weak == 0)
|
||||
{
|
||||
action = noop;
|
||||
action = NoOp;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextIntVal |= partialDestroyStartedMask;
|
||||
action = partialDestroy;
|
||||
nextIntVal |= kPartialDestroyStartedMask;
|
||||
action = PartialDestroy;
|
||||
}
|
||||
}
|
||||
if (refCounts.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel))
|
||||
if (refCounts_.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(!(prevIntVal & partialDestroyStartedMask)),
|
||||
(!(prevIntVal & kPartialDestroyStartedMask)),
|
||||
"xrpl::IntrusiveRefCounts::addWeakReleaseStrongRef : not "
|
||||
"started partial destroy");
|
||||
return action;
|
||||
@@ -339,7 +339,7 @@ IntrusiveRefCounts::addWeakReleaseStrongRef() const
|
||||
inline ReleaseWeakRefAction
|
||||
IntrusiveRefCounts::releaseWeakRef() const
|
||||
{
|
||||
auto prevIntVal = refCounts.fetch_sub(weakDelta, std::memory_order_acq_rel);
|
||||
auto prevIntVal = refCounts_.fetch_sub(kWeakDelta, std::memory_order_acq_rel);
|
||||
RefCountPair prev = prevIntVal;
|
||||
if (prev.weak == 1 && prev.strong == 0)
|
||||
{
|
||||
@@ -348,19 +348,19 @@ IntrusiveRefCounts::releaseWeakRef() const
|
||||
// This case should only be hit if the partialDestroyStartedBit is
|
||||
// set non-atomically (and even then very rarely). The code is kept
|
||||
// in case we need to set the flag non-atomically for perf reasons.
|
||||
refCounts.wait(prevIntVal, std::memory_order_acquire);
|
||||
prevIntVal = refCounts.load(std::memory_order_acquire);
|
||||
refCounts_.wait(prevIntVal, std::memory_order_acquire);
|
||||
prevIntVal = refCounts_.load(std::memory_order_acquire);
|
||||
prev = RefCountPair{prevIntVal};
|
||||
}
|
||||
if (prev.partialDestroyFinishedBit == 0u)
|
||||
{
|
||||
// partial destroy MUST finish before running a full destroy (when
|
||||
// using weak pointers)
|
||||
refCounts.wait(prevIntVal - weakDelta, std::memory_order_acquire);
|
||||
refCounts_.wait(prevIntVal - kWeakDelta, std::memory_order_acquire);
|
||||
}
|
||||
return ReleaseWeakRefAction::destroy;
|
||||
return ReleaseWeakRefAction::Destroy;
|
||||
}
|
||||
return ReleaseWeakRefAction::noop;
|
||||
return ReleaseWeakRefAction::NoOp;
|
||||
}
|
||||
|
||||
inline bool
|
||||
@@ -369,13 +369,13 @@ IntrusiveRefCounts::checkoutStrongRefFromWeak() const noexcept
|
||||
auto curValue = RefCountPair{1, 1}.combinedValue();
|
||||
auto desiredValue = RefCountPair{2, 1}.combinedValue();
|
||||
|
||||
while (!refCounts.compare_exchange_weak(curValue, desiredValue, std::memory_order_acq_rel))
|
||||
while (!refCounts_.compare_exchange_weak(curValue, desiredValue, std::memory_order_acq_rel))
|
||||
{
|
||||
RefCountPair const prev{curValue};
|
||||
if (prev.strong == 0u)
|
||||
return false;
|
||||
|
||||
desiredValue = curValue + strongDelta;
|
||||
desiredValue = curValue + kStrongDelta;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -383,38 +383,38 @@ IntrusiveRefCounts::checkoutStrongRefFromWeak() const noexcept
|
||||
inline bool
|
||||
IntrusiveRefCounts::expired() const noexcept
|
||||
{
|
||||
RefCountPair const val = refCounts.load(std::memory_order_acquire);
|
||||
RefCountPair const val = refCounts_.load(std::memory_order_acquire);
|
||||
return val.strong == 0;
|
||||
}
|
||||
|
||||
inline std::size_t
|
||||
IntrusiveRefCounts::use_count() const noexcept
|
||||
IntrusiveRefCounts::useCount() const noexcept
|
||||
{
|
||||
RefCountPair const val = refCounts.load(std::memory_order_acquire);
|
||||
RefCountPair const val = refCounts_.load(std::memory_order_acquire);
|
||||
return val.strong;
|
||||
}
|
||||
|
||||
inline IntrusiveRefCounts::~IntrusiveRefCounts() noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
auto v = refCounts.load(std::memory_order_acquire);
|
||||
auto v = refCounts_.load(std::memory_order_acquire);
|
||||
XRPL_ASSERT(
|
||||
(!(v & valueMask)), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : count must be zero");
|
||||
auto t = v & tagMask;
|
||||
XRPL_ASSERT((!t || t == tagMask), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : valid tag");
|
||||
(!(v & kValueMask)), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : count must be zero");
|
||||
auto t = v & kTagMask;
|
||||
XRPL_ASSERT((!t || t == kTagMask), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : valid tag");
|
||||
#endif
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
inline IntrusiveRefCounts::RefCountPair::RefCountPair(IntrusiveRefCounts::FieldType v) noexcept
|
||||
: strong{static_cast<CountType>(v & strongMask)}
|
||||
, weak{static_cast<CountType>((v & weakMask) >> StrongCountNumBits)}
|
||||
, partialDestroyStartedBit{v & partialDestroyStartedMask}
|
||||
, partialDestroyFinishedBit{v & partialDestroyFinishedMask}
|
||||
: strong{static_cast<CountType>(v & kStrongMask)}
|
||||
, weak{static_cast<CountType>((v & kWeakMask) >> kStrongCountNumBits)}
|
||||
, partialDestroyStartedBit{v & kPartialDestroyStartedMask}
|
||||
, partialDestroyFinishedBit{v & kPartialDestroyFinishedMask}
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(strong < checkStrongMaxValue && weak < checkWeakMaxValue),
|
||||
(strong < kCheckStrongMaxValue && weak < kCheckWeakMaxValue),
|
||||
"xrpl::IntrusiveRefCounts::RefCountPair(FieldType) : inputs inside "
|
||||
"range");
|
||||
}
|
||||
@@ -425,7 +425,7 @@ inline IntrusiveRefCounts::RefCountPair::RefCountPair(
|
||||
: strong{s}, weak{w}
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(strong < checkStrongMaxValue && weak < checkWeakMaxValue),
|
||||
(strong < kCheckStrongMaxValue && weak < kCheckWeakMaxValue),
|
||||
"xrpl::IntrusiveRefCounts::RefCountPair(CountType, CountType) : "
|
||||
"inputs inside range");
|
||||
}
|
||||
@@ -434,11 +434,11 @@ inline IntrusiveRefCounts::FieldType
|
||||
IntrusiveRefCounts::RefCountPair::combinedValue() const noexcept
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(strong < checkStrongMaxValue && weak < checkWeakMaxValue),
|
||||
(strong < kCheckStrongMaxValue && weak < kCheckWeakMaxValue),
|
||||
"xrpl::IntrusiveRefCounts::RefCountPair::combinedValue : inputs "
|
||||
"inside range");
|
||||
return (static_cast<IntrusiveRefCounts::FieldType>(weak)
|
||||
<< IntrusiveRefCounts::StrongCountNumBits) |
|
||||
<< IntrusiveRefCounts::kStrongCountNumBits) |
|
||||
static_cast<IntrusiveRefCounts::FieldType>(strong) | partialDestroyStartedBit |
|
||||
partialDestroyFinishedBit;
|
||||
}
|
||||
@@ -449,7 +449,7 @@ partialDestructorFinished(T** o)
|
||||
{
|
||||
T& self = **o;
|
||||
IntrusiveRefCounts::RefCountPair const p =
|
||||
self.refCounts.fetch_or(IntrusiveRefCounts::partialDestroyFinishedMask);
|
||||
self.refCounts_.fetch_or(IntrusiveRefCounts::kPartialDestroyFinishedMask);
|
||||
XRPL_ASSERT(
|
||||
(!p.partialDestroyFinishedBit && p.partialDestroyStartedBit && !p.strong),
|
||||
"xrpl::partialDestructorFinished : not a weak ref");
|
||||
@@ -458,7 +458,7 @@ partialDestructorFinished(T** o)
|
||||
// There was a weak count before the partial destructor ran (or we would
|
||||
// have run the full destructor) and now there isn't a weak count. Some
|
||||
// thread is waiting to run the destructor.
|
||||
self.refCounts.notify_one();
|
||||
self.refCounts_.notify_one();
|
||||
}
|
||||
// Set the pointer to null to emphasize that the object shouldn't be used
|
||||
// after calling this function as it may be destroyed in another thread.
|
||||
|
||||
118
include/xrpl/basics/IntrusiveRefCounts.h.ai.json
Normal file
118
include/xrpl/basics/IntrusiveRefCounts.h.ai.json
Normal file
@@ -0,0 +1,118 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 272,
|
||||
"name": "v"
|
||||
},
|
||||
{
|
||||
"lineno": 282,
|
||||
"name": "s"
|
||||
},
|
||||
{
|
||||
"lineno": 282,
|
||||
"name": "w"
|
||||
},
|
||||
{
|
||||
"lineno": 299,
|
||||
"name": "o"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 38,
|
||||
"name": "IntrusiveRefCounts"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"FieldType v",
|
||||
"CountType s, CountType w"
|
||||
],
|
||||
"lineno": 101,
|
||||
"name": "IntrusiveRefCounts::RefCountPair"
|
||||
}
|
||||
],
|
||||
"description": "Implements atomic reference counting for intrusive smart pointers, including strong and weak reference management, partial destruction, and thread-safe operations for use in the XRPL codebase.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/IntrusiveRefCounts.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 120,
|
||||
"name": "IntrusiveRefCounts::addStrongRef"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 125,
|
||||
"name": "IntrusiveRefCounts::addWeakRef"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 130,
|
||||
"name": "IntrusiveRefCounts::releaseStrongRef"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 170,
|
||||
"name": "IntrusiveRefCounts::addWeakReleaseStrongRef"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 210,
|
||||
"name": "IntrusiveRefCounts::releaseWeakRef"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 235,
|
||||
"name": "IntrusiveRefCounts::checkoutStrongRefFromWeak"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 248,
|
||||
"name": "IntrusiveRefCounts::expired"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 253,
|
||||
"name": "IntrusiveRefCounts::use_count"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 258,
|
||||
"name": "IntrusiveRefCounts::~IntrusiveRefCounts"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"IntrusiveRefCounts::FieldType v"
|
||||
],
|
||||
"lineno": 271,
|
||||
"name": "IntrusiveRefCounts::RefCountPair::RefCountPair"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"IntrusiveRefCounts::CountType s",
|
||||
"IntrusiveRefCounts::CountType w"
|
||||
],
|
||||
"lineno": 281,
|
||||
"name": "IntrusiveRefCounts::RefCountPair::RefCountPair"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 289,
|
||||
"name": "IntrusiveRefCounts::RefCountPair::combinedValue"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T** o"
|
||||
],
|
||||
"lineno": 299,
|
||||
"name": "partialDestructorFinished"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
56
include/xrpl/basics/IntrusiveRefCounts.h.ai.md
Normal file
56
include/xrpl/basics/IntrusiveRefCounts.h.ai.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# `IntrusiveRefCounts.h` — Atomic Reference Counting for Intrusive Smart Pointers
|
||||
|
||||
## Role in the System
|
||||
|
||||
`IntrusiveRefCounts` is the reference-counting backbone for XRPL's custom intrusive smart pointer family: `SharedIntrusive<T>`, `WeakIntrusive<T>`, and `SharedWeakUnion<T>`, all defined in `IntrusivePointer.h`. A class participates in this system simply by inheriting from `IntrusiveRefCounts`; the notable consumer is `SHAMapInnerNode`, whose billions-of-ops-per-second traversal patterns make every byte and every allocation matter.
|
||||
|
||||
The design answers a specific criticism of `std::shared_ptr`: with `make_shared`, the control block and the object are co-allocated, which is efficient, but the memory cannot be reclaimed until the weak count also hits zero. XRPL's intrusive design embeds the counts directly in the object, saves the separate control-block allocation, and — crucially — introduces a *partial destruction* protocol that lets the object shed its expensive payload (e.g., child node pointers) the moment the strong count reaches zero, even while weak pointers keep the shell alive.
|
||||
|
||||
## The Packed Atomic Field
|
||||
|
||||
All state lives in a single `mutable std::atomic<uint32_t> refCounts`. The 32 bits are divided as follows:
|
||||
|
||||
| Bits | Field |
|
||||
|------|-------|
|
||||
| 0–15 | Strong count (16 bits) |
|
||||
| 16–29 | Weak count (14 bits) |
|
||||
| 30 | `partialDestroyStartedBit` |
|
||||
| 31 | `partialDestroyFinishedBit` |
|
||||
|
||||
Packing everything into one atomic word means that decrementing a count *and* setting a flag can be done as a single compare-and-swap, eliminating any window between the two operations that a second atomic would expose. The helper struct `RefCountPair` wraps the unpack/repack logic: its constructor extracts each field by masking and shifting, and `combinedValue()` reassembles them.
|
||||
|
||||
## Destruction Lifecycle and the Partial Destructor
|
||||
|
||||
When `releaseStrongRef()` finds that the strong count is dropping to exactly one (i.e., to zero), it branches on whether any weak references exist:
|
||||
|
||||
- **No weak refs**: returns `ReleaseStrongRefAction::destroy`. The caller (in `IntrusivePointer.ipp`) runs `delete ptr`, calling the full destructor.
|
||||
- **Weak refs present**: atomically sets `partialDestroyStartedBit` *in the same CAS that decrements the count*, then returns `partialDestroy`. The caller invokes `ptr->partialDestructor()`, then calls the free function `partialDestructorFinished(&ptr)`.
|
||||
|
||||
This CAS loop in `releaseStrongRef()` almost always executes once; looping is necessary only if another thread modifies `refCounts` between the `load` and `compare_exchange_weak`.
|
||||
|
||||
`partialDestructorFinished()` is a `friend` function template declared in the class. It atomically sets `partialDestroyFinishedBit` via `fetch_or`, then — if the weak count is already zero at that point — calls `refCounts.notify_one()` to wake any thread blocked in `releaseWeakRef()`. The intentional `T**` (double-pointer) signature is a deliberate API ergonomic signal: after the call, `*o` is set to `nullptr` to discourage use-after-free, because another thread may immediately delete the object upon seeing the finished bit.
|
||||
|
||||
## Why Two Bits for Partial Destroy
|
||||
|
||||
There is a genuine race that a single bit cannot handle. Consider:
|
||||
|
||||
1. Thread A: last strong pointer releases → strong count goes to zero, weak count is 1, `partialDestroyStarted` is about to be set (but has not been set yet).
|
||||
2. Thread B: last weak pointer releases → sees `strong == 0`, `weak == 1 → 0` → would naively call the full destructor, racing with Thread A's partial destructor.
|
||||
|
||||
The `partialDestroyStarted` bit, set atomically in the same CAS that decrements the strong count, prevents Thread B from proceeding until the partial destructor's state is stable. The `partialDestroyFinished` bit then lets `releaseWeakRef()` know it is safe to destroy. When neither bit is set, `releaseWeakRef()` calls `refCounts.wait(…)` — a futex-style block on the atomic value — and rechecks upon wake-up.
|
||||
|
||||
## Key Operations
|
||||
|
||||
**`addStrongRef()` / `addWeakRef()`** use `fetch_add` with `acq_rel` ordering — the common fast path that requires no CAS loop.
|
||||
|
||||
**`addWeakReleaseStrongRef()`** is an atomic composite operation needed when `SharedWeakUnion` converts itself from a strong to a weak reference. Doing these as two separate operations would create a transient moment where the object has neither kind of reference holding it, which could trigger premature destruction. The implementation computes `weakDelta - strongDelta` and applies it as one delta in a CAS loop, while still setting `partialDestroyStartedBit` if appropriate.
|
||||
|
||||
**`checkoutStrongRefFromWeak()`** implements `lock()` semantics: it atomically increments the strong count, but only if the strong count is currently nonzero. If it reaches zero between load and CAS, the loop exits with `false`, signalling that the object is already being destroyed.
|
||||
|
||||
## Design Notes and Tradeoffs
|
||||
|
||||
The `addStrongRef()` is `noexcept` by requirement: `make_SharedIntrusive` calls it immediately after `new T(...)`, and if it could throw, the freshly allocated object would leak. The `static_assert` in `make_SharedIntrusive` enforces this.
|
||||
|
||||
The `uint16_t` strong count cap of 65 535 and 14-bit weak count cap of 16 383 are annotated with a `TODO`: if audit reveals these are insufficient, both types would need to widen to `uint32_t`, moving the entire atomic to `uint64_t`. The `checkStrongMaxValue` and `checkWeakMaxValue` constants leave a 32-unit margin below the hard cap, and debug-mode assertions in `RefCountPair`'s constructors fire before the actual overflow, providing early warning.
|
||||
|
||||
The `partialDestructorFinished()` free function is deliberately *not* called at the end of the `partialDestructor()` virtual method itself. This means any class that inherits `IntrusiveRefCounts` and implements its own `partialDestructor()` must explicitly call `partialDestructorFinished()` when done. The comment explains this was chosen to make the protocol visible at the call site rather than hiding it inside the smart-pointer machinery, reducing the chance that new subclasses silently skip the required notification.
|
||||
14
include/xrpl/basics/KeyCache.h.ai.json
Normal file
14
include/xrpl/basics/KeyCache.h.ai.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"args": [],
|
||||
"classes": [],
|
||||
"description": "Defines a type alias KeyCache as a TaggedCache specialized for uint256 keys and int values within the xrpl namespace.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/KeyCache.h",
|
||||
"functions": [],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 5,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
include/xrpl/basics/KeyCache.h.ai.md
Normal file
13
include/xrpl/basics/KeyCache.h.ai.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# `KeyCache.h` — Key-Only Cache Type Alias
|
||||
|
||||
`KeyCache.h` provides a single-line type alias that expresses a common, narrow caching pattern throughout the XRPL codebase: track the *presence* of `uint256` keys over time, without attaching any value to them.
|
||||
|
||||
```cpp
|
||||
using KeyCache = TaggedCache<uint256, int, true>;
|
||||
```
|
||||
|
||||
The three template arguments to `TaggedCache` are: the key type (`uint256`, the 256-bit hash used pervasively in XRPL), a value type placeholder (`int`), and the `IsKeyCache` boolean flag set to `true`. That third argument is the critical one. Inside `TaggedCache`, `IsKeyCache = true` activates a compile-time branch via `std::conditional` that substitutes `KeyOnlyEntry` — a struct holding nothing but a `last_access` timestamp — in place of the full `ValueEntry` that carries a `shared_ptr` to an actual object. The `int` value type is therefore never stored or accessed; it exists only to satisfy the template parameter list.
|
||||
|
||||
This design lets callers answer a single yes/no question efficiently: *"Have I seen this hash recently enough that I don't need to re-check it?"* The primary consumer is `FullBelowCache` in `include/xrpl/shamap/FullBelowCache.h`, which uses a `KeyCache` to remember which SHAMap tree nodes have all their descendants resident in the database. When a node is marked "full below," the ledger acquisition machinery can skip redundant subtree traversals, a meaningful performance win during sync.
|
||||
|
||||
Because `KeyCache` is built directly on `TaggedCache`, it inherits the full sweep-based expiry mechanism, thread-safe access under `std::recursive_mutex`, and the `touch_if_exists` / `insert` interface — with `insert` enabled only in the key-only overload path when `IsKeyCache` is `true`.
|
||||
@@ -26,17 +26,17 @@ struct LocalValues
|
||||
template <class T>
|
||||
struct Value : BasicValue
|
||||
{
|
||||
T t_;
|
||||
T t;
|
||||
|
||||
Value() = default;
|
||||
explicit Value(T t) : t_(std::move(t))
|
||||
explicit Value(T t) : t(std::move(t))
|
||||
{
|
||||
}
|
||||
|
||||
void*
|
||||
get() override
|
||||
{
|
||||
return &t_;
|
||||
return &t;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,8 +55,8 @@ template <class = void>
|
||||
boost::thread_specific_ptr<detail::LocalValues>&
|
||||
getLocalValues()
|
||||
{
|
||||
static boost::thread_specific_ptr<detail::LocalValues> tsp(&detail::LocalValues::cleanup);
|
||||
return tsp;
|
||||
static boost::thread_specific_ptr<detail::LocalValues> kTsp(&detail::LocalValues::cleanup);
|
||||
return kTsp;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
76
include/xrpl/basics/LocalValue.h.ai.json
Normal file
76
include/xrpl/basics/LocalValue.h.ai.json
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 34,
|
||||
"name": "lvs"
|
||||
},
|
||||
{
|
||||
"lineno": 53,
|
||||
"name": "Args... args"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 9,
|
||||
"name": "LocalValues"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 15,
|
||||
"name": "BasicValue"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T",
|
||||
"t"
|
||||
],
|
||||
"lineno": 21,
|
||||
"name": "Value"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Args... args"
|
||||
],
|
||||
"lineno": 52,
|
||||
"name": "LocalValue"
|
||||
}
|
||||
],
|
||||
"description": "Implements a thread- and coroutine-local storage utility for storing values specific to the calling thread or coroutine, using boost::thread_specific_ptr and custom value wrappers.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/LocalValue.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"lvs"
|
||||
],
|
||||
"lineno": 34,
|
||||
"name": "cleanup"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 41,
|
||||
"name": "getLocalValues"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 67,
|
||||
"name": "operator*"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 61,
|
||||
"name": "operator->"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
},
|
||||
{
|
||||
"lineno": 8,
|
||||
"name": "detail"
|
||||
}
|
||||
]
|
||||
}
|
||||
64
include/xrpl/basics/LocalValue.h.ai.md
Normal file
64
include/xrpl/basics/LocalValue.h.ai.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# `LocalValue.h` — Coroutine-Aware Local Storage
|
||||
|
||||
## Role and Motivation
|
||||
|
||||
Standard `thread_local` storage is insufficient in a system that runs cooperative coroutines on a thread pool. XRPL's `JobQueue::Coro` coroutines (backed by `boost::coroutines2`) are created on one thread, suspended with `yield()`, and resumed — potentially on a different thread. If two coroutines share a worker thread, a naive `thread_local` variable would expose the state left behind by a previously suspended coroutine, producing subtle, hard-to-reproduce bugs.
|
||||
|
||||
`LocalValue<T>` solves this by providing **coroutine-aware local storage**: each coroutine (or plain thread, as a fallback) gets its own independent copy of a `T`, keyed to its execution context rather than its OS thread.
|
||||
|
||||
## Architecture
|
||||
|
||||
The design has two interlocking layers: a per-coroutine dictionary (`LocalValues`) managed via a thread-local pointer, and a typed wrapper (`LocalValue<T>`) that uses its own address as a dictionary key.
|
||||
|
||||
### `detail::LocalValues`
|
||||
|
||||
`LocalValues` is a runtime dictionary that maps `void const*` keys to heap-allocated `BasicValue` instances. The keys are the addresses of `LocalValue<T>` objects; the values hold a type-erased `Value<T>` (a concrete subclass of `BasicValue`) accessed through a virtual `get()` that returns `void*`. This type-erasure pattern lets a single heterogeneous map hold values of arbitrary types without any registry or type-ID scheme — the `LocalValue<T>` template handles all casts.
|
||||
|
||||
The `onCoro` flag distinguishes two ownership modes. When `onCoro == true`, the `LocalValues` is embedded directly in a `Coro` object (`detail::LocalValues lvs_`) and its lifetime is tied to the coroutine's. When `onCoro == false`, the `LocalValues` was heap-allocated for a plain thread worker, and the `cleanup()` deleter passed to `boost::thread_specific_ptr` will `delete` it on thread exit.
|
||||
|
||||
### Thread-Local Pointer Swap in `Coro::resume()`
|
||||
|
||||
The critical mechanism lives in `Coro.ipp`. Every time a coroutine is resumed, `Coro::resume()` performs a pointer swap:
|
||||
|
||||
```cpp
|
||||
auto saved = detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(&lvs_);
|
||||
// ... run coroutine body ...
|
||||
detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(saved);
|
||||
```
|
||||
|
||||
This installs the coroutine's private `lvs_` as the active `LocalValues` for the duration of the coroutine's time slice, then restores the previous state. Any `LocalValue<T>` dereference during that time slice will therefore see the coroutine's private dictionary. Because `lvs_` is owned by the `Coro` object and the `thread_specific_ptr` merely borrows it (it will not delete it thanks to `cleanup()`'s `onCoro` guard), there is no double-free risk.
|
||||
|
||||
### `LocalValue<T>` — The Public Interface
|
||||
|
||||
`LocalValue<T>` is a global or static object that holds a single "prototype" value `t_` set at construction time. The prototype is never mutated; it only serves as the initializer for per-context copies.
|
||||
|
||||
`operator*()` implements the lookup:
|
||||
|
||||
1. Call `detail::getLocalValues().get()` to retrieve (or lazily create) the `LocalValues` for the current context.
|
||||
2. If no `LocalValues` exists yet, allocate one with `onCoro = false` (plain thread path) and register it with the `thread_specific_ptr`.
|
||||
3. Search for `this` in `lvs->values`. On a hit, cast the `void*` back to `T&` and return it.
|
||||
4. On a miss, emplace a new `Value<T>` copy-initialized from `t_`, then return a reference to that new copy.
|
||||
|
||||
`operator->()` is a trivial forwarding wrapper to `operator*()`.
|
||||
|
||||
## Concrete Usage
|
||||
|
||||
In `IOUAmount.cpp`, `LocalValue<bool>` governs whether IOU arithmetic uses the newer `STNumber` code path or the legacy one. Wrapping this flag in a `LocalValue` rather than `thread_local` ensures that two coroutines executing concurrently on the same thread pool can independently select their arithmetic mode without one overwriting the other's flag:
|
||||
|
||||
```cpp
|
||||
static LocalValue<bool> r{true};
|
||||
```
|
||||
|
||||
The coroutine test in `Coroutine_test.cpp` verifies the isolation property directly: four coroutines each set `*lv = id` (their own integer ID), interleave via yields, and confirm that no coroutine ever sees another's value. A plain-thread job running on the same pool also sees an independent copy.
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
**Address-keyed map instead of a registry.** Using `this` (the `LocalValue<T>*`) as the map key avoids any global registry or static ID counter. Each `LocalValue<T>` is naturally unique by address, and the approach requires no synchronization beyond what the `thread_specific_ptr` already provides.
|
||||
|
||||
**Lazy allocation.** The per-context copy is created on first dereference rather than at construction. This keeps coroutine startup cheap when only some `LocalValue` instances are actually accessed.
|
||||
|
||||
**`void*` type erasure with `unique_ptr<BasicValue>`.** A single `unordered_map` can hold values of unrelated types (`bool`, `int`, custom structs) because ownership and destruction are managed through the virtual destructor of `BasicValue`. The `LocalValue<T>` template retains type knowledge and performs the cast safely — the key equality guarantees that the `void*` behind a given key will always be a `T*`.
|
||||
|
||||
**`onCoro` ownership flag.** The asymmetry between coroutine-owned and thread-owned `LocalValues` is handled by a single boolean rather than two separate code paths or a custom deleter per instance. The `boost::thread_specific_ptr` deleter is fixed at static-initialization time, so the flag is the only extensible hook available.
|
||||
@@ -10,23 +10,11 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// DEPRECATED use beast::severities::Severity instead
|
||||
enum LogSeverity {
|
||||
lsINVALID = -1, // used to indicate an invalid severity
|
||||
lsTRACE = 0, // Very low-level progress information, details inside
|
||||
// an operation
|
||||
lsDEBUG = 1, // Function-level progress information, operations
|
||||
lsINFO = 2, // Server-level progress information, major operations
|
||||
lsWARNING = 3, // Conditions that warrant human attention, may indicate
|
||||
// a problem
|
||||
lsERROR = 4, // A condition that indicates a problem
|
||||
lsFATAL = 5 // A severe condition that indicates a server problem
|
||||
};
|
||||
|
||||
/** Manages partitions for logging. */
|
||||
class Logs
|
||||
{
|
||||
@@ -38,17 +26,17 @@ private:
|
||||
std::string partition_;
|
||||
|
||||
public:
|
||||
Sink(std::string partition, beast::severities::Severity thresh, Logs& logs);
|
||||
Sink(std::string partition, beast::Severity thresh, Logs& logs);
|
||||
|
||||
Sink(Sink const&) = delete;
|
||||
Sink&
|
||||
operator=(Sink const&) = delete;
|
||||
|
||||
void
|
||||
write(beast::severities::Severity level, std::string const& text) override;
|
||||
write(beast::Severity level, std::string const& text) override;
|
||||
|
||||
void
|
||||
writeAlways(beast::severities::Severity level, std::string const& text) override;
|
||||
writeAlways(beast::Severity level, std::string const& text) override;
|
||||
};
|
||||
|
||||
/** Manages a system file containing logged output.
|
||||
@@ -76,7 +64,7 @@ private:
|
||||
@return `true` if a system file is associated and opened for
|
||||
writing.
|
||||
*/
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
isOpen() const noexcept;
|
||||
|
||||
/** Associate a system file with the log.
|
||||
@@ -129,18 +117,18 @@ private:
|
||||
/** @} */
|
||||
|
||||
private:
|
||||
std::unique_ptr<std::ofstream> m_stream;
|
||||
boost::filesystem::path m_path;
|
||||
std::unique_ptr<std::ofstream> stream_;
|
||||
boost::filesystem::path path_;
|
||||
};
|
||||
|
||||
std::mutex mutable mutex_;
|
||||
std::map<std::string, std::unique_ptr<beast::Journal::Sink>, boost::beast::iless> sinks_;
|
||||
beast::severities::Severity thresh_;
|
||||
beast::Severity thresh_;
|
||||
File file_;
|
||||
bool silent_ = false;
|
||||
|
||||
public:
|
||||
Logs(beast::severities::Severity level);
|
||||
Logs(beast::Severity level);
|
||||
|
||||
Logs(Logs const&) = delete;
|
||||
Logs&
|
||||
@@ -160,18 +148,18 @@ public:
|
||||
beast::Journal
|
||||
journal(std::string const& name);
|
||||
|
||||
beast::severities::Severity
|
||||
beast::Severity
|
||||
threshold() const;
|
||||
|
||||
void
|
||||
threshold(beast::severities::Severity thresh);
|
||||
threshold(beast::Severity thresh);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>>
|
||||
partition_severities() const;
|
||||
partitionSeverities() const;
|
||||
|
||||
void
|
||||
write(
|
||||
beast::severities::Severity level,
|
||||
beast::Severity level,
|
||||
std::string const& partition,
|
||||
std::string const& text,
|
||||
bool console);
|
||||
@@ -191,34 +179,25 @@ public:
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<beast::Journal::Sink>
|
||||
makeSink(std::string const& partition, beast::severities::Severity startingLevel);
|
||||
makeSink(std::string const& partition, beast::Severity startingLevel);
|
||||
|
||||
public:
|
||||
static LogSeverity
|
||||
fromSeverity(beast::severities::Severity level);
|
||||
|
||||
static beast::severities::Severity
|
||||
toSeverity(LogSeverity level);
|
||||
|
||||
static std::string
|
||||
toString(LogSeverity s);
|
||||
toString(beast::Severity s);
|
||||
|
||||
static LogSeverity
|
||||
static std::optional<beast::Severity>
|
||||
fromString(std::string const& s);
|
||||
|
||||
private:
|
||||
enum {
|
||||
// Maximum line length for log messages.
|
||||
// If the message exceeds this length it will be truncated with
|
||||
// ellipses.
|
||||
maximumMessageCharacters = 12 * 1024
|
||||
};
|
||||
// Maximum line length for log messages.
|
||||
// If the message exceeds this length it will be truncated with ellipses.
|
||||
static constexpr auto kMaximumMessageCharacters = 12 * 1024;
|
||||
|
||||
static void
|
||||
format(
|
||||
std::string& output,
|
||||
std::string const& message,
|
||||
beast::severities::Severity severity,
|
||||
beast::Severity severity,
|
||||
std::string const& partition);
|
||||
};
|
||||
|
||||
|
||||
62
include/xrpl/basics/Log.h.ai.json
Normal file
62
include/xrpl/basics/Log.h.ai.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 36,
|
||||
"name": "partition"
|
||||
},
|
||||
{
|
||||
"lineno": 36,
|
||||
"name": "thresh"
|
||||
},
|
||||
{
|
||||
"lineno": 36,
|
||||
"name": "logs"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"level"
|
||||
],
|
||||
"lineno": 27,
|
||||
"name": "Logs"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"partition",
|
||||
"thresh",
|
||||
"logs"
|
||||
],
|
||||
"lineno": 33,
|
||||
"name": "Logs::Sink"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 61,
|
||||
"name": "Logs::File"
|
||||
}
|
||||
],
|
||||
"description": "This file defines logging utilities for the XRPL project, including log severity levels, a Logs manager class for handling log partitions and files, and macros/utilities for debug logging.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Log.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"sink"
|
||||
],
|
||||
"lineno": 168,
|
||||
"name": "setDebugLogSink"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 175,
|
||||
"name": "debugLog"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 18,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
57
include/xrpl/basics/Log.h.ai.md
Normal file
57
include/xrpl/basics/Log.h.ai.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# `include/xrpl/basics/Log.h` — Logging Infrastructure
|
||||
|
||||
## Role in the System
|
||||
|
||||
`Log.h` is the entry point for all structured logging in the XRPL C++ server. It defines the central `Logs` class, which acts as a factory and registry for named logging partitions, manages the output file, and controls severity thresholds. Every component in the server — consensus, ledger management, networking, transaction processing — acquires a `beast::Journal` handle from `Logs`, then uses that handle to write messages at the appropriate severity level without coupling to the output destination.
|
||||
|
||||
The header sits at the boundary between xrpl's own types and the `beast` logging primitives it builds on. `beast::Journal` (from `xrpl/beast/utility/Journal.h`) is the lightweight, copyable handle that individual subsystems carry; `Logs` is the stateful root that backs all those handles.
|
||||
|
||||
## The `beast::Journal` Layer
|
||||
|
||||
Before understanding `Logs`, it helps to understand what it produces. A `beast::Journal` is a non-owning, copyable pointer to a `beast::Journal::Sink`. The `Sink` is an abstract base providing two key operations: `write()` (filtered by threshold) and `writeAlways()` (bypasses the threshold check). The `Journal` itself offers named severity accessors — `trace()`, `debug()`, `info()`, `warn()`, `error()`, `fatal()` — each returning a `Journal::Stream` that implements `operator bool()` returning whether the level is currently active.
|
||||
|
||||
This architecture makes cheap caller-side checks possible: every `Stream` can be tested for activity before any string formatting happens.
|
||||
|
||||
## The `JLOG` Macro and Lazy Evaluation
|
||||
|
||||
The `JLOG` macro is the primary idiom for logging throughout the codebase:
|
||||
|
||||
```cpp
|
||||
JLOG(j.warn()) << "Computed value: " << expensiveFunction();
|
||||
```
|
||||
|
||||
Expanding to an `if (!x) {} else x` pattern, this ensures that if the `warn()` stream is below threshold, the `<<` chain is never evaluated. This is architecturally significant: without it, even a disabled `trace()` call would still serialize all arguments into a string, burning CPU in a hot path. The macro rather than an inline function avoids any overhead whatsoever at the disabled level — the compiler simply skips the branch. `CLOG` is a similar guard for optional pointer-style stream objects (used when the stream may be null).
|
||||
|
||||
## The `Logs` Class
|
||||
|
||||
`Logs` is constructed once per process with an initial severity threshold and acts as the single routing point for all log output. It maintains a `std::map<std::string, unique_ptr<Sink>>` keyed by partition name (subsystem label), with a `boost::beast::iless` comparator for case-insensitive lookup. This means code can request `"Ledger"` or `"ledger"` and get the same sink.
|
||||
|
||||
### Partition Sinks
|
||||
|
||||
The inner `Logs::Sink` class extends `beast::Journal::Sink`, carrying a partition name and a back-reference to its parent `Logs`. When `write()` is called on a sink, it delegates immediately to `Logs::write()`, which holds the shared `mutex_`, formats the message, writes to the log file, and — unless `silent_` is set — writes to `std::cerr`. The indirection through `Logs::write()` is what makes it possible to serialize all partition output through one lock and one file handle.
|
||||
|
||||
Callers acquire sinks via `Logs::get()` or `Logs::journal()`. The `get()` implementation uses `emplace()` on the map: if the partition already exists the existing sink is returned, otherwise a new one is created via the virtual `makeSink()` factory method. Because `makeSink()` is virtual, tests can subclass `Logs` to inject mock sinks without touching the rest of the system.
|
||||
|
||||
Changing the global threshold via `Logs::threshold(Severity)` updates `thresh_` and then iterates all existing sinks to propagate the change. This means a run-time config reload can silence verbose partitions or turn on trace output without restarting the server.
|
||||
|
||||
### File Output and Log Rotation
|
||||
|
||||
The nested `Logs::File` class wraps a `std::ofstream` opened in append mode. The design is intentionally minimal: `open()`, `close()`, `write()`, `writeln()`. The critical method is `closeAndReopen()`, which is the POSIX log rotation idiom: external tools like `logrotate(8)` rename the live log file and then send a signal to the server; the server responds by calling `Logs::rotate()`, which closes its file handle and reopens the path, creating a fresh file at the original location. The `File` class stores `m_path` specifically to enable this reopen without any external coordination.
|
||||
|
||||
### Message Formatting and Security Scrubbing
|
||||
|
||||
`Logs::format()` — a private static method called unconditionally before any write — prepends a timestamp and renders the severity as a three-letter tag (`TRC`, `DBG`, `NFO`, `WRN`, `ERR`, `FTL`), followed by the partition name. It then truncates messages exceeding 12 KB to prevent runaway log lines.
|
||||
|
||||
Critically, `format()` also performs **sensitive data redaction**. After composing the full output string, it scans for JSON keys `"seed"`, `"seed_hex"`, `"secret"`, `"master_key"`, `"master_seed"`, `"master_seed_hex"`, and `"passphrase"`, and replaces the associated quoted values with asterisks. This is a defensive measure to prevent operator errors from leaking wallet secrets into log files — even if a developer mistakenly logs a raw JSON RPC request containing signing keys.
|
||||
|
||||
## Deprecated `LogSeverity` Enum
|
||||
|
||||
The `LogSeverity` enum (`lsTRACE`, `lsDEBUG`, etc.) is a legacy mapping that predates the `beast::severities::Severity` enum. It is marked deprecated but kept for backwards compatibility, with `fromSeverity()` and `toSeverity()` providing the translation. The `fromString()` method accepts several aliases (`"warn"`, `"warning"`, `"warnings"`) using case-insensitive comparison, which serves the config file parser.
|
||||
|
||||
## Debug Logging
|
||||
|
||||
`setDebugLogSink()` and `debugLog()` provide a secondary, process-global logging channel backed by a thread-safe `DebugSink` Meyers singleton (defined in `Log.cpp`). This channel defaults to a null sink and is primarily for test code that wants to capture log output without constructing a full `Logs` instance. The documentation explicitly warns that output from `debugLog()` may never be seen — it is unsuitable for any information that matters operationally.
|
||||
|
||||
## Thread Safety
|
||||
|
||||
All mutable state in `Logs` — the sink map, the file handle, and the threshold — is protected by a single `mutable mutex_`. `beast::Journal` and `Stream` objects are copyable and safe to use across threads because they only dereference a `Sink*` for reads. The `DebugSink` in `Log.cpp` has its own separate mutex for swapping the debug sink atomically while concurrent `debugLog()` calls may be in flight.
|
||||
32
include/xrpl/basics/MallocTrim.h.ai.json
Normal file
32
include/xrpl/basics/MallocTrim.h.ai.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 54,
|
||||
"name": "tag"
|
||||
},
|
||||
{
|
||||
"lineno": 54,
|
||||
"name": "journal"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Provides a facility to attempt to return freed memory to the operating system by invoking malloc_trim on Linux/glibc, along with a report structure for before/after memory metrics.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/MallocTrim.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"tag",
|
||||
"journal"
|
||||
],
|
||||
"lineno": 54,
|
||||
"name": "mallocTrim"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
45
include/xrpl/basics/MallocTrim.h.ai.md
Normal file
45
include/xrpl/basics/MallocTrim.h.ai.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# `include/xrpl/basics/MallocTrim.h`
|
||||
|
||||
## Role in the System
|
||||
|
||||
This header is a targeted memory-pressure relief valve for the XRPL node process. On long-running servers, glibc's ptmalloc allocator can accumulate free pages in its internal arenas without returning them to the OS, causing the process's Resident Set Size (RSS) to drift upward over time even when application-level memory usage has genuinely declined. `MallocTrim.h` exposes a controlled wrapper around `::malloc_trim(0)` that reclaims that slack, paired with a diagnostic report so callers can observe what the operation actually accomplished.
|
||||
|
||||
## `MallocTrimReport`
|
||||
|
||||
The `MallocTrimReport` struct is the observability surface for a single trim invocation. All fields default to sentinel values (`-1` or `false`) that signal "not populated" rather than zero, which is important because a real RSS reading or page-fault count of zero is valid and meaningful. The fields capture:
|
||||
|
||||
- **`supported`** — whether the current platform supports trimming at all. This lets callers distinguish "trim ran and did nothing" from "trim was never attempted."
|
||||
- **`trimResult`** — the raw return value from `::malloc_trim`: `1` if the allocator actually released pages, `0` if there was nothing to release.
|
||||
- **`rssBeforeKB` / `rssAfterKB`** — process RSS read from `/proc/self/statm` bracketing the trim call, in kilobytes.
|
||||
- **`durationUs`** — wall-clock duration of the `::malloc_trim` call in microseconds, useful for detecting trim latency spikes.
|
||||
- **`minfltDelta` / `majfltDelta`** — thread-level minor and major page fault deltas (via `RUSAGE_THREAD`) across the trim, making it possible to detect cases where trimming caused unexpected kernel re-faulting of pages.
|
||||
|
||||
The inline `deltaKB()` method returns the signed RSS change (negative means memory was freed, positive means RSS grew). It guards against uninitialized sentinel values by returning `0` if either RSS reading is negative.
|
||||
|
||||
## `mallocTrim()` Function
|
||||
|
||||
The function signature is:
|
||||
|
||||
```cpp
|
||||
MallocTrimReport mallocTrim(std::string_view tag, beast::Journal journal);
|
||||
```
|
||||
|
||||
The `tag` is a caller-supplied identifier that appears in log output, allowing multiple call sites to be distinguished in diagnostics. The actual implementation in `MallocTrim.cpp` is compiled conditionally: the entire trim path is gated on `#if defined(__GLIBC__) && BOOST_OS_LINUX`. On any other platform, the function is a no-op that returns a default-constructed report with `supported = false`.
|
||||
|
||||
When active, the implementation makes a deliberate performance tradeoff gated on the journal's debug severity. If debug logging is disabled, only `::malloc_trim(0)` is called and the report stays mostly at sentinel values — no `/proc` reads, no rusage calls, minimal overhead. If debug logging is enabled, the function reads `/proc/self/statm` before and after, calls `getrusage(RUSAGE_THREAD, ...)` before and after, and measures wall time. This layered instrumentation is only paid when someone is actively debugging memory behavior, which makes the function cheap enough for production call sites.
|
||||
|
||||
The trim padding constant is hardcoded to `TRIM_PAD = 0`. A comment in the implementation documents that this choice came from 12-hour empirical testing on Mainnet across four padding values (0, 256 KB, 1 MB, 16 MB): zero delivered the best balance of RSS reduction and trim-latency stability without adding a tuning surface. This is a non-obvious design choice that prevents the parameter from becoming a configuration footgun.
|
||||
|
||||
## Allocator Compatibility and Scope Limits
|
||||
|
||||
The header's comment block is worth reading carefully: `malloc_trim` only affects glibc's own `sbrk`-based arenas. Large allocations backed by `mmap` are returned to the OS when freed, regardless of trimming. More critically, if jemalloc or tcmalloc is linked or `LD_PRELOAD`ed, calling `::malloc_trim` from glibc's symbol is harmless but does not touch those allocators' arenas. The function has no way to detect this at runtime, so the `supported` flag is set based on compiler/OS detection only, not on whether the active allocator will actually respond.
|
||||
|
||||
## Call Site
|
||||
|
||||
The function is invoked in `Application.cpp`'s `doSweep()` method, immediately after cache sweeps and ledger cleanup:
|
||||
|
||||
```cpp
|
||||
mallocTrim("doSweep", m_journal);
|
||||
```
|
||||
|
||||
This is the canonical usage pattern: call at a known point after significant bulk-free operations, rather than on a timer. The header comment recommends rate-limiting calls to avoid churn, which `doSweep`'s existing sweep timer naturally provides.
|
||||
32
include/xrpl/basics/MathUtilities.h.ai.json
Normal file
32
include/xrpl/basics/MathUtilities.h.ai.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 16,
|
||||
"name": "count"
|
||||
},
|
||||
{
|
||||
"lineno": 16,
|
||||
"name": "total"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Provides a constexpr function to calculate the percentage of one number divided by another, rounded up and capped between 0 and 100, with static_assert unit tests.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/MathUtilities.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"count",
|
||||
"total"
|
||||
],
|
||||
"lineno": 16,
|
||||
"name": "calculatePercent"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
23
include/xrpl/basics/MathUtilities.h.ai.md
Normal file
23
include/xrpl/basics/MathUtilities.h.ai.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# `MathUtilities.h` — Integer Percentage Utility
|
||||
|
||||
This header lives in the `xrpl::basics` utility layer and provides a single function, `calculatePercent`, for computing integer percentages with ceiling rounding and range clamping. It exists because this specific combination of behaviors — ceiling division, overflow-safe clamping, and compile-time verifiability — recurs in the XRPL codebase wherever a count-over-total ratio needs to be expressed as a human-readable integer percentage without floating point.
|
||||
|
||||
## `calculatePercent`
|
||||
|
||||
```cpp
|
||||
constexpr std::size_t calculatePercent(std::size_t count, std::size_t total)
|
||||
```
|
||||
|
||||
The function answers: "what percent of `total` is `count`, rounded up to the nearest whole percent, and never exceeding 100?" The ceiling rounding is intentional: a fraction like 1/99 (≈1.01%) rounds up to 2, signalling that *some* nonzero fraction is present. This is the conservative, safety-oriented direction for thresholding use cases — if you're checking whether at least N% of validators have seen something, you want to err on the side of reporting a higher fraction rather than losing signal in truncation.
|
||||
|
||||
The arithmetic `((std::min(count, total) * 100) + total - 1) / total` encodes three distinct behaviors in one expression. The `std::min(count, total)` clamp prevents the numerator from exceeding `total * 100`, which both enforces the 100% cap and guards against the case where `count > total` (e.g., a validator set that temporarily grows). The `+ total - 1` addend is the standard ceiling-division trick: it causes any non-zero remainder in the integer division to round up rather than truncate. The whole expression stays in unsigned integer arithmetic with no floating point, making it safe across all platforms and constexpr-eligible.
|
||||
|
||||
The `assert(total != 0)` guard catches division-by-zero in debug builds. The comment explains why `XRPL_ASSERT` is not used here: the function is `constexpr`, and `XRPL_ASSERT` is not constexpr-compatible, so the plain `assert` is the appropriate defence at runtime while still permitting compile-time evaluation.
|
||||
|
||||
## Real-World Use
|
||||
|
||||
The primary call site is in `LedgerMaster.cpp`, which uses `calculatePercent` to decide whether to warn operators about a potential software upgrade need. Two separate thresholds are evaluated: whether at least 90% of the UNL (Unique Node List) validators have sent validation messages, and whether at least 60% of xrpld-running validators are on a higher version. Both checks use `calculatePercent` with named constants (`reportingPercent`, `cutoffPercent`) rather than raw literals, and the thresholding via `>=` naturally benefits from ceiling rounding — a value just above a threshold boundary will not be falsely excluded by truncation.
|
||||
|
||||
## Compile-Time Tests
|
||||
|
||||
The `static_assert` block at namespace scope doubles as both documentation and zero-cost test coverage. Fourteen cases are checked at compile time, covering the zero numerator, full 100% match, over-100% input, boundary rounding cases (1/99, 1/64, near-100% fractions), and large-scale inputs up to 100 million. If the implementation is ever changed and any of these properties breaks, the build fails immediately — there is no need for a separate test binary or runtime fixture. This pattern is well-suited to a pure arithmetic utility that has no external dependencies.
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
return data_;
|
||||
}
|
||||
|
||||
ProtectedDataType const&
|
||||
[[nodiscard]] ProtectedDataType const&
|
||||
get() const
|
||||
{
|
||||
return data_;
|
||||
@@ -131,7 +131,7 @@ public:
|
||||
* @tparam LockType The type of lock to use
|
||||
* @return A lock on the mutex and a reference to the protected data
|
||||
*/
|
||||
template <template <typename...> typename LockType = std::lock_guard>
|
||||
template <template <typename...> typename LockType = std::scoped_lock>
|
||||
Lock<ProtectedDataType const, LockType, MutexType>
|
||||
lock() const
|
||||
{
|
||||
@@ -144,7 +144,7 @@ public:
|
||||
* @tparam LockType The type of lock to use
|
||||
* @return A lock on the mutex and a reference to the protected data
|
||||
*/
|
||||
template <template <typename...> typename LockType = std::lock_guard>
|
||||
template <template <typename...> typename LockType = std::scoped_lock>
|
||||
Lock<ProtectedDataType, LockType, MutexType>
|
||||
lock()
|
||||
{
|
||||
|
||||
60
include/xrpl/basics/Mutex.hpp.ai.json
Normal file
60
include/xrpl/basics/Mutex.hpp.ai.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 11,
|
||||
"name": "ProtectedDataType"
|
||||
},
|
||||
{
|
||||
"lineno": 11,
|
||||
"name": "MutexType"
|
||||
},
|
||||
{
|
||||
"lineno": 15,
|
||||
"name": "LockType"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"MutexType& mutex, ProtectedDataType& data"
|
||||
],
|
||||
"lineno": 18,
|
||||
"name": "Lock"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"ProtectedDataType data"
|
||||
],
|
||||
"lineno": 56,
|
||||
"name": "Mutex"
|
||||
}
|
||||
],
|
||||
"description": "Provides a thread-safe container (Mutex) for protecting data with a mutex, inspired by Rust's Mutex, and a Lock class for accessing the protected data safely.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Mutex.hpp",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"Args&&... args"
|
||||
],
|
||||
"lineno": 66,
|
||||
"name": "Mutex::make"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 81,
|
||||
"name": "Mutex::lock (const)"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 92,
|
||||
"name": "Mutex::lock"
|
||||
}
|
||||
],
|
||||
"language": "cpp header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 8,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
51
include/xrpl/basics/Mutex.hpp.ai.md
Normal file
51
include/xrpl/basics/Mutex.hpp.ai.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# `include/xrpl/basics/Mutex.hpp`
|
||||
|
||||
## Purpose and Motivation
|
||||
|
||||
This header introduces a Rust-inspired `Mutex<T>` idiom to the XRPL C++ codebase. The core problem it solves is a well-known C++ anti-pattern: separating a mutex from the data it protects, which lets callers accidentally read or write the data without holding the lock. The standard library provides `std::mutex` and RAII guards, but the data itself remains a separate, freely-accessible member. Nothing in the type system stops code from doing `data_ = x;` without ever calling `lock()`.
|
||||
|
||||
The Rust model solves this by making the lock guard the only path to the data. `Mutex.hpp` reproduces that guarantee in C++: the protected value lives inside `Mutex<T>`, is entirely private, and the only way to reach it is through a `Lock` object returned by `Mutex::lock()`. A `Lock` holds both the RAII lock and a reference to the data, so when the `Lock` goes out of scope the mutex is released and the reference becomes unreachable simultaneously.
|
||||
|
||||
The file is credited to the Clio project (`https://github.com/XRPLF/clio`) and lives under the `xrpl` namespace in `include/xrpl/basics/`.
|
||||
|
||||
## `Lock<ProtectedDataType, LockType, MutexType>`
|
||||
|
||||
`Lock` is a thin RAII wrapper that bundles a lock instance (`LockType<MutexType> lock_`) with a reference to the protected data (`ProtectedDataType& data_`). It exposes `operator*`, `get()`, and `operator->` — all in both const and non-const variants — so users interact with the guarded value naturally.
|
||||
|
||||
The constructor `Lock(MutexType& mutex, ProtectedDataType& data)` is `private`, with `friend class Mutex<std::remove_const_t<ProtectedDataType>, MutexType>` as the sole access point. The `std::remove_const_t` strip is intentional: when the `Mutex` is const it instantiates `Lock<ProtectedDataType const, ...>`, whose friend declaration must still point back to `Mutex<ProtectedDataType>` (non-const), not the non-existent `Mutex<ProtectedDataType const>`. This single line of `friend` is what makes the safety guarantee airtight — no code path outside `Mutex::lock()` can construct a `Lock`.
|
||||
|
||||
The conversion operators `operator LockType<MutexType>&()` and `operator LockType<MutexType> const&()` allow a `Lock` to decay to a reference to the underlying lock object. This matters in practice when `std::unique_lock` is chosen as the `LockType` and the lock needs to be passed to `std::condition_variable::wait()`, which accepts a `std::unique_lock<std::mutex>&`. The conversion keeps the idiom composable with the standard library's synchronisation utilities.
|
||||
|
||||
## `Mutex<ProtectedDataType, MutexType>`
|
||||
|
||||
`Mutex` stores a `mutable MutexType mutex_` and a `ProtectedDataType data_{}`. The `mutable` qualifier is deliberate: acquiring a lock is logically a non-mutating operation on the container, so it must be callable on a `const Mutex` instance. Without `mutable` the const `lock()` overload could not take `mutex_` by non-const reference as required by any standard lock type.
|
||||
|
||||
Two `lock()` overloads handle const-correctness propagation:
|
||||
|
||||
```cpp
|
||||
// const Mutex → Lock<ProtectedDataType const, ...> (read-only access)
|
||||
Lock<ProtectedDataType const, LockType, MutexType> lock() const;
|
||||
|
||||
// non-const Mutex → Lock<ProtectedDataType, ...> (read-write access)
|
||||
Lock<ProtectedDataType, LockType, MutexType> lock();
|
||||
```
|
||||
|
||||
Calling `lock()` on a `const Mutex` yields a `Lock` whose `operator*` and `operator->` return `const` references. The compiler enforces this — there is no cast or escape hatch. The test suite verifies it with `static_assert(std::is_const_v<...>)`.
|
||||
|
||||
Both overloads are templated on `LockType`, which defaults to `std::lock_guard`. Callers can substitute `std::unique_lock` for deferred or timed locking, or combine a `std::shared_mutex` as the `MutexType` with `std::shared_lock` as the `LockType` to implement reader-writer semantics with the same ergonomic API. The test in `src/tests/libxrpl/basics/Mutex.cpp` demonstrates exactly this: multiple shared readers coexist while an exclusive writer uses `std::unique_lock`.
|
||||
|
||||
The `make()` static factory provides perfect-forwarding in-place construction of the protected object:
|
||||
|
||||
```cpp
|
||||
auto m = Mutex<MyStruct>::make(arg1, arg2); // forwards to MyStruct(arg1, arg2)
|
||||
```
|
||||
|
||||
This avoids a move-construct-then-copy sequence when constructing from arguments and also supports move-only types like `std::unique_ptr<T>`.
|
||||
|
||||
## Design Trade-offs
|
||||
|
||||
The main cost of this pattern is that the `Lock` object must remain alive for the entire scope of any access. Code that needs to unlock mid-scope, or pass just the protected value to a function, requires `std::unique_lock` as the `LockType` so the conversion operator can expose the lock for manual `unlock()`/`lock()` calls. The default `std::lock_guard` is intentionally non-flexible — it signals "lock for this scope, nothing else" and prevents premature unlocking.
|
||||
|
||||
There is no `try_lock()` surface exposed through `Mutex` itself. A caller wanting try-lock semantics must drop to `std::unique_lock` (for `try_lock()`) or interact with the raw mutex type outside this wrapper, which is a deliberate friction point rather than an oversight. The design prioritises the common, safe path — RAII lock for the lifetime of a `Lock` object — over less common, riskier patterns.
|
||||
|
||||
The file is the sole resident of the `basics/` header directory that was found, suggesting it was added as a focused utility pulled from Clio rather than part of a larger local module. Its test coverage in `Mutex.cpp` is comprehensive: default construction, value construction, `make()` with forwarding, const and non-const access, custom lock types, `std::shared_mutex` reader-writer patterns, and move-only protected types.
|
||||
@@ -70,27 +70,27 @@ isPowerOfTen(T value)
|
||||
struct MantissaRange
|
||||
{
|
||||
using rep = std::uint64_t;
|
||||
enum mantissa_scale { small, large };
|
||||
enum class MantissaScale { Small, Large };
|
||||
|
||||
explicit constexpr MantissaRange(mantissa_scale scale_)
|
||||
: min(getMin(scale_)), log(logTen(min).value_or(-1)), scale(scale_)
|
||||
explicit constexpr MantissaRange(MantissaScale scale)
|
||||
: min(getMin(scale)), log(logTen(min).value_or(-1)), scale(scale)
|
||||
{
|
||||
}
|
||||
|
||||
rep min;
|
||||
rep max{(min * 10) - 1};
|
||||
int log;
|
||||
mantissa_scale scale;
|
||||
MantissaScale scale;
|
||||
|
||||
private:
|
||||
static constexpr rep
|
||||
getMin(mantissa_scale scale_)
|
||||
getMin(MantissaScale scale)
|
||||
{
|
||||
switch (scale_)
|
||||
switch (scale)
|
||||
{
|
||||
case small:
|
||||
case MantissaScale::Small:
|
||||
return 1'000'000'000'000'000ULL;
|
||||
case large:
|
||||
case MantissaScale::Large:
|
||||
return 1'000'000'000'000'000'000ULL;
|
||||
default:
|
||||
// Since this can never be called outside a non-constexpr
|
||||
@@ -214,26 +214,26 @@ class Number
|
||||
|
||||
public:
|
||||
// The range for the exponent when normalized
|
||||
constexpr static int minExponent = -32768;
|
||||
constexpr static int maxExponent = 32768;
|
||||
static constexpr int kMinExponent = -32768;
|
||||
static constexpr int kMaxExponent = 32768;
|
||||
|
||||
constexpr static internalrep maxRep = std::numeric_limits<rep>::max();
|
||||
static_assert(maxRep == 9'223'372'036'854'775'807);
|
||||
static_assert(-maxRep == std::numeric_limits<rep>::min() + 1);
|
||||
static constexpr internalrep kMaxRep = std::numeric_limits<rep>::max();
|
||||
static_assert(kMaxRep == 9'223'372'036'854'775'807);
|
||||
static_assert(-kMaxRep == std::numeric_limits<rep>::min() + 1);
|
||||
|
||||
// May need to make unchecked private
|
||||
struct unchecked
|
||||
struct Unchecked
|
||||
{
|
||||
explicit unchecked() = default;
|
||||
explicit Unchecked() = default;
|
||||
};
|
||||
|
||||
// Like unchecked, normalized is used with the ctors that take an
|
||||
// internalrep mantissa. Unlike unchecked, those ctors will normalize the
|
||||
// value.
|
||||
// Only unit tests are expected to use this class
|
||||
struct normalized
|
||||
struct Normalized
|
||||
{
|
||||
explicit normalized() = default;
|
||||
explicit Normalized() = default;
|
||||
};
|
||||
|
||||
explicit constexpr Number() = default;
|
||||
@@ -244,17 +244,17 @@ public:
|
||||
bool negative,
|
||||
internalrep mantissa,
|
||||
int exponent,
|
||||
unchecked) noexcept;
|
||||
Unchecked) noexcept;
|
||||
// Assume unsigned values are... unsigned. i.e. positive
|
||||
explicit constexpr Number(internalrep mantissa, int exponent, unchecked) noexcept;
|
||||
explicit constexpr Number(internalrep mantissa, int exponent, Unchecked) noexcept;
|
||||
// Only unit tests are expected to use this ctor
|
||||
explicit Number(bool negative, internalrep mantissa, int exponent, normalized);
|
||||
explicit Number(bool negative, internalrep mantissa, int exponent, Normalized);
|
||||
// Assume unsigned values are... unsigned. i.e. positive
|
||||
explicit Number(internalrep mantissa, int exponent, normalized);
|
||||
explicit Number(internalrep mantissa, int exponent, Normalized);
|
||||
|
||||
constexpr rep
|
||||
[[nodiscard]] constexpr rep
|
||||
mantissa() const noexcept;
|
||||
constexpr int
|
||||
[[nodiscard]] constexpr int
|
||||
exponent() const noexcept;
|
||||
|
||||
constexpr Number
|
||||
@@ -339,7 +339,7 @@ public:
|
||||
}
|
||||
|
||||
/** Return the sign of the amount */
|
||||
constexpr int
|
||||
[[nodiscard]] constexpr int
|
||||
signum() const noexcept
|
||||
{
|
||||
if (negative_)
|
||||
@@ -347,7 +347,7 @@ public:
|
||||
return (mantissa_ != 0u) ? 1 : 0;
|
||||
}
|
||||
|
||||
Number
|
||||
[[nodiscard]] Number
|
||||
truncate() const noexcept;
|
||||
|
||||
friend constexpr bool
|
||||
@@ -384,49 +384,51 @@ public:
|
||||
root2(Number f);
|
||||
|
||||
// Thread local rounding control. Default is to_nearest
|
||||
enum rounding_mode { to_nearest, towards_zero, downward, upward };
|
||||
static rounding_mode
|
||||
enum class RoundingMode { ToNearest, TowardsZero, Downward, Upward };
|
||||
|
||||
static RoundingMode
|
||||
getround();
|
||||
// Returns previously set mode
|
||||
static rounding_mode
|
||||
setround(rounding_mode mode);
|
||||
|
||||
static RoundingMode
|
||||
setround(RoundingMode inMode);
|
||||
|
||||
/** Returns which mantissa scale is currently in use for normalization.
|
||||
*
|
||||
* If you think you need to call this outside of unit tests, no you don't.
|
||||
*/
|
||||
static MantissaRange::mantissa_scale
|
||||
static MantissaRange::MantissaScale
|
||||
getMantissaScale();
|
||||
|
||||
/** Changes which mantissa scale is used for normalization.
|
||||
*
|
||||
* If you think you need to call this outside of unit tests, no you don't.
|
||||
*/
|
||||
static void
|
||||
setMantissaScale(MantissaRange::mantissa_scale scale);
|
||||
setMantissaScale(MantissaRange::MantissaScale scale);
|
||||
|
||||
static internalrep
|
||||
minMantissa()
|
||||
{
|
||||
return range_.get().min;
|
||||
return kRange.get().min;
|
||||
}
|
||||
|
||||
static internalrep
|
||||
maxMantissa()
|
||||
{
|
||||
return range_.get().max;
|
||||
return kRange.get().max;
|
||||
}
|
||||
|
||||
static int
|
||||
mantissaLog()
|
||||
{
|
||||
return range_.get().log;
|
||||
return kRange.get().log;
|
||||
}
|
||||
|
||||
/// oneSmall is needed because the ranges are private
|
||||
constexpr static Number
|
||||
static constexpr Number
|
||||
oneSmall();
|
||||
/// oneLarge is needed because the ranges are private
|
||||
constexpr static Number
|
||||
static constexpr Number
|
||||
oneLarge();
|
||||
|
||||
// And one is needed because it needs to choose between oneSmall and
|
||||
@@ -440,28 +442,28 @@ public:
|
||||
normalizeToRange(T minMantissa, T maxMantissa) const;
|
||||
|
||||
private:
|
||||
static thread_local rounding_mode mode_;
|
||||
static thread_local RoundingMode mode;
|
||||
// The available ranges for mantissa
|
||||
|
||||
constexpr static MantissaRange smallRange{MantissaRange::small};
|
||||
static_assert(isPowerOfTen(smallRange.min));
|
||||
static_assert(smallRange.min == 1'000'000'000'000'000LL);
|
||||
static_assert(smallRange.max == 9'999'999'999'999'999LL);
|
||||
static_assert(smallRange.log == 15);
|
||||
static_assert(smallRange.min < maxRep);
|
||||
static_assert(smallRange.max < maxRep);
|
||||
constexpr static MantissaRange largeRange{MantissaRange::large};
|
||||
static_assert(isPowerOfTen(largeRange.min));
|
||||
static_assert(largeRange.min == 1'000'000'000'000'000'000ULL);
|
||||
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
|
||||
static_assert(largeRange.log == 18);
|
||||
static_assert(largeRange.min < maxRep);
|
||||
static_assert(largeRange.max > maxRep);
|
||||
static constexpr MantissaRange kSmallRange{MantissaRange::MantissaScale::Small};
|
||||
static_assert(isPowerOfTen(kSmallRange.min));
|
||||
static_assert(kSmallRange.min == 1'000'000'000'000'000LL);
|
||||
static_assert(kSmallRange.max == 9'999'999'999'999'999LL);
|
||||
static_assert(kSmallRange.log == 15);
|
||||
static_assert(kSmallRange.min < kMaxRep);
|
||||
static_assert(kSmallRange.max < kMaxRep);
|
||||
static constexpr MantissaRange kLargeRange{MantissaRange::MantissaScale::Large};
|
||||
static_assert(isPowerOfTen(kLargeRange.min));
|
||||
static_assert(kLargeRange.min == 1'000'000'000'000'000'000ULL);
|
||||
static_assert(kLargeRange.max == internalrep(9'999'999'999'999'999'999ULL));
|
||||
static_assert(kLargeRange.log == 18);
|
||||
static_assert(kLargeRange.min < kMaxRep);
|
||||
static_assert(kLargeRange.max > kMaxRep);
|
||||
|
||||
// The range for the mantissa when normalized.
|
||||
// Use reference_wrapper to avoid making copies, and prevent accidentally
|
||||
// changing the values inside the range.
|
||||
static thread_local std::reference_wrapper<MantissaRange const> range_;
|
||||
static thread_local std::reference_wrapper<MantissaRange const> kRange;
|
||||
|
||||
void
|
||||
normalize();
|
||||
@@ -469,7 +471,7 @@ private:
|
||||
/** Normalize Number components to an arbitrary range.
|
||||
*
|
||||
* min/maxMantissa are parameters because this function is used by both
|
||||
* normalize(), which reads from range_, and by normalizeToRange,
|
||||
* normalize(), which reads from kRange, and by normalizeToRange,
|
||||
* which is public and can accept an arbitrary range from the caller.
|
||||
*/
|
||||
template <class T>
|
||||
@@ -485,18 +487,18 @@ private:
|
||||
friend void
|
||||
doNormalize(
|
||||
bool& negative,
|
||||
T& mantissa_,
|
||||
int& exponent_,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa);
|
||||
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
isnormal() const noexcept;
|
||||
|
||||
// Copy the number, but modify the exponent by "exponentDelta". Because the
|
||||
// mantissa doesn't change, the result will be "mostly" normalized, but the
|
||||
// exponent could go out of range, so it will be checked.
|
||||
Number
|
||||
[[nodiscard]] Number
|
||||
shiftExponent(int exponentDelta) const;
|
||||
|
||||
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
|
||||
@@ -509,31 +511,31 @@ private:
|
||||
class Guard;
|
||||
};
|
||||
|
||||
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, unchecked) noexcept
|
||||
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept
|
||||
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
|
||||
{
|
||||
}
|
||||
|
||||
constexpr Number::Number(internalrep mantissa, int exponent, unchecked) noexcept
|
||||
: Number(false, mantissa, exponent, unchecked{})
|
||||
constexpr Number::Number(internalrep mantissa, int exponent, Unchecked) noexcept
|
||||
: Number(false, mantissa, exponent, Unchecked{})
|
||||
{
|
||||
}
|
||||
|
||||
constexpr static Number numZero{};
|
||||
static constexpr Number kNumZero{};
|
||||
|
||||
inline Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
|
||||
: Number(negative, mantissa, exponent, unchecked{})
|
||||
inline Number::Number(bool negative, internalrep mantissa, int exponent, Normalized)
|
||||
: Number(negative, mantissa, exponent, Unchecked{})
|
||||
{
|
||||
normalize();
|
||||
}
|
||||
|
||||
inline Number::Number(internalrep mantissa, int exponent, normalized)
|
||||
: Number(false, mantissa, exponent, normalized{})
|
||||
inline Number::Number(internalrep mantissa, int exponent, Normalized)
|
||||
: Number(false, mantissa, exponent, Normalized{})
|
||||
{
|
||||
}
|
||||
|
||||
inline Number::Number(rep mantissa, int exponent)
|
||||
: Number(mantissa < 0, externalToInternal(mantissa), exponent, normalized{})
|
||||
: Number(mantissa < 0, externalToInternal(mantissa), exponent, Normalized{})
|
||||
{
|
||||
}
|
||||
|
||||
@@ -550,10 +552,10 @@ constexpr Number::rep
|
||||
Number::mantissa() const noexcept
|
||||
{
|
||||
auto m = mantissa_;
|
||||
if (m > maxRep)
|
||||
if (m > kMaxRep)
|
||||
{
|
||||
XRPL_ASSERT_PARTS(
|
||||
!isnormal() || (m % 10 == 0 && m / 10 <= maxRep),
|
||||
!isnormal() || (m % 10 == 0 && m / 10 <= kMaxRep),
|
||||
"xrpl::Number::mantissa",
|
||||
"large normalized mantissa has no remainder");
|
||||
m /= 10;
|
||||
@@ -571,10 +573,10 @@ constexpr int
|
||||
Number::exponent() const noexcept
|
||||
{
|
||||
auto e = exponent_;
|
||||
if (mantissa_ > maxRep)
|
||||
if (mantissa_ > kMaxRep)
|
||||
{
|
||||
XRPL_ASSERT_PARTS(
|
||||
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep),
|
||||
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= kMaxRep),
|
||||
"xrpl::Number::exponent",
|
||||
"large normalized mantissa has no remainder");
|
||||
++e;
|
||||
@@ -669,29 +671,29 @@ operator/(Number const& x, Number const& y)
|
||||
inline Number
|
||||
Number::min() noexcept
|
||||
{
|
||||
return Number{false, range_.get().min, minExponent, unchecked{}};
|
||||
return Number{false, kRange.get().min, kMinExponent, Unchecked{}};
|
||||
}
|
||||
|
||||
inline Number
|
||||
Number::max() noexcept
|
||||
{
|
||||
return Number{false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
|
||||
return Number{false, std::min(kRange.get().max, kMaxRep), kMaxExponent, Unchecked{}};
|
||||
}
|
||||
|
||||
inline Number
|
||||
Number::lowest() noexcept
|
||||
{
|
||||
return Number{true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
|
||||
return Number{true, std::min(kRange.get().max, kMaxRep), kMaxExponent, Unchecked{}};
|
||||
}
|
||||
|
||||
inline bool
|
||||
Number::isnormal() const noexcept
|
||||
{
|
||||
MantissaRange const& range = range_;
|
||||
auto const abs_m = mantissa_;
|
||||
MantissaRange const& range = kRange;
|
||||
auto const absM = mantissa_;
|
||||
return *this == Number{} ||
|
||||
(range.min <= abs_m && abs_m <= range.max && (abs_m <= maxRep || abs_m % 10 == 0) &&
|
||||
minExponent <= exponent_ && exponent_ <= maxExponent);
|
||||
(range.min <= absM && absM <= range.max && (absM <= kMaxRep || absM % 10 == 0) &&
|
||||
kMinExponent <= exponent_ && exponent_ <= kMaxExponent);
|
||||
}
|
||||
|
||||
template <Integral64 T>
|
||||
@@ -755,34 +757,34 @@ squelch(Number const& x, Number const& limit) noexcept
|
||||
}
|
||||
|
||||
inline std::string
|
||||
to_string(MantissaRange::mantissa_scale const& scale)
|
||||
to_string(MantissaRange::MantissaScale const& scale)
|
||||
{
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::small:
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
return "small";
|
||||
case MantissaRange::large:
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
return "large";
|
||||
default:
|
||||
throw std::runtime_error("Bad scale");
|
||||
}
|
||||
}
|
||||
|
||||
class saveNumberRoundMode
|
||||
class SaveNumberRoundMode
|
||||
{
|
||||
Number::rounding_mode mode_;
|
||||
Number::RoundingMode mode_;
|
||||
|
||||
public:
|
||||
~saveNumberRoundMode()
|
||||
~SaveNumberRoundMode()
|
||||
{
|
||||
Number::setround(mode_);
|
||||
}
|
||||
explicit saveNumberRoundMode(Number::rounding_mode mode) noexcept : mode_{mode}
|
||||
explicit SaveNumberRoundMode(Number::RoundingMode mode) noexcept : mode_{mode}
|
||||
{
|
||||
}
|
||||
saveNumberRoundMode(saveNumberRoundMode const&) = delete;
|
||||
saveNumberRoundMode&
|
||||
operator=(saveNumberRoundMode const&) = delete;
|
||||
SaveNumberRoundMode(SaveNumberRoundMode const&) = delete;
|
||||
SaveNumberRoundMode&
|
||||
operator=(SaveNumberRoundMode const&) = delete;
|
||||
};
|
||||
|
||||
// saveNumberRoundMode doesn't do quite enough for us. What we want is a
|
||||
@@ -791,10 +793,10 @@ public:
|
||||
// build it here.
|
||||
class NumberRoundModeGuard
|
||||
{
|
||||
saveNumberRoundMode saved_;
|
||||
SaveNumberRoundMode saved_;
|
||||
|
||||
public:
|
||||
explicit NumberRoundModeGuard(Number::rounding_mode mode) noexcept
|
||||
explicit NumberRoundModeGuard(Number::RoundingMode mode) noexcept
|
||||
: saved_{Number::setround(mode)}
|
||||
{
|
||||
}
|
||||
@@ -812,10 +814,10 @@ public:
|
||||
*/
|
||||
class NumberMantissaScaleGuard
|
||||
{
|
||||
MantissaRange::mantissa_scale const saved_;
|
||||
MantissaRange::MantissaScale const saved_;
|
||||
|
||||
public:
|
||||
explicit NumberMantissaScaleGuard(MantissaRange::mantissa_scale scale) noexcept
|
||||
explicit NumberMantissaScaleGuard(MantissaRange::MantissaScale scale) noexcept
|
||||
: saved_{Number::getMantissaScale()}
|
||||
{
|
||||
Number::setMantissaScale(scale);
|
||||
|
||||
363
include/xrpl/basics/Number.h.ai.json
Normal file
363
include/xrpl/basics/Number.h.ai.json
Normal file
@@ -0,0 +1,363 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 10,
|
||||
"name": "amount"
|
||||
},
|
||||
{
|
||||
"lineno": 13,
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"lineno": 31,
|
||||
"name": "scale_"
|
||||
},
|
||||
{
|
||||
"lineno": 119,
|
||||
"name": "mantissa"
|
||||
},
|
||||
{
|
||||
"lineno": 119,
|
||||
"name": "exponent"
|
||||
},
|
||||
{
|
||||
"lineno": 117,
|
||||
"name": "negative"
|
||||
},
|
||||
{
|
||||
"lineno": 373,
|
||||
"name": "mode"
|
||||
},
|
||||
{
|
||||
"lineno": 407,
|
||||
"name": "scale"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"mantissa_scale scale_"
|
||||
],
|
||||
"lineno": 28,
|
||||
"name": "MantissaRange"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 81,
|
||||
"name": "Number"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 120,
|
||||
"name": "Number::unchecked"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 127,
|
||||
"name": "Number::normalized"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number::rounding_mode mode"
|
||||
],
|
||||
"lineno": 370,
|
||||
"name": "saveNumberRoundMode"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number::rounding_mode mode"
|
||||
],
|
||||
"lineno": 386,
|
||||
"name": "NumberRoundModeGuard"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"MantissaRange::mantissa_scale scale"
|
||||
],
|
||||
"lineno": 404,
|
||||
"name": "NumberMantissaScaleGuard"
|
||||
}
|
||||
],
|
||||
"description": "Defines the xrpl::Number class, a floating point type for representing a wide range of asset values with precise mantissa and exponent control, including normalization, rounding, and mantissa range switching. Also provides related utilities, guards, and helper functions.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Number.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"Number const& amount"
|
||||
],
|
||||
"lineno": 10,
|
||||
"name": "to_string"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T value"
|
||||
],
|
||||
"lineno": 13,
|
||||
"name": "logTen"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T value"
|
||||
],
|
||||
"lineno": 24,
|
||||
"name": "isPowerOfTen"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 154,
|
||||
"name": "operator=="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 160,
|
||||
"name": "operator!="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 165,
|
||||
"name": "operator<"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 186,
|
||||
"name": "signum"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 191,
|
||||
"name": "truncate"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 194,
|
||||
"name": "operator>"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 198,
|
||||
"name": "operator<="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 202,
|
||||
"name": "operator>="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"std::ostream& os",
|
||||
"Number const& x"
|
||||
],
|
||||
"lineno": 206,
|
||||
"name": "operator<<"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& amount"
|
||||
],
|
||||
"lineno": 211,
|
||||
"name": "to_string"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number f",
|
||||
"unsigned d"
|
||||
],
|
||||
"lineno": 213,
|
||||
"name": "root"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number f"
|
||||
],
|
||||
"lineno": 215,
|
||||
"name": "root2"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 220,
|
||||
"name": "getround"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"rounding_mode mode"
|
||||
],
|
||||
"lineno": 222,
|
||||
"name": "setround"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 229,
|
||||
"name": "getMantissaScale"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"MantissaRange::mantissa_scale scale"
|
||||
],
|
||||
"lineno": 234,
|
||||
"name": "setMantissaScale"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 238,
|
||||
"name": "minMantissa"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 243,
|
||||
"name": "maxMantissa"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 248,
|
||||
"name": "mantissaLog"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 254,
|
||||
"name": "oneSmall"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 256,
|
||||
"name": "oneLarge"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 260,
|
||||
"name": "one"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T minMantissa",
|
||||
"T maxMantissa"
|
||||
],
|
||||
"lineno": 264,
|
||||
"name": "normalizeToRange"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 282,
|
||||
"name": "normalize"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"bool& negative",
|
||||
"T& mantissa",
|
||||
"int& exponent",
|
||||
"internalrep const& minMantissa",
|
||||
"internalrep const& maxMantissa"
|
||||
],
|
||||
"lineno": 287,
|
||||
"name": "normalize"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"bool& negative",
|
||||
"T& mantissa_",
|
||||
"int& exponent_",
|
||||
"MantissaRange::rep const& minMantissa",
|
||||
"MantissaRange::rep const& maxMantissa"
|
||||
],
|
||||
"lineno": 295,
|
||||
"name": "doNormalize"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 299,
|
||||
"name": "isnormal"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"int exponentDelta"
|
||||
],
|
||||
"lineno": 304,
|
||||
"name": "shiftExponent"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"rep mantissa"
|
||||
],
|
||||
"lineno": 309,
|
||||
"name": "externalToInternal"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number x"
|
||||
],
|
||||
"lineno": 324,
|
||||
"name": "abs"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& f",
|
||||
"unsigned n"
|
||||
],
|
||||
"lineno": 334,
|
||||
"name": "power"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number f",
|
||||
"unsigned d"
|
||||
],
|
||||
"lineno": 338,
|
||||
"name": "root"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number f"
|
||||
],
|
||||
"lineno": 341,
|
||||
"name": "root2"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& f",
|
||||
"unsigned n",
|
||||
"unsigned d"
|
||||
],
|
||||
"lineno": 344,
|
||||
"name": "power"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& limit"
|
||||
],
|
||||
"lineno": 348,
|
||||
"name": "squelch"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"MantissaRange::mantissa_scale const& scale"
|
||||
],
|
||||
"lineno": 355,
|
||||
"name": "to_string"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 8,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user