Compare commits

...

114 Commits

Author SHA1 Message Date
Pratik Mankawde
b54c500bb2 Merge branch 'pratik/std-coro/migrate-test-code' into pratik/std-coro/cleanup-boost-coroutine 2026-07-27 13:48:08 +01:00
Pratik Mankawde
b6526522fe Merge branch 'pratik/std-coro/migrate-entry-points' into pratik/std-coro/migrate-test-code 2026-07-27 13:48:08 +01:00
Pratik Mankawde
61041155d2 Merge branch 'pratik/std-coro/add-coroutine-primitives' into pratik/std-coro/migrate-entry-points 2026-07-27 13:48:08 +01:00
Pratik Mankawde
5ea72b45f1 Merge remote-tracking branch 'origin/develop' into pratik/std-coro/add-coroutine-primitives 2026-07-27 13:47:54 +01:00
Pratik Mankawde
7b95a8ea8b Merge branch 'pratik/std-coro/migrate-test-code' into pratik/std-coro/cleanup-boost-coroutine 2026-07-27 13:06:43 +01:00
Pratik Mankawde
7385004952 Move coroutine-capture NOLINTNEXTLINE comments next to the lambdas
clang-format placed the suppression comments two lines above the lambda
expressions (before the postCoroTask call), so NOLINTNEXTLINE suppressed
the wrong line and clang-tidy still reported
cppcoreguidelines-avoid-capturing-lambda-coroutines at the lambda. Move
the comments inside the argument list, immediately before each lambda,
matching the pattern already used in threadSpecificStorage.
2026-07-27 13:06:13 +01:00
Andrzej Budzanowski
20801d98ac test: Improve the server status test to not race and randomly fail (#7304)
Co-authored-by: Alex Kremer <akremer@ripple.com>
2026-07-27 11:58:15 +00:00
Pratik Mankawde
fce702d5c3 Merge branch 'pratik/std-coro/migrate-test-code' into pratik/std-coro/cleanup-boost-coroutine 2026-07-27 12:36:46 +01:00
Pratik Mankawde
a6fea1227f Suppress clang-tidy coroutine-capture warnings in core tests
The JobQueue_test and Coroutine_test coroutine lambdas capture pointers
to test-scope locals, but each test drives the coroutine to completion
(or, for the stopped-queue case, guarantees it never starts) before the
locals go out of scope. Add NOLINTNEXTLINE for
cppcoreguidelines-avoid-capturing-lambda-coroutines with comments
explaining the lifetime guarantee, and drop the unused <memory> include
flagged by misc-include-cleaner.
2026-07-27 12:36:10 +01:00
Pratik Mankawde
968f9e1bf3 Merge branch 'pratik/std-coro/migrate-entry-points' into pratik/std-coro/migrate-test-code 2026-07-27 12:34:16 +01:00
Pratik Mankawde
70813b336a Suppress clang-tidy coroutine-capture warnings in pathfinding tests
The Path_test and TestHelpers coroutine lambdas capture locals by
reference, but the caller blocks on Gate::waitFor() until the coroutine
signals completion, so the captures cannot dangle. Add NOLINTNEXTLINE
for cppcoreguidelines-avoid-capturing-lambda-coroutines with a comment
explaining the lifetime guarantee.
2026-07-27 12:33:49 +01:00
Pratik Mankawde
47d84d2e21 Merge branch 'pratik/std-coro/migrate-test-code' into pratik/std-coro/cleanup-boost-coroutine 2026-07-27 12:06:41 +01:00
Pratik Mankawde
ab8350b558 Merge branch 'pratik/std-coro/migrate-entry-points' into pratik/std-coro/migrate-test-code 2026-07-27 12:06:41 +01:00
Pratik Mankawde
072e11ecd1 Address clang-tidy findings in entry-point migration
- Suppress cppcoreguidelines-avoid-capturing-lambda-coroutines with
  NOLINT where lifetime is guaranteed: GRPCServer (thisShared keeps
  CallData alive), ServerHandler RPC/WS clients (captures by value,
  handler outlives JobQueue jobs), and PathMPT_test (test blocks on
  Gate::waitFor until the coroutine completes).
- Change ServerHandler::processRequest to take Output const& to fix
  cppcoreguidelines-rvalue-reference-param-not-moved.
- Replace std::lock_guard with std::scoped_lock in RipplePathFind.
- Remove redundant includes and default member initializer in
  Context.h, GRPCServer.h, RipplePathFind.cpp.
2026-07-27 12:06:25 +01:00
Pratik Mankawde
3ac48e297f Merge branch 'pratik/std-coro/migrate-test-code' into pratik/std-coro/cleanup-boost-coroutine 2026-07-27 11:30:34 +01:00
Pratik Mankawde
f669b70451 Merge branch 'pratik/std-coro/migrate-entry-points' into pratik/std-coro/migrate-test-code 2026-07-27 11:30:34 +01:00
Pratik Mankawde
62839b1531 Merge branch 'pratik/std-coro/add-coroutine-primitives' into pratik/std-coro/migrate-entry-points 2026-07-27 11:30:34 +01:00
Pratik Mankawde
9bddc54722 Fix clang-tidy violations in coroutine primitives
- NOLINT the compiler-mandated coroutine protocol names (promise_type,
  await_ready, ...) that conflict with readability-identifier-naming and
  readability-convert-member-functions-to-static
- Add [[nodiscard]] to handle(), done(), await_ready()
- Replace std::lock_guard with std::scoped_lock const
- Pass CoroTaskRunner name by value and std::move it; default-init
  runCount_ in-class
- Drop unused <coroutine> include from JobQueue.h; include
  instrumentation.h directly in JobQueueAwaiter.h
- CoroTask_test: kN constant naming, const locals, file-level NOLINT for
  cppcoreguidelines-avoid-capturing-lambda-coroutines (lifetimes are
  gated and joined)
2026-07-27 11:30:16 +01:00
dependabot[bot]
b878818e80 ci: [DEPENDABOT] bump actions/checkout from 7.0.0 to 7.0.1 (#7871)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 09:55:01 +00:00
Pratik Mankawde
598bda0ebc Merge branch 'pratik/std-coro/migrate-test-code' into pratik/std-coro/cleanup-boost-coroutine 2026-07-27 10:30:16 +01:00
Pratik Mankawde
25b9b4d27d Fix JobQueue_test resume() contract violation and Coroutine_test timeout hazards
- PostCoroTest2 called runner->resume() directly from the test thread,
  violating the documented precondition on CoroTaskRunner::resume() (a
  post() must precede every resume) and driving runCount_ negative.
  Rewrite the loop as post()+join(); the non-atomic yieldCount now also
  verifies the happens-before edge join() provides via mutexRun_.
- PostCoroTest3 wrote false into an already-false flag, so it could
  not detect the coroutine running after stop(). Write true and assert
  the flag stays false.
- Coroutine_test: early-return when a Gate waitFor() times out instead
  of falling through to c->join()/a[i]->join(), which would deref a
  null shared_ptr (correctOrder, threadSpecificStorage first loop) or
  block indefinitely (second loop).
2026-07-27 10:29:57 +01:00
Pratik Mankawde
22ed85c3ad Merge branch 'pratik/std-coro/migrate-entry-points' into pratik/std-coro/migrate-test-code 2026-07-27 10:26:00 +01:00
Pratik Mankawde
8c7a27d721 Name the ripple_path_find wait timeout and fire completion on exception
- Replace the magic 30s in doRipplePathFind with
  RPC::Tuning::kPathfindCompletionTimeout.
- In PathRequestManager::updateAll, invoke updateComplete() via a
  ScopeExit guard on the one-shot (hasCompletion) path so the blocked
  RPC handler is released immediately even if doUpdate throws, instead
  of waiting out the full timeout.
2026-07-25 15:05:08 +01:00
Pratik Mankawde
9266c23ef6 Bound concurrent blocking ripple_path_find calls to prevent worker starvation
The no-ledger branch of doRipplePathFind parks its JobQueue worker on a
condition_variable for up to 30s, waiting for a completion that is fired
from a JtUpdatePf job -- which itself needs a free worker to run. With
unbounded JtClientRpc concurrency, enough simultaneous ripple_path_find
calls could park every worker, stalling the entire JobQueue until the
30s timeouts expired. The old Boost.Coroutine implementation suspended
and released the worker, so it did not have this failure mode.

Reuse the RPC::LegacyPathFind guard (already applied on the
ledger-specified branch) to admit at most kMaxPathfindsInProgress
non-admin blocking requests, returning rpcTOO_BUSY beyond that. The
guard stays in scope across the wait.
2026-07-25 12:39:04 +01:00
Pratik Mankawde
5e9ce16de6 Merge branch 'pratik/std-coro/add-coroutine-primitives' into pratik/std-coro/migrate-entry-points 2026-07-25 12:37:14 +01:00
Pratik Mankawde
8d4ea00453 Strengthen CoroTask_test assertions and timeout handling
- testExceptionPropagation now co_awaits an inner CoroTask<void> that
  throws and asserts the rethrown message, covering the
  CoroTask<void>::await_resume rethrow path; the old version could not
  distinguish a throw from a normal return.
- testJobQueueAwaiter now actually awaits the JobQueueAwaiter struct
  (single use per coroutine, per the GCC-12 note) and asserts the full
  ordered step sequence instead of only the terminal value.
- testValueException asserts the caught exception's message.
- All waitFor() timeouts early-return on failure instead of falling
  through to join()/state reads (null-deref and TSAN-race hazards on
  timeout, plus the nSuspend_ assert in ~Env).
- Remove dead shared_ptr locals in testCorrectOrder/testMultipleYields.
- Add missing includes (<array>, <stdexcept>, <string>, <vector>,
  LocalValue.h, CoroTask.h).
2026-07-25 12:36:30 +01:00
Pratik Mankawde
bdcf094171 Guard resume() against completed coroutines and log unhandled exceptions
- resume() now skips the handle resume when the task is null or done
  (duplicate external post() after completion), matching the old
  Coro::resume() 'if (coro_)' guard instead of invoking UB in release
  builds. The runCount_ bookkeeping still runs to balance post().
- Exceptions escaping a top-level coroutine body were captured by
  unhandled_exception() and destroyed unobserved with the frame; they
  are now logged at error level before the frame is released.
- Document that join() may return via the finished_ disjunct while the
  final resume() is still completing its bookkeeping.
2026-07-25 12:33:51 +01:00
Pratik Mankawde
a42e8174d9 Fix check-rename CI failure
- Remove BoostToStdCoroutineSwitchPlan.md and BoostToStdCoroutineTaskList.md
  working documents from the repo root; the rename script rewrites their
  'rippled' references and the job fails on the resulting dirty tree.
- Reword two CoroTask.h comments to use 'xrpld'.
- Drop cspell words (cppcoro, gantt, Pratik, Mankawde) that existed only
  for the removed documents.
2026-07-25 12:30:38 +01:00
Pratik Mankawde
791c061a98 Merge branch 'pratik/std-coro/migrate-test-code' into pratik/std-coro/cleanup-boost-coroutine
Forward-merge the test-code migration (carrying the latest develop) into the
Boost.Coroutine cleanup.

Conflict resolutions: keep this branch's removal of JobQueue::Coro,
CoroCreateT, postCoro, Coro.ipp and the Boost.Coroutine2 includes, while
taking develop's include set, Doxygen comment style and formatting for the
retained CoroTaskRunner / postCoroTask declarations.
2026-07-24 21:02:25 +01:00
Pratik Mankawde
91004209e9 Merge branch 'pratik/std-coro/migrate-entry-points' into pratik/std-coro/migrate-test-code
Forward-merge the entry-point migration (carrying the latest develop) into
the test-code migration.

Conflict resolutions in Coroutine_test.cpp and JobQueue_test.cpp: keep the
postCoroTask / CoroTaskRunner form from this branch, with develop's renames
(JtClient, Gate::waitFor, kN) and develop's initialization of the
unprotected flag.
2026-07-24 20:57:01 +01:00
Pratik Mankawde
4c18dd867c Merge branch 'pratik/std-coro/add-coroutine-primitives' into pratik/std-coro/migrate-entry-points
Forward-merge the updated coroutine primitives (which themselves carry the
latest develop) into the entry-point migration.

Conflict resolutions:
- .cspell.config.yaml: keep develop's dotfile name, merge word lists.
- Context.h: drop the coro member; infoSub gets a default initializer.
- Application.cpp, ServerHandler.cpp, TestHelpers.cpp, Path_test.cpp,
  PathMPT_test.cpp: adopt develop's designated-initializer JsonContext and
  renamed identifiers (JtClient, kApiVersionIfUnspecified, kMaxSrcCur, Gate).
- GRPCServer: process(coro) becomes processRequest(), posted with
  postCoroTask.
- RipplePathFind.cpp: follow develop's move to handlers/orderbook/ and
  PathRequestManager; the completion state shared with the path-finding
  continuation is now heap-allocated so it outlives an early return.
- AMMTest.cpp: develop moved find_paths_request into TestHelpers.cpp, so the
  local copy is dropped and the migration applied there instead.
2026-07-24 20:52:28 +01:00
Pratik Mankawde
fb6ece56f2 Merge remote-tracking branch 'origin/develop' into pratik/std-coro/add-coroutine-primitives
Resolves the develop rename of cspell.config.yaml and the JobQueue.h
conflicts, and aligns the new coroutine primitives with develop naming
(CreateT, mutex_/mutexRun_, JtClient, forceMultiThread,
beast::unit_test::Suite).

Also addresses two review findings:

- postCoroTask now holds a jobCounter_ reservation for the whole
  function. JobQueue::stop() joins jobCounter_ before asserting
  nSuspend_ == 0, so the reservation closes the window between the
  ++nSuspend_ and the balancing post()/expectEarlyExit(), and doubles
  as the shutdown check.

- YieldPostAwaiter::await_suspend returns a coroutine_handle<>
  (symmetric transfer) instead of resuming inline. This keeps a
  yield loop against a stopping JobQueue from growing the stack
  without bound and avoids touching a frame that resume() may have
  already destroyed.
2026-07-24 20:39:41 +01:00
Sergey Kuznetsov
fecfc0cf3f chore: Fix clang version in devshell (#7860)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-07-24 17:30:20 +00:00
Ayaz Salikhov
ea0a6904f0 chore: Verify tooling version for Nix-managed environments (#7862) 2026-07-24 15:52:45 +00:00
Ayaz Salikhov
29d74142ae build: Patсh binary in local Linux nix environment (#7859)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-24 14:59:24 +00:00
Kassaking7
9afa1cf4d1 fix: Update PermissionedDEX invariant domain tracking for valid offer replacement (#7387)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-07-23 21:40:21 +00:00
Ed Hennis
7908aec2ec feat: Check default fields are not default when serializing (#6267)
Co-authored-by: Vito <5780819+Tapanito@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-07-23 21:39:11 +00:00
Marek Foss
4c0180b3db test: Migrate csf and xrpld-consensus Beast non-JTx tests to GTest (#7046)
Co-authored-by: Alex Kremer <akremer@ripple.com>
2026-07-23 21:38:21 +00:00
Marek Foss
4acccfeda8 test: Modularize Peerfinder component and migrate Peerfinder tests from Beast to GTest and GMock (#7054)
Co-authored-by: Alex Kremer <akremer@ripple.com>
2026-07-23 21:00:06 +00:00
Ed Hennis
b89d75a2d5 test: Add an RAII class to manage the env.parseFailureExpected flag (#7669) 2026-07-23 20:57:00 +00:00
Ayaz Salikhov
40cdf49d15 build: Use custom libc in a devshell by default (#7852) 2026-07-23 19:05:24 +00:00
Mayukha Vadari
38c54c3f36 feat: Add fixCleanup3_4_0 amendment (no functionality yet) (#7854) 2026-07-23 18:50:59 +00:00
Sergey Kuznetsov
95e1ffea6e ci: Add llvm-tools-preview to rust toolchain (#7853) 2026-07-23 16:49:44 +00:00
Sergey Kuznetsov
74cfb3586a ci: Update CI image (#7850) 2026-07-23 11:21:49 +00:00
Ayaz Salikhov
0072ced94c ci: Update XRPLF/actions (#7849) 2026-07-23 10:22:18 +00:00
Mayukha Vadari
12ed506565 chore: Clean up grammar in PR template (#7846) 2026-07-22 18:50:10 +00:00
Ayaz Salikhov
3122de86bf build: Create versioned compiler/tooling symlinks in nix environments (#7844) 2026-07-22 17:04:02 +00:00
Sergey Kuznetsov
7edf39e622 ci: Use rust-overlay to bring Rust into nix (#7837) 2026-07-22 14:00:17 +00:00
dependabot[bot]
ba01b05f33 ci: [DEPENDABOT] bump actions/setup-python from 6.3.0 to 7.0.0 (#7830)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 16:04:45 +00:00
Sergey Kuznetsov
c414e1877d ci: Update pre-commit image (#7838) 2026-07-21 15:37:47 +00:00
Sergey Kuznetsov
f56b79f00d ci: Add cargo to pre-commit image (#7835) 2026-07-21 13:39:37 +00:00
Ayaz Salikhov
4918f3bb20 chore: Add doxygen in pre-commit image (#7836) 2026-07-21 13:38:26 +00:00
Mayukha Vadari
ee0a3dfad7 ci: Improve test debuggability (#7619)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-07-21 13:14:40 +00:00
Bart
042c9660cd chore: Add CODEOWNERS for CI-related changes (#7832)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-07-20 21:06:24 +00:00
Ayaz Salikhov
60b49f3fe0 ci: Use in-house image for pre-commit (#7610) 2026-07-20 19:11:09 +00:00
Ayaz Salikhov
4c869ee16c ci: Build separate pre-commit docker image (#7831) 2026-07-20 17:40:38 +00:00
Ayaz Salikhov
b2f45813e0 chore: Workaround boost compiler resolution inside Nix environment (#7826) 2026-07-20 14:04:08 +00:00
Vito Tumas
5ce0b1c2c7 refactor: Restructure LendingHelpers to improve readability (#7807) 2026-07-16 15:47:56 +00:00
Andrzej Budzanowski
701311f27e test: Add google benchmark dependency and migrate nodestore timing test as a benchmark (#7317)
Co-authored-by: Marek Foss <marek.foss@neti-soft.com>
Co-authored-by: Alex Kremer <akremer@ripple.com>
2026-07-16 15:44:43 +00:00
Sergey Kuznetsov
b1a670c46e ci: Add Rust to CI (#7808)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-07-16 15:28:04 +00:00
Bart
18e311e1e2 chore: Bump version to 3.3.0-rc1 (#7806)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-07-16 13:54:12 +00:00
Denis Angell
69b70d7a0d fix: Refactor Batch Transaction IDs (#7736)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
2026-07-16 13:15:59 +00:00
Mayukha Vadari
b42cde3e85 refactor: Remove redundant enable checks in ConfidentialMPT txs (#7809) 2026-07-16 09:51:19 +00:00
Peter Chen
cd38c0e800 chore: Update mpt-crypto-lib to 0.4.0-rc4 (#7813) 2026-07-15 23:47:22 +00:00
Vlad
433e5f6896 fix: Reject zero CheckID in CheckCancel and CheckCash (#7685) 2026-07-15 22:08:45 +00:00
Ayaz Salikhov
781ab723af ci: Fix workflow launch on matrix-unrelated labels (#7812) 2026-07-15 18:24:31 +00:00
Ed Hennis
a24e543af3 fix: Allocate TaggedCache::getKeys() memory outside of lock (#7567)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-07-15 13:30:20 +00:00
Sophia Xie
a0fd1cce54 fix: Re-store nodes missing from both backends during online_delete rotation (#7763)
Co-authored-by: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com>
2026-07-14 23:42:40 +00:00
Kassaking7
cda63d00a2 fix: Add amendment sponsor for AccountRootsDeletedClean (#7801) 2026-07-14 19:41:53 +00:00
Ed Hennis
530e09dbe8 fix: Update base_uint and test changes released in 3.1.3 (#7570)
Co-authored-by: Sergey Kuznetsov <skuznetsov@ripple.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-07-14 18:48:10 +00:00
Ed Hennis
f10dd7b450 fix: Handle rounding just above kMaxRep more accurately (#7389)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-07-14 18:47:41 +00:00
Ed Hennis
0a4676d947 fix: Document and assert "after" is never null in invariants (#7354)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-07-14 18:16:46 +00:00
Ayaz Salikhov
0dc942508e ci: Run full matrix only on Ready to merge or Full CI build labeled PRs (#7689)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-07-14 16:09:20 +00:00
Gregory Tsipenyuk
2403670da9 fix: Strengthen Clawback invariant checks for MPT balances (#7285) 2026-07-14 14:31:06 +00:00
Jingchen
c621136748 test: Add unit tests for IP address related functions (#7744)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ed Hennis <ed@ripple.com>
2026-07-14 14:29:18 +00:00
Sergey Kuznetsov
2e25435a4a ci: Add Rust to Nix docker image (#7571) 2026-07-14 13:28:55 +00:00
Ayaz Salikhov
ab3ff66cd9 docs: Add more information about pre-commit hooks and how to set them up (#7802) 2026-07-14 12:55:34 +00:00
Alex Kremer
73e97b8b84 test: Add JSON array size tests (#7592) 2026-07-14 12:35:04 +00:00
Ayaz Salikhov
e1d4f357dc chore: Enable most readability checks (#7772) 2026-07-14 12:21:40 +00:00
Bart
acd54fd627 ci: Do not run conflict checker when label is applied (#7774)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-07-14 10:22:49 +00:00
Ayaz Salikhov
cd06ee221d chore: Run clang_tidy_check with pass_filenames: false from pre-commit (#7800) 2026-07-13 19:19:18 +00:00
Peter Chen
752dab8b30 feat: Add delegate filter param for account_tx RPC (#6126) 2026-07-13 18:44:59 +00:00
Mayukha Vadari
86583bc34e refactor: Move jss.h include out of Indexes.h (#7799) 2026-07-13 17:44:47 +00:00
Ayaz Salikhov
2b4d067ace test: Add tests for check doxygen style (#7795) 2026-07-13 14:38:33 +00:00
Ayaz Salikhov
62634463f4 style: Add pre-commit hook to check doxygen style (#7794) 2026-07-13 13:27:14 +00:00
Ayaz Salikhov
73b6852a12 style: Unify style for all Doxygen comments (#7776) 2026-07-13 10:40:40 +00:00
Ed Hennis
8306ac7710 fix: Improve Number addition/subtraction rounding (#7369)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-11 00:18:31 +00:00
Olek
fd2cc6dcb3 feat: XLS-68: Sponsor, #5887 continuation (#7350)
Co-authored-by: tequ <git@tequ.dev>
Co-authored-by: yinyiqian1 <yqian@ripple.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Peter Chen <34582813+PeterChen13579@users.noreply.github.com>
Co-authored-by: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <asalikhov@ripple.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Co-authored-by: Zhiyuan Wang <1830604455@qq.com>
2026-07-10 21:58:19 +00:00
Sergey Kuznetsov
c7adb215ed chore: Add .envrc for automatic devshell switch by direnv (#7756) 2026-07-08 17:28:48 +00:00
Ayaz Salikhov
71ee0f400b chore: Use same compiler in Nix devshell as in CI (#7751) 2026-07-08 14:51:39 +00:00
Ayaz Salikhov
58af1e6f18 release: Bump version to 3.3.0-b1 (#7755) 2026-07-08 12:40:24 +00:00
Ayaz Salikhov
e372c45836 chore: Enable most performance checks (#7727) 2026-07-07 21:03:56 +00:00
Ayaz Salikhov
2ebc96a4a6 perf: Use std::from/to_chars for JSON double parsing/formating (#7735) 2026-07-07 21:02:39 +00:00
Ayaz Salikhov
c5a6de6ef7 chore: Enable most cppcoreguidelines checks (#7660) 2026-07-07 13:12:52 +00:00
Marek Foss
f5e63f8a91 test: Migrate basics Beast tests to GTest (#7136)
Co-authored-by: Alex Kremer <akremer@ripple.com>
2026-07-06 19:48:36 +00:00
Marek Foss
7a153a2bce test: Migrate resource, shamap Beast tests to GTest (#7133)
Co-authored-by: Alex Kremer <akremer@ripple.com>
2026-07-06 17:26:19 +00:00
Ayaz Salikhov
f59d5c0894 chore: Enable most misc checks (#7663) 2026-07-06 15:22:11 +00:00
Jingchen
bfde271609 chore: Fix unity build (#7730)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-07-06 15:22:02 +00:00
Ayaz Salikhov
d07d2aba2e chore: Enable most modernize checks (#7664) 2026-07-06 12:19:52 +00:00
Ayaz Salikhov
c92285f10d chore: Delete dead code (#7718) 2026-07-03 15:59:20 +00:00
Ayaz Salikhov
53649cc298 chore: Enable modernize-use-constraints (#7715) 2026-07-03 14:28:15 +00:00
Ayaz Salikhov
f151293e8a chore: Enable modernize-avoid-bind (#7711) 2026-07-03 11:17:03 +00:00
Ayaz Salikhov
7ba1d76d05 chore: Enable modernize-use-auto (#7707) 2026-07-02 20:02:55 +00:00
Bart
3d847f2a60 build: Add protobuf dependencies to Nix (#7706)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-07-02 18:46:27 +00:00
yinyiqian1
6003fd03fc feat: Enable ConfidentialTransfer and BatchV1_1 (#7698) 2026-07-02 18:37:37 +00:00
Ayaz Salikhov
41622b87ae chore: Enable modernize-unary-static-assert (#7705) 2026-07-02 18:30:59 +00:00
Ayaz Salikhov
6f0f5b8bb3 chore: Make clang-tidy happy on macOS (#7701) 2026-07-02 16:26:09 +00:00
Bart
3b9e24e0e0 chore: Improve pre-commit hooks (#7702)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-07-02 15:01:30 +00:00
Timothy Banks
4c619e8a85 refactor: Retire DisallowIncomingV1 fix (#7364) 2026-07-02 14:17:32 +00:00
Pratik Mankawde
b53df32334 feat: Remove Boost.Coroutine dependency and old Coro API
Remove the legacy Boost.Coroutine infrastructure now that all callers
have been migrated to C++20 std::coroutine:

- Delete Coro.ipp: Remove the Boost.Coroutine-based Coro class and
  its suspend/resume/post/runOnJobQueue helpers.
- Remove Coro API from JobQueue.h: Remove postCoro(), Coro class
  declaration, and all Boost.Coroutine-related methods.
- Remove Boost.Coroutine from build: Drop coroutine and context from
  Boost components in cmake and conanfile.py.
- Remove BOOST_COROUTINES_NO_DEPRECATION_WARNING compile definition.
2026-03-25 15:48:17 +00:00
Pratik Mankawde
42cced50fb feat: Migrate coroutine tests from Boost.Coroutine to C++20 coroutines
Migrate Coroutine_test and JobQueue_test from Boost.Coroutine to
C++20 std::coroutine using CoroTask/CoroTaskRunner:

- Coroutine_test: Replace Coro-based coroutine tests with CoroTask
  equivalents using co_await runner->yieldAndPost().
- JobQueue_test: Replace Coro suspend/resume patterns with CoroTask
  equivalents, use pointer-by-value captures in coroutine lambdas
  to avoid dangling reference issues.
2026-03-25 15:48:17 +00:00
Pratik Mankawde
0e815aa1ac feat: Migrate production entry points from Boost.Coroutine to C++20 coroutines
Migrate all production coroutine entry points from Boost.Coroutine
to C++20 std::coroutine using the CoroTask/CoroTaskRunner primitives:

- RipplePathFind: Replace Coro suspend/resume with co_await pattern,
  add cv timeout for graceful shutdown.
- ServerHandler: Replace Coro-based processRequest with CoroTask,
  simplify coroutine lifecycle management.
- GRPCServer: Replace Coro with CoroTask for streaming RPC handlers.
- Remove Coro usage from Context.h aggregate initialization.
- Add exception handling in coroutine bodies to prevent unhandled
  exceptions from escaping the coroutine frame.
2026-03-25 15:48:10 +00:00
Pratik Mankawde
21149a81e3 feat: Add C++20 coroutine primitives: CoroTask, CoroTaskRunner, JobQueueAwaiter
Add C++20 std::coroutine based task primitives for the JobQueue:

- CoroTask<T>: A coroutine return type with RAII ownership semantics
  and symmetric transfer for efficient resumption.
- CoroTaskRunner: Manages coroutine lifecycle on the JobQueue with
  suspend/resume tracking, LocalValue preservation, and graceful
  shutdown support.
- JobQueueAwaiter: External awaiter combining yield+post atomically.
- yieldAndPost(): Inline awaiter workaround for GCC-12 codegen bug
  where external awaiters at multiple co_await points corrupt the
  coroutine state machine resume index.
- CoroTask_test: Comprehensive test suite covering task lifecycle,
  suspend/resume, shutdown, and value-returning coroutines.
- BoostToStdCoroutineSwitchPlan.md: Migration plan documentation.
2026-03-25 15:46:44 +00:00
Pratik Mankawde
b78202a99a docs: Add Boost to C++20 coroutine migration plan
Comprehensive migration plan documenting the switch from
Boost.Coroutine2 to C++20 standard coroutines in rippled, including
research analysis, implementation phases, risk assessment, and
testing strategy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 15:44:19 +00:00
1118 changed files with 56905 additions and 31480 deletions

View File

@@ -9,132 +9,64 @@ Checks: "-*,
cppcoreguidelines-*,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-capturing-lambda-coroutines,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-avoid-reference-coroutine-parameters,
-cppcoreguidelines-c-copy-assignment-signature,
-cppcoreguidelines-explicit-virtual-functions,
-cppcoreguidelines-interfaces-global-init,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-missing-std-forward,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-no-malloc,
-cppcoreguidelines-noexcept-destructor,
-cppcoreguidelines-noexcept-move-operations,
-cppcoreguidelines-noexcept-swap,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-slicing,
-cppcoreguidelines-special-member-functions,
llvm-namespace-comment,
misc-*,
-misc-anonymous-namespace-in-header,
-misc-confusable-identifiers,
-misc-coroutine-hostile-raii,
-misc-misleading-bidirectional,
-misc-misleading-identifier,
-misc-multiple-inheritance,
-misc-new-delete-overloads,
-misc-no-recursion,
-misc-non-copyable-objects,
-misc-non-private-member-variables-in-classes,
-misc-override-with-different-visibility,
-misc-predictable-rand,
-misc-unconventional-assign-operator,
-misc-uniqueptr-reset-release,
-misc-unused-parameters,
-misc-use-anonymous-namespace,
-misc-use-internal-linkage,
modernize-*,
-modernize-avoid-bind,
-modernize-avoid-c-arrays,
-modernize-avoid-c-style-cast,
-modernize-avoid-setjmp-longjmp,
-modernize-avoid-variadic-functions,
-modernize-deprecated-ios-base-aliases,
-modernize-loop-convert,
-modernize-macro-to-enum,
-modernize-min-max-use-initializer-list,
-modernize-raw-string-literal,
-modernize-redundant-void-arg,
-modernize-replace-auto-ptr,
-modernize-replace-disallow-copy-and-assign-macro,
-modernize-replace-random-shuffle,
-modernize-return-braced-init-list,
-modernize-shrink-to-fit,
-modernize-unary-static-assert,
-modernize-use-auto,
-modernize-use-bool-literals,
-modernize-use-constraints,
-modernize-use-default-member-init,
-modernize-use-integer-sign-comparison,
-modernize-use-noexcept,
-modernize-use-nullptr,
-modernize-use-std-format,
-modernize-use-std-print,
-modernize-use-trailing-return-type,
-modernize-use-transparent-functors,
-modernize-use-uncaught-exceptions,
performance-*,
-performance-avoid-endl,
-performance-enum-size,
-performance-inefficient-algorithm,
-performance-inefficient-string-concatenation,
-performance-no-int-to-ptr,
-performance-noexcept-destructor,
-performance-noexcept-move-constructor,
-performance-noexcept-swap,
-performance-type-promotion-in-math-fn,
-performance-unnecessary-copy-initialization,
-performance-unnecessary-value-param,
readability-*,
-readability-avoid-const-params-in-decls,
-readability-avoid-unconditional-preprocessor-if,
-readability-container-data-pointer,
-readability-delete-null-pointer,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-inconsistent-declaration-parameter-name,
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-misplaced-array-index,
-readability-named-parameter,
-readability-operators-representation,
-readability-qualified-auto,
-readability-redundant-access-specifiers,
-readability-redundant-control-flow,
-readability-redundant-function-ptr-dereference,
-readability-redundant-preprocessor,
-readability-redundant-smartptr-get,
-readability-redundant-string-cstr,
-readability-simplify-subscript-expr,
-readability-static-accessed-through-instance,
-readability-string-compare,
-readability-uniqueptr-delete-release,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
-readability-use-concise-preprocessor-directives
-readability-uppercase-literal-suffix
"
# ---
# bugprone-narrowing-conversions, # This will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs

View File

@@ -30,15 +30,15 @@ ignoreRegExpList:
- ABCDEFGHIJKLMNOPQRSTUVWXYZ
- ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
overrides:
- filename: "**/*_test.cpp" # all test files
- filename:
- "**/*_test.cpp" # legacy boost.test files
- "src/tests/**/*.cpp" # gtest test files
ignoreRegExpList:
- /"[^"]*"/g # double-quoted strings
- /'[^']*'/g # single-quoted strings
- /`[^`]*`/g # backtick strings
suggestWords:
- xprl->xrpl
- xprld->xrpld # cspell: disable-line not sure what this problem is....
- unsynched->unsynced # cspell: disable-line not sure what this problem is....
- unsynched->unsynced
- synched->synced
- synch->sync
words:
@@ -67,6 +67,7 @@ words:
- Btrfs
- Buildx
- canonicality
- canonicalised
- changespq
- checkme
- choco
@@ -74,6 +75,7 @@ words:
- citardauq
- clawback
- clawbacks
- clippy
- cmaketoolchain
- coeffs
- coldwallet
@@ -216,6 +218,7 @@ words:
- Nyffenegger
- onlatest
- ostr
- oxalica
- pargs
- partitioner
- paychan
@@ -244,6 +247,8 @@ words:
- Raphson
- rcflags
- replayer
- repost
- reposts
- rerandomize
- rerandomization
- rerandomized
@@ -261,6 +266,9 @@ words:
- rocksdb
- Rohrs
- roundings
- rustc
- rustfmt
- rustup
- sahyadri
- Satoshi
- scons
@@ -281,8 +289,11 @@ words:
- sles
- soci
- socidb
- sponsee
- sponsees
- SRPMS
- sslws
- stackful
- statsd
- STATSDCOLLECTOR
- stissue
@@ -302,6 +313,8 @@ words:
- takerpays
- ters
- TMEndpointv2
- toolchain
- tparam
- trixie
- tx
- txid
@@ -329,9 +342,11 @@ words:
- unserviced
- unshareable
- unshares
- unsponsored
- unsquelch
- unsquelched
- unsquelching
- unsuffixed
- unvalidated
- unveto
- unvetoed

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

View File

@@ -11,6 +11,9 @@ endfunction()
function(create_symbolic_link target link)
endfunction()
function(xrpl_add_benchmark name)
endfunction()
macro(exclude_from_default target_)
endmacro()

View File

@@ -1,10 +1,10 @@
<!--
This PR template helps you to write a good pull request description.
This PR template helps you write a good pull request description.
Please feel free to include additional useful information even beyond what is requested below.
If your branch is on a personal fork and has a name that allows it to
run CI build/test jobs (e.g. "ci/foo"), remember to rename it BEFORE
opening the PR. This avoids unnecessary redundant test runs. Renaming
opening the PR. This avoids redundant test runs. Renaming
the branch after opening the PR will close the PR.
https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-branches-in-your-repository/renaming-a-branch
-->
@@ -15,7 +15,7 @@ https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-
Please include a summary of the changes.
This may be a direct input to the release notes.
If too broad, please consider splitting into multiple PRs.
If a relevant task or issue, please link it here.
If there is a relevant task or issue, please link it here.
-->
### Context of Change
@@ -65,5 +65,5 @@ This section may not be needed if your change includes thoroughly commented unit
<!--
## Future Tasks
For future tasks related to PR.
For future tasks related to this PR.
-->

View File

@@ -40,18 +40,18 @@ listed later.
| 04 | xrpl/protocol |
| 05 | xrpl/core xrpl/resource xrpl/server |
| 06 | xrpl/ledger xrpl/nodestore xrpl/net |
| 07 | xrpl/shamap |
| 07 | xrpl/shamap xrpl/consensus |
## xrpld Modules (Application Implementation)
| Level / Tier | Module(s) |
| ------------ | -------------------------------- |
| 05 | xrpld/conditions xrpld/consensus |
| 06 | xrpld/core xrpld/peerfinder |
| 07 | xrpld/shamap xrpld/overlay |
| 08 | xrpld/app |
| 09 | xrpld/rpc |
| 10 | xrpld/perflog |
| Level / Tier | Module(s) |
| ------------ | --------------------------- |
| 05 | xrpld/conditions |
| 06 | xrpld/core xrpld/peerfinder |
| 07 | xrpld/shamap xrpld/overlay |
| 08 | xrpld/app |
| 09 | xrpld/rpc |
| 10 | xrpld/perflog |
## Test Modules

View File

@@ -1,9 +1,6 @@
Loop: xrpld.app xrpld.overlay
xrpld.app > xrpld.overlay
Loop: xrpld.app xrpld.peerfinder
xrpld.peerfinder ~= xrpld.app
Loop: xrpld.app xrpld.rpc
xrpld.rpc > xrpld.app

View File

@@ -1,8 +1,13 @@
benchmarks.libxrpl > xrpl.basics
benchmarks.libxrpl > xrpl.config
benchmarks.libxrpl > xrpl.nodestore
libxrpl.basics > xrpl.basics
libxrpl.conditions > xrpl.basics
libxrpl.conditions > xrpl.conditions
libxrpl.config > xrpl.basics
libxrpl.config > xrpl.config
libxrpl.consensus > xrpl.basics
libxrpl.consensus > xrpl.consensus
libxrpl.core > xrpl.basics
libxrpl.core > xrpl.core
libxrpl.core > xrpl.json
@@ -22,6 +27,9 @@ libxrpl.nodestore > xrpl.config
libxrpl.nodestore > xrpl.json
libxrpl.nodestore > xrpl.nodestore
libxrpl.nodestore > xrpl.protocol
libxrpl.peerfinder > xrpl.basics
libxrpl.peerfinder > xrpl.peerfinder
libxrpl.peerfinder > xrpl.protocol
libxrpl.protocol > xrpl.basics
libxrpl.protocol > xrpl.json
libxrpl.protocol > xrpl.protocol
@@ -57,9 +65,9 @@ test.app > test.jtx
test.app > test.unit_test
test.app > xrpl.basics
test.app > xrpl.config
test.app > xrpl.consensus
test.app > xrpl.core
test.app > xrpld.app
test.app > xrpld.consensus
test.app > xrpld.core
test.app > xrpld.overlay
test.app > xrpld.rpc
@@ -72,7 +80,6 @@ test.app > xrpl.server
test.app > xrpl.shamap
test.app > xrpl.tx
test.basics > test.jtx
test.basics > test.unit_test
test.basics > xrpl.basics
test.basics > xrpl.core
test.basics > xrpld.rpc
@@ -81,12 +88,9 @@ test.basics > xrpl.protocol
test.beast > xrpl.basics
test.conditions > xrpl.basics
test.conditions > xrpl.conditions
test.consensus > test.csf
test.consensus > test.jtx
test.consensus > test.unit_test
test.consensus > xrpl.basics
test.consensus > xrpld.app
test.consensus > xrpld.consensus
test.consensus > xrpl.ledger
test.consensus > xrpl.protocol
test.consensus > xrpl.shamap
@@ -101,10 +105,6 @@ test.core > xrpl.json
test.core > xrpl.protocol
test.core > xrpl.rdb
test.core > xrpl.server
test.csf > xrpl.basics
test.csf > xrpld.consensus
test.csf > xrpl.json
test.csf > xrpl.ledger
test.json > test.jtx
test.json > xrpl.json
test.jtx > test.unit_test
@@ -144,27 +144,19 @@ test.overlay > xrpl.config
test.overlay > xrpld.app
test.overlay > xrpld.core
test.overlay > xrpld.overlay
test.overlay > xrpld.peerfinder
test.overlay > xrpl.json
test.overlay > xrpl.nodestore
test.overlay > xrpl.peerfinder
test.overlay > xrpl.protocol
test.overlay > xrpl.resource
test.overlay > xrpl.server
test.overlay > xrpl.shamap
test.peerfinder > test.beast
test.peerfinder > test.unit_test
test.peerfinder > xrpl.basics
test.peerfinder > xrpld.core
test.peerfinder > xrpld.peerfinder
test.peerfinder > xrpl.protocol
test.protocol > test.jtx
test.protocol > test.unit_test
test.protocol > xrpl.basics
test.protocol > xrpld.core
test.protocol > xrpl.json
test.protocol > xrpl.protocol
test.resource > test.unit_test
test.resource > xrpl.basics
test.resource > xrpl.resource
test.rpc > test.jtx
test.rpc > xrpl.basics
test.rpc > xrpl.config
@@ -188,29 +180,30 @@ test.server > xrpld.core
test.server > xrpl.json
test.server > xrpl.protocol
test.server > xrpl.server
test.shamap > test.unit_test
test.shamap > xrpl.basics
test.shamap > xrpl.config
test.shamap > xrpl.nodestore
test.shamap > xrpl.protocol
test.shamap > xrpl.shamap
test.unit_test > xrpl.basics
test.unit_test > xrpl.protocol
tests.libxrpl > xrpl.basics
tests.libxrpl > xrpl.config
tests.libxrpl > xrpl.consensus
tests.libxrpl > xrpl.core
tests.libxrpl > xrpl.json
tests.libxrpl > xrpl.ledger
tests.libxrpl > xrpl.net
tests.libxrpl > xrpl.nodestore
tests.libxrpl > xrpl.peerfinder
tests.libxrpl > xrpl.protocol
tests.libxrpl > xrpl.protocol_autogen
tests.libxrpl > xrpl.resource
tests.libxrpl > xrpl.server
tests.libxrpl > xrpl.shamap
tests.libxrpl > xrpl.tx
xrpl.conditions > xrpl.basics
xrpl.conditions > xrpl.protocol
xrpl.config > xrpl.basics
xrpl.consensus > xrpl.basics
xrpl.consensus > xrpl.json
xrpl.consensus > xrpl.ledger
xrpl.consensus > xrpl.protocol
xrpl.core > xrpl.basics
xrpl.core > xrpl.json
xrpl.core > xrpl.protocol
@@ -225,6 +218,8 @@ xrpl.nodestore > xrpl.basics
xrpl.nodestore > xrpl.config
xrpl.nodestore > xrpl.json
xrpl.nodestore > xrpl.protocol
xrpl.peerfinder > xrpl.basics
xrpl.peerfinder > xrpl.protocol
xrpl.protocol > xrpl.basics
xrpl.protocol > xrpl.json
xrpl.protocol_autogen > xrpl.json
@@ -251,23 +246,20 @@ xrpl.tx > xrpl.protocol
xrpld.app > test.unit_test
xrpld.app > xrpl.basics
xrpld.app > xrpl.config
xrpld.app > xrpl.consensus
xrpld.app > xrpl.core
xrpld.app > xrpld.consensus
xrpld.app > xrpld.core
xrpld.app > xrpl.json
xrpld.app > xrpl.ledger
xrpld.app > xrpl.net
xrpld.app > xrpl.nodestore
xrpld.app > xrpl.peerfinder
xrpld.app > xrpl.protocol
xrpld.app > xrpl.rdb
xrpld.app > xrpl.resource
xrpld.app > xrpl.server
xrpld.app > xrpl.shamap
xrpld.app > xrpl.tx
xrpld.consensus > xrpl.basics
xrpld.consensus > xrpl.json
xrpld.consensus > xrpl.ledger
xrpld.consensus > xrpl.protocol
xrpld.core > xrpl.basics
xrpld.core > xrpl.config
xrpld.core > xrpl.core
@@ -276,21 +268,22 @@ xrpld.core > xrpl.protocol
xrpld.core > xrpl.rdb
xrpld.overlay > xrpl.basics
xrpld.overlay > xrpl.config
xrpld.overlay > xrpl.consensus
xrpld.overlay > xrpl.core
xrpld.overlay > xrpld.consensus
xrpld.overlay > xrpld.core
xrpld.overlay > xrpld.peerfinder
xrpld.overlay > xrpl.json
xrpld.overlay > xrpl.ledger
xrpld.overlay > xrpl.peerfinder
xrpld.overlay > xrpl.protocol
xrpld.overlay > xrpl.resource
xrpld.overlay > xrpl.server
xrpld.overlay > xrpl.shamap
xrpld.overlay > xrpl.tx
xrpld.peerfinder > xrpl.basics
xrpld.peerfinder > xrpl.config
xrpld.peerfinder > xrpld.app
xrpld.peerfinder > xrpld.core
xrpld.peerfinder > xrpl.protocol
xrpld.peerfinder > xrpl.peerfinder
xrpld.peerfinder > xrpl.rdb
xrpld.perflog > xrpl.basics
xrpld.perflog > xrpl.config

View File

@@ -25,24 +25,16 @@ def get_cmake_args(build_type: str, extra_args: str) -> str:
return " ".join(args)
def runs_on_event(exclude_event_types: list[str], event: str | None) -> bool:
"""Whether a config should run for the current event.
'exclude_event_types' is a list of GitHub event names (e.g.
["pull_request"]) on which the config should NOT run; an empty list means
the config runs on every event. When no event is given (event is None), no
filtering is applied.
"""
if event is None:
return True
return event not in exclude_event_types
# ---------------------------------------------------------------------------
# Input types — shapes of the JSON config files
# ---------------------------------------------------------------------------
# Every config must declare 'minimal'. Minimal configs form the reduced matrix
# built for pull requests by default; the full matrix adds the rest. Packaging
# configs declare it too, but packaging is gated in the workflow, not by it.
@dataclasses.dataclass
class LinuxConfig:
"""One entry in linux.json's 'configs' or 'package_configs' arrays."""
@@ -50,13 +42,11 @@ class LinuxConfig:
compiler: list[str]
build_type: list[str]
arch: list[str]
minimal: bool
sanitizers: list[str] = dataclasses.field(default_factory=list)
suffix: str = ""
extra_cmake_args: str = ""
image: str = "" # only used by package_configs entries
# List of GitHub event names (e.g. "pull_request") on which this config
# should NOT run. Empty means it runs on every event.
exclude_event_types: list[str] = dataclasses.field(default_factory=list)
@dataclasses.dataclass
@@ -89,11 +79,9 @@ class PlatformConfig:
"""One entry in macos.json's or windows.json's 'configs' array."""
build_type: list[str]
minimal: bool
build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug)
extra_cmake_args: str = ""
# List of GitHub event names (e.g. "pull_request") on which this config
# should NOT run. Empty means it runs on every event.
exclude_event_types: list[str] = dataclasses.field(default_factory=list)
def __post_init__(self) -> None:
if isinstance(self.build_type, str):
@@ -168,20 +156,18 @@ _ARCHS: dict[str, Architecture] = {
}
def expand_linux_matrix(
linux: LinuxFile, event: str | None = None
) -> list[MatrixEntry]:
def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]:
"""Expand a LinuxFile into a flat list of matrix entries.
Each config entry is expanded over the cross-product of its
compiler, build_type, sanitizers, and architecture lists. Configs that
exclude the current event are skipped.
compiler, build_type, sanitizers, and architecture lists. When 'minimal' is
true, only configs flagged as minimal are included.
"""
entries: list[MatrixEntry] = []
for distro, configs in linux.configs.items():
for cfg in configs:
if not runs_on_event(cfg.exclude_event_types, event):
if minimal and not cfg.minimal:
continue
# An empty sanitizers list means "one entry with no sanitizer".
effective_sanitizers = cfg.sanitizers or [""]
@@ -240,19 +226,17 @@ def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
return entries
def expand_platform_matrix(
pf: PlatformFile, event: str | None = None
) -> list[MatrixEntry]:
def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]:
"""Expand a PlatformFile (macOS or Windows) into matrix entries.
Configs that exclude the current event are skipped.
When 'minimal' is true, only configs flagged as minimal are included.
"""
platform_name, arch = pf.platform.split("/")
is_windows = platform_name == "windows"
entries: list[MatrixEntry] = []
for cfg in pf.configs:
if not runs_on_event(cfg.exclude_event_types, event):
if minimal and not cfg.minimal:
continue
for build_type in cfg.build_type:
entries.append(
@@ -292,12 +276,12 @@ if __name__ == "__main__":
action="store_true",
)
parser.add_argument(
"-e",
"--event",
help="The GitHub event name that triggered the workflow (e.g. 'push', "
"'pull_request'). Configs are filtered by their 'event_type'. If "
"omitted, no filtering is applied.",
default=None,
"-m",
"--minimal",
help="Emit only the minimal matrix (the configs flagged 'minimal'), "
"used for pull requests by default. If omitted, the full matrix is "
"emitted.",
action="store_true",
)
args = parser.parse_args()
@@ -308,15 +292,15 @@ if __name__ == "__main__":
else:
if args.config in ("linux", None):
matrix += expand_linux_matrix(
LinuxFile.load(THIS_DIR / "linux.json"), args.event
LinuxFile.load(THIS_DIR / "linux.json"), args.minimal
)
if args.config in ("macos", None):
matrix += expand_platform_matrix(
PlatformFile.load(THIS_DIR / "macos.json"), args.event
PlatformFile.load(THIS_DIR / "macos.json"), args.minimal
)
if args.config in ("windows", None):
matrix += expand_platform_matrix(
PlatformFile.load(THIS_DIR / "windows.json"), args.event
PlatformFile.load(THIS_DIR / "windows.json"), args.minimal
)
print(f"matrix={json.dumps({'include': [dataclasses.asdict(e) for e in matrix]})}")

View File

@@ -1,17 +1,31 @@
{
"image_tag": "sha-e29b523",
"image_tag": "sha-40cdf49",
"configs": {
"ubuntu": [
{
"compiler": ["clang"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": true
},
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false
},
{
"compiler": ["gcc", "clang"],
"build_type": ["Debug", "Release"],
"arch": ["amd64", "arm64"]
"arch": ["arm64"],
"minimal": false
},
{
"compiler": ["gcc", "clang"],
"build_type": ["Debug", "Release"],
"arch": ["amd64"],
"minimal": false,
"sanitizers": ["address", "undefinedbehavior"]
},
@@ -19,6 +33,7 @@
"compiler": ["gcc"],
"build_type": ["Debug"],
"arch": ["amd64"],
"minimal": true,
"suffix": "coverage",
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
},
@@ -26,6 +41,7 @@
"compiler": ["clang"],
"build_type": ["Debug"],
"arch": ["amd64"],
"minimal": false,
"suffix": "voidstar",
"extra_cmake_args": "-Dvoidstar=ON"
},
@@ -33,6 +49,7 @@
"compiler": ["clang"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false,
"suffix": "reffee",
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=1000"
},
@@ -40,9 +57,9 @@
"compiler": ["gcc"],
"build_type": ["Debug"],
"arch": ["amd64"],
"minimal": false,
"suffix": "unity",
"extra_cmake_args": "-Dunity=ON",
"exclude_event_types": ["pull_request"]
"extra_cmake_args": "-Dunity=ON"
}
],
@@ -50,7 +67,8 @@
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"]
"arch": ["amd64"],
"minimal": false
}
],
@@ -58,7 +76,8 @@
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"]
"arch": ["amd64"],
"minimal": false
}
]
},
@@ -68,6 +87,7 @@
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false,
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-577d745"
}
],
@@ -77,6 +97,7 @@
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false,
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-577d745"
}
]

View File

@@ -4,13 +4,14 @@
"configs": [
{
"build_type": "Release",
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
"minimal": true
},
{
"build_type": "Debug",
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
"build_only": true,
"exclude_event_types": ["pull_request"]
"minimal": false
}
]
}

View File

@@ -2,11 +2,11 @@
"platform": "windows/amd64",
"runner": ["self-hosted", "Windows", "dev-box-windows-2026"],
"configs": [
{ "build_type": "Release" },
{ "build_type": "Release", "minimal": true },
{
"build_type": "Debug",
"build_only": true,
"exclude_event_types": ["pull_request"]
"minimal": false
}
]
}

View File

@@ -1,4 +1,4 @@
name: Build Nix Docker images
name: Build `nix` Docker images
on:
push:
@@ -8,20 +8,24 @@ on:
- ".github/workflows/build-nix-images.yml"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
- "nix/**"
- "!nix/docker/README.md"
- "!nix/devshell.nix"
- "bin/check-tools.sh"
- "bin/default-loader-path.sh"
- "bin/install-sanitizer-libs.sh"
pull_request:
paths:
- ".github/workflows/build-nix-images.yml"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
- "nix/**"
- "!nix/docker/README.md"
- "!nix/devshell.nix"
- "bin/check-tools.sh"
- "bin/default-loader-path.sh"
- "bin/install-sanitizer-libs.sh"
workflow_dispatch:
@@ -36,7 +40,7 @@ defaults:
jobs:
build-merge:
name: Build and push nix-${{ matrix.distro.name }}
name: Build and push `nix-${{ matrix.distro.name }}` image
permissions:
contents: read
packages: write
@@ -54,7 +58,7 @@ jobs:
base_image: debian:bookworm
- name: rhel
base_image: registry.access.redhat.com/ubi9/ubi:latest
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
with:
image_name: xrpld/nix-${{ matrix.distro.name }}
dockerfile: nix/docker/Dockerfile

View File

@@ -1,4 +1,4 @@
name: Build packaging Docker images
name: Build `packaging` Docker images
on:
push:
@@ -26,7 +26,7 @@ defaults:
jobs:
build-merge:
name: Build and push packaging-${{ matrix.distro.name }}
name: Build and push `packaging-${{ matrix.distro.name }}` image
permissions:
contents: read
packages: write
@@ -38,7 +38,7 @@ jobs:
base_image: debian:bookworm
- name: rhel
base_image: registry.access.redhat.com/ubi9/ubi:latest
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
with:
image_name: xrpld/packaging-${{ matrix.distro.name }}
dockerfile: package/Dockerfile

View File

@@ -0,0 +1,38 @@
name: Build `pre-commit` Docker image
on:
push:
branches:
- develop
paths:
- ".github/workflows/build-pre-commit-image.yml"
- "bin/pre-commit/Dockerfile"
- "rust-toolchain.toml"
pull_request:
paths:
- ".github/workflows/build-pre-commit-image.yml"
- "bin/pre-commit/Dockerfile"
- "rust-toolchain.toml"
workflow_dispatch:
concurrency:
# Read `on-trigger.yml` for the rationale behind this concurrency group name.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
build-merge:
name: Build and push `pre-commit` image
permissions:
contents: read
packages: write
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
with:
image_name: xrpld/pre-commit
dockerfile: bin/pre-commit/Dockerfile
base_image: ubuntu:26.04
push: ${{ github.event_name == 'push' }}

View File

@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Write PR body to file
env:

View File

@@ -20,4 +20,4 @@ on:
jobs:
check_title:
if: ${{ github.event.pull_request.draft != true }}
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@d7c65e49225a38f6d8010eacf017bb5a98d7476c

114
.github/workflows/check-tools.yml vendored Normal file
View File

@@ -0,0 +1,114 @@
# Verifies the committed snapshots of `bin/check-tools.sh` output for each Nix
# environment (see nix/check-tools/). If the environment changes — a new image
# tag, an updated flake.lock, a different tool list — without the matching
# snapshot being regenerated and committed, this workflow fails so the drift is
# caught in review.
#
# To regenerate the snapshots, see nix/check-tools/README.md.
name: Check tools
on:
pull_request:
paths:
- ".github/workflows/check-tools.yml"
- ".github/scripts/strategy-matrix/linux.json"
- "bin/check-tools.sh"
- "nix/**"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
push:
branches:
- "develop"
paths:
- ".github/workflows/check-tools.yml"
- ".github/scripts/strategy-matrix/linux.json"
- "bin/check-tools.sh"
- "nix/**"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
# The nix-nixos image tag is pinned alongside the build matrix in linux.json,
# so snapshots are checked against the exact image CI builds against.
linux-image-tag:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Read nix image tag
id: tag
run: echo "tag=$(jq -r .image_tag .github/scripts/strategy-matrix/linux.json)" >>"${GITHUB_OUTPUT}"
# One job for all environments; they differ only in whether the tools come
# from the nix-nixos container (Linux) or `nix develop` (macOS).
check-tools:
needs: linux-image-tag
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
snapshot: nix/check-tools/nix-ubuntu-amd64.txt
nix_develop: false
- runner: ubuntu-24.04-arm
snapshot: nix/check-tools/nix-ubuntu-arm64.txt
nix_develop: false
- runner: macos-26-apple-clang-21
snapshot: nix/check-tools/macos.txt
nix_develop: true
runs-on: ${{ matrix.runner }}
# Linux runs inside the pinned nix-nixos image; macOS runs natively and uses
# the flake's dev shell instead (see the run step below).
container: ${{ !matrix.nix_develop && format('ghcr.io/xrplf/xrpld/nix-ubuntu:{0}', needs.linux-image-tag.outputs.tag) || null }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
with:
enable_ccache: false
- name: Regenerate snapshot
env:
CHECK_TOOLS_SKIP_CLONE: "1"
# check-tools.sh skips some macOS tools when CI is set; the snapshots
# capture the full `nix develop` environment, so unset it here.
CI: ""
run: |
if [ "${{ matrix.nix_develop }}" = "true" ]; then
# `nix develop` prints the dev-shell greeting first; keep only the
# check-tools.sh output (from the "Detected OS:" line onward).
nix --extra-experimental-features "nix-command flakes" develop \
-c bash bin/check-tools.sh | sed -n '/^Detected OS:/,$p' >"${{ matrix.snapshot }}"
else
bash bin/check-tools.sh >"${{ matrix.snapshot }}"
fi
- name: Verify snapshot is up to date
run: |
if ! git diff --exit-code -- "${{ matrix.snapshot }}"; then
echo "::error::${{ matrix.snapshot }} is out of date. Regenerate it (see nix/check-tools/README.md) and commit the result."
exit 1
fi
- name: Upload regenerated snapshot
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: check-tools-${{ runner.os }}-${{ runner.arch }}
path: ${{ matrix.snapshot }}

View File

@@ -14,6 +14,7 @@ permissions:
jobs:
main:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'IgnoreConflicts') }}
runs-on: ubuntu-latest
steps:
- name: Check if PRs are dirty

View File

@@ -1,7 +1,11 @@
# This workflow runs all workflows to check, build and test the project on
# various Linux flavors, as well as on MacOS and Windows, on every push to a
# user branch. However, it will not run if the pull request is a draft unless it
# has the 'DraftRunCI' label. For commits to PRs that target a release branch,
# This workflow runs workflows to check, build and test the project
# on every meaningful change on pull_request.
# However, it will not run if the PR is a draft
# unless it has the 'DraftRunCI' or 'Full CI build' label.
#
# By default a PR builds only a minimal matrix.
# The full matrix runs once the PR is labeled "Ready to merge" or "Full CI build".
# For commits to PRs that target a release branch,
# it also uploads the libxrpl recipe to the Conan remote.
name: PR
@@ -15,8 +19,16 @@ on:
- reopened
- synchronize
- ready_for_review
# Trigger on label changes so toggling "Ready to merge" or "Full CI build"
# switches between the minimal and full matrix without needing a new push.
- labeled
- unlabeled
concurrency:
# A single per-ref group with cancel-in-progress means any newer run (a push
# or a label change) supersedes the in-progress one for that ref. Keeping
# exactly one authoritative run per ref ensures a fast do-nothing run can never
# mask a real build's checks.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@@ -25,15 +37,22 @@ defaults:
shell: bash
jobs:
# This job determines whether the rest of the workflow should run. It runs
# when the PR is not a draft (which should also cover merge-group) or
# has the 'DraftRunCI' label.
# This job determines whether the rest of the workflow should run at all,
# based on the current set of labels: it runs when the PR is not a draft
# (which should also cover merge-group) or has the 'DraftRunCI' or
# 'Full CI build' label. Whether a build then happens, and whether it is the
# minimal or full matrix, is decided further below and in the strategy matrix.
should-run:
if: ${{ !github.event.pull_request.draft || contains(github.event.pull_request.labels.*.name, 'DraftRunCI') }}
if: >-
${{
!github.event.pull_request.draft
|| contains(github.event.pull_request.labels.*.name, 'DraftRunCI')
|| contains(github.event.pull_request.labels.*.name, 'Full CI build')
}}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Determine changed files
# This step checks whether any files have changed that should
# cause the next jobs to run. We do it this way rather than
@@ -71,6 +90,7 @@ jobs:
.clang-tidy
.codecov.yml
bin/check-tools.sh
bin/default-loader-path.sh
cfg/**
cmake/**
conan/**
@@ -91,15 +111,17 @@ jobs:
# least one of:
# * Any of the files checked in the `changes` step were modified
# * The PR is NOT a draft and is labeled "Ready to merge"
# * The PR is labeled "Full CI build" (draft or not)
# * The workflow is running from the merge queue
id: go
env:
FILES: ${{ steps.changes.outputs.any_changed }}
DRAFT: ${{ github.event.pull_request.draft }}
READY: ${{ contains(github.event.pull_request.labels.*.name, 'Ready to merge') }}
FULL: ${{ contains(github.event.pull_request.labels.*.name, 'Full CI build') }}
MERGE: ${{ github.event_name == 'merge_group' }}
run: |
echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FILES == 'true' || env.MERGE == 'true' }}" >>"${GITHUB_OUTPUT}"
echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FULL == 'true' || env.FILES == 'true' || env.MERGE == 'true' }}" >>"${GITHUB_OUTPUT}"
cat "${GITHUB_OUTPUT}"
outputs:
go: ${{ steps.go.outputs.go == 'true' }}
@@ -142,7 +164,10 @@ jobs:
package:
needs: [should-run, build-test]
if: ${{ needs.should-run.outputs.go == 'true' }}
# Packaging consumes the debian/rhel release binaries, which are only built
# by the full matrix. Skip it for pull requests that ran only the minimal
# matrix (i.e. not yet labeled "Ready to merge" or "Full CI build").
if: ${{ needs.should-run.outputs.go == 'true' && (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'Ready to merge') || contains(github.event.pull_request.labels.*.name, 'Full CI build')) }}
uses: ./.github/workflows/reusable-package.yml
upload-recipe:

View File

@@ -28,6 +28,7 @@ on:
- ".clang-tidy"
- ".codecov.yml"
- "bin/check-tools.sh"
- "bin/default-loader-path.sh"
- "cfg/**"
- "cmake/**"
- "conan/**"

View File

@@ -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@1bde119a1ab71305ba5d3716e7a82cea1c7bdede
uses: XRPLF/actions/.github/workflows/pre-commit.yml@3ba08d6ddf114092891d48491fc2e26c3ba15552
with:
runs_on: ubuntu-latest
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-f56b79f" }'

View File

@@ -41,13 +41,13 @@ env:
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-40cdf49
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
with:
enable_ccache: false

View File

@@ -110,10 +110,10 @@ jobs:
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
with:
enable_ccache: ${{ inputs.ccache_enabled }}
@@ -124,7 +124,7 @@ jobs:
- name: Check tools
env:
CHECK_TOOLS_SKIP_CLONE: "1"
run: ./bin/check-tools.sh
run: ./bin/check-tools.sh || true
- name: Print build environment
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
@@ -223,11 +223,13 @@ jobs:
BUILD_TYPE: ${{ inputs.build_type }}
CMAKE_TARGET: ${{ inputs.cmake_target }}
run: |
set -o pipefail
cmake \
--build . \
--config "${BUILD_TYPE}" \
--parallel "${BUILD_NPROC}" \
--target "${CMAKE_TARGET}"
--target "${CMAKE_TARGET}" \
2>&1 | tee "${GITHUB_WORKSPACE}/build.log"
- name: Show ccache statistics
if: ${{ inputs.ccache_enabled }}
@@ -322,27 +324,46 @@ jobs:
PRELOAD=""
fi
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee "${GITHUB_WORKSPACE}/unittest.log"
- name: Show test failure summary
if: ${{ failure() && !inputs.build_only }}
env:
WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
# Smoke-run every benchmark module with a single repetition to confirm the
# benchmarks still build and execute. This is a correctness check, not a
# performance measurement, so it is skipped for instrumented builds
# (sanitizers/coverage/voidstar), where it would be slow and meaningless,
# and on Windows, where the `install` target does not build them.
- name: Run the benchmarks
if: ${{ !inputs.build_only && runner.os != 'Windows' && env.SANITIZERS_ENABLED == 'false' && env.COVERAGE_ENABLED != 'true' && env.VOIDSTAR_ENABLED != 'true' }}
working-directory: ${{ env.BUILD_DIR }}
run: |
if [ ! -d "${WORKING_DIR}" ]; then
echo "Working directory '${WORKING_DIR}' does not exist."
exit 0
fi
rc=0
while IFS= read -r bench; do
echo "::group::${bench}"
"./${bench}" --benchmark_repetitions=1 || rc=1
echo "::endgroup::"
done < <(find src/benchmarks -type f -perm -u+x -name 'xrpl.bench.*')
exit "${rc}"
cd "${WORKING_DIR}"
- name: Show build/test failure summary
if: ${{ failure() }}
run: |
cd "${GITHUB_WORKSPACE}"
if [ ! -f unittest.log ]; then
echo "unittest.log not found; embedded tests may not have run."
exit 0
fi
if ! grep -E "failed" unittest.log; then
echo "Log present but no failure lines found in unittest.log."
if [ -f unittest.log ]; then
if ! grep -E "failed" unittest.log | grep -vE "^I[0-9]|^[0-9]+> (ERR:|FTL:)"; then
echo "unittest.log present but no failure lines found."
fi
elif [ -f build.log ]; then
# GCC/Clang emit "error:" (covers "fatal error:"); MSVC emits
# "error C####:", "error LNK####:", and "fatal error LNK####:".
# -A6 prints the lines that follow each match (source line, caret,
# notes, and the "N errors generated" tally) to capture the whole
# diagnostic block.
if ! grep -E -A6 "error:|error C[0-9]{4}|error LNK[0-9]{4}|fatal error" build.log; then
echo "build.log present but no compile errors found."
fi
else
echo "unittest.log/build.log not found; something went wrong."
exit 1
fi
- name: Debug failure (Linux)
if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }}

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check levelization
run: python .github/scripts/levelization/generate.py
- name: Check for differences

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check definitions
run: .github/scripts/rename/definitions.sh .
- name: Check copyright notices

View File

@@ -34,16 +34,16 @@ jobs:
needs: [determine-files]
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-e29b523"
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-40cdf49"
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
with:
enable_ccache: false

View File

@@ -27,10 +27,10 @@ jobs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
@@ -54,7 +54,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download pre-built binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1

View File

@@ -23,10 +23,10 @@ jobs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
@@ -35,5 +35,8 @@ jobs:
id: generate
env:
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}', inputs.os) || '' }}
GENERATE_EVENT: ${{ github.event_name }}
run: ./generate.py ${GENERATE_CONFIG} --event="${GENERATE_EVENT}" >>"${GITHUB_OUTPUT}"
# Run only the minimal matrix for pull requests that are not yet
# labeled "Ready to merge" or "Full CI build". Any other event (merge
# queue, push, schedule, manual dispatch) runs the full matrix.
GENERATE_MINIMAL: ${{ (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'Ready to merge') && !contains(github.event.pull_request.labels.*.name, 'Full CI build')) && '--minimal' || '' }}
run: ./generate.py ${GENERATE_CONFIG} ${GENERATE_MINIMAL} >>"${GITHUB_OUTPUT}"

View File

@@ -40,14 +40,14 @@ defaults:
jobs:
upload:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-40cdf49
env:
REMOTE_NAME: ${{ inputs.remote_name }}
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
CONAN_PASSWORD_XRPLF: ${{ secrets.remote_password }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Generate build version number
id: version

View File

@@ -65,10 +65,10 @@ jobs:
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
with:
enable_ccache: false

3
.gitignore vendored
View File

@@ -81,6 +81,9 @@ DerivedData
# Python
__pycache__
# Rust build artifacts.
target/
# Direnv's directory
/.direnv

View File

@@ -32,6 +32,11 @@ repos:
# as standalone translation units, so they have no compile_commands.json
# entry to lint (verify_headers checks them transitively).
exclude: '^include/xrpl/protocol_autogen|\.ipp$'
# run-clang-tidy --fix may edit headers included by files it is not run on,
# so pre-commit must not split the files across parallel hook invocations.
# The script determines the staged files itself and lets run-clang-tidy
# handle parallelism internally.
pass_filenames: false
- id: fix-include-style
name: fix include style
entry: ./bin/pre-commit/fix_include_style.py
@@ -43,9 +48,14 @@ repos:
language: python
entry: ./bin/pre-commit/fix_pragma_once.py
files: \.(h|hpp)$
- id: check-doxygen-style
name: check Doxygen comment style
entry: ./bin/pre-commit/check_doxygen_style.py
language: python
types_or: [c++, c]
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5
rev: f4d7745e17a28aad7eed2f4874ca8d1568c11c4c # frozen: v22.1.8
hooks:
- id: clang-format
args: [--style=file]
@@ -58,7 +68,7 @@ repos:
- id: gersemi
- repo: https://github.com/rbubley/mirrors-prettier
rev: 39e2973981e6d2f9b6c543b0086a2d2393abdc89 # frozen: v3.9.4
rev: 9337a74165b178ae2c766f60bee7252a0f06f3e8 # frozen: v3.9.5
hooks:
- id: prettier
args: [--end-of-line=auto]
@@ -90,20 +100,19 @@ repos:
- repo: https://github.com/streetsidesoftware/cspell-cli
rev: ea11f9efc0bec520073405bc30552da887ba71bc # frozen: v10.0.1
hooks:
- id: cspell # Spell check changed files
- id: cspell
name: check changed files spelling
exclude: |
(?x)^(
.config/cspell.config.yaml|
\.cspell\.config\.yaml|
include/xrpl/protocol_autogen/(transactions|ledger_entries)/.*
)$
- id: cspell # Spell check the commit message
- id: cspell
name: check commit message spelling
args:
- --no-must-find-files
- --no-progress
- --no-summary
- --files
- .git/COMMIT_EDITMSG
stages: [commit-msg]
- repo: local

View File

@@ -28,6 +28,9 @@ This section contains changes targeting a future version.
### Additions
- `account_tx`: Added an optional `delegate` request object to filter delegated transactions. The object requires `delegate_filter`, which must be either `actor` for transactions owned by the requested account but signed by another account, or `authorizer` for transactions signed by the requested account on behalf of another account. The optional `counter_party` account narrows the results to a specific signer/delegate for `actor` or a specific owner/delegator for `authorizer`. Malformed `delegate`, `delegate_filter`, and `counter_party` values return standard invalid field errors, and invalid account IDs return `actMalformed`.
When paginating delegate-filtered queries, a marker from a delegate-filtered query includes a `delegate` flag and is only valid for follow-up requests that also supply `delegate` (mixing marker conventions returns `invalidParams`). Because filtering is applied after the ledger scan, a page may contain fewer results than `limit` (possibly zero) while still returning a marker, so callers must continue until no marker is present.
- `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)):

View File

@@ -25,7 +25,7 @@ You can verify that the required tools are installed and runnable with:
| ----------- | --------------- |
| GCC | 15.2 |
| Clang | 22 |
| Apple Clang | 17 |
| Apple Clang | 21 |
| MSVC | 19.44[^windows] |
## Operating Systems

View File

@@ -131,6 +131,10 @@ else()
endif()
target_link_libraries(xrpl_libs INTERFACE ${nudb})
if(benchmark)
find_package(benchmark REQUIRED)
endif()
if(coverage)
include(XrplCov)
endif()
@@ -145,3 +149,7 @@ if(tests)
include(CTest)
add_subdirectory(src/tests/libxrpl)
endif()
if(benchmark)
add_subdirectory(src/benchmarks/libxrpl)
endif()

4
CODEOWNERS Normal file
View File

@@ -0,0 +1,4 @@
# By default, anyone can review changes.
# The CI tooling team should review changes to the CI configuration.
/.github/ @XRPLF/ci-tooling

View File

@@ -83,8 +83,11 @@ If you create new source files, they must be organized as follows:
`src/libxrpl`.
- All other non-test files must go under `src/xrpld`.
- All test source files must go under `src/test`.
- All benchmark source files must go under `src/benchmarks`.
The source must be formatted according to the style guide below.
The source must be formatted according to the style guide below. The easiest
way to satisfy this is to install the [`pre-commit`](#pre-commit-hooks) hooks,
which format and lint your changes automatically on every commit.
Header includes must be [levelized](.github/scripts/levelization).
@@ -212,13 +215,61 @@ This is a non-exhaustive list of recommended style guidelines. These are
not always strictly enforced and serve as a way to keep the codebase
coherent rather than a set of _thou shalt not_ commandments.
## Pre-commit hooks
We use the [`pre-commit`](https://pre-commit.com/) framework to run the
formatting and linting tools that keep the codebase consistent. `pre-commit`
runs each tool configured in
[`.pre-commit-config.yaml`](./.pre-commit-config.yaml) in its own isolated
environment, so you don't need to install most of the individual tools
yourself. The version of each hook sourced from an external repository
(`clang-format`, `gersemi`, etc.) is pinned in that file, so running the hooks
locally uses exactly the same versions as CI. A few `local` hooks — most notably
`clang-tidy` — run tools from your own environment; see
[Installing clang-tidy](#installing-clang-tidy) for how to get those.
To get started, install `pre-commit` and enable the git hook scripts:
```bash
pip install pre-commit
pre-commit install
```
Once installed, the hooks run automatically on your staged files every time you
`git commit`. You can also run them on demand:
```bash
# Run all hooks against only the staged files
pre-commit run
# Run all hooks against every file in the repository
pre-commit run --all-files
# Run a single hook (e.g. clang-format) against all files
pre-commit run clang-format --all-files
```
The hooks configured in this repository include, among others:
- `clang-format` — C++/proto formatting (see [Formatting](#formatting))
- `clang-tidy` — C++ static analysis (see [Clang-tidy](#clang-tidy)); opt in with `TIDY=1`
- `fix-include-style`, `fix-pragma-once`, `check-doxygen-style` — C++ hygiene
- `gersemi` — CMake formatting
- `prettier`, `black`, `shfmt` — formatting for JavaScript/JSON/Markdown, Python, and shell
- `cspell` — spell checking
The same hooks run in CI on every pull request, so running them locally before
you push helps you avoid CI failures.
## Formatting
All code must conform to `clang-format` version 22,
according to the settings in [`.clang-format`](./.clang-format),
unless the result would be unreasonably difficult to read or maintain.
To demarcate lines that should be left as-is, surround them with comments like
this:
All code must conform to `clang-format`, according to the settings in
[`.clang-format`](./.clang-format), unless the result would be unreasonably
difficult to read or maintain. The `clang-format` version is pinned in
[`.pre-commit-config.yaml`](./.pre-commit-config.yaml), so the
[`pre-commit`](#pre-commit-hooks) hook always formats with the same version as
CI. To demarcate lines that should be left as-is, surround them with comments
like this:
```
// clang-format off
@@ -226,9 +277,21 @@ this:
// clang-format on
```
You can format individual files in place by running `clang-format -i <file>...`
The easiest way to format your changes is to let the `pre-commit` hook run
automatically on commit, or to run it manually:
```bash
pre-commit run clang-format --all-files
```
You can also format individual files in place by running `clang-format -i <file>...`
from any directory within this project.
> [!NOTE]
> This uses whatever `clang-format` version is installed locally, which may
> differ from the pinned version used by `pre-commit` and CI, so the results
> can vary.
There is a Continuous Integration job that runs clang-format on pull requests. If the code doesn't comply, a patch file that corrects auto-fixable formatting issues is generated.
To download the patch file:
@@ -239,13 +302,6 @@ To download the patch file:
4. Download the zip file and extract it to your local git repository. Run `git apply [patch-file-name]`.
5. Commit and push.
You can install a pre-commit hook to automatically run `clang-format` before every commit:
```
pip3 install pre-commit
pre-commit install
```
## Clang-tidy
All code must pass `clang-tidy` checks according to the settings in [`.clang-tidy`](./.clang-tidy).
@@ -267,7 +323,7 @@ Before running clang-tidy, you must build the project to generate required files
#### Via pre-commit (recommended)
If you have already installed the pre-commit hooks (see above), you can run clang-tidy on your staged files using:
If you have already installed the [`pre-commit`](#pre-commit-hooks) hooks, you can run clang-tidy on your staged files using:
```
TIDY=1 pre-commit run clang-tidy

View File

@@ -30,8 +30,10 @@ missing=()
checked=0
# check <name> [probe-command...]
# Runs the probe (default: "<name> --version") quietly. Records <name> as
# missing if the command is not found or exits non-zero.
# Runs the probe (default: "<name> --version"), capturing both stdout and
# stderr, and prints one aligned line: the status, the name, and the first
# non-blank line of the probe output (its version). Records <name> as missing
# if the command is not found or exits non-zero.
check() {
local name="$1"
shift
@@ -40,10 +42,11 @@ check() {
probe=("${name}" --version)
fi
echo "Checking ${name}..."
checked=$((checked + 1))
if "${probe[@]}" | head -n 1; then
printf ' [ ok ] %s\n' "${name}"
local output version
if output="$("${probe[@]}" 2>&1)"; then
version="$(printf '%s\n' "${output}" | grep -m1 '[^[:space:]]' || true)"
printf ' [ ok ] %-20s %s\n' "${name}" "${version}"
else
printf ' [MISS] %s\n' "${name}"
missing+=("${name}")
@@ -85,12 +88,14 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
check file
check less
check make
check netstat which netstat
# net-tools netstat reports "net-tools X.Y"; macOS ships BSD netstat with no
# version flag, so fall back to a presence marker there.
check netstat sh -c 'command -v netstat >/dev/null && { netstat --version 2>&1 | grep -m1 -oE "net-tools [0-9.]+" || echo present; }'
check ninja
check perl
check perl perl -e 'print "$^V\n"'
check pkg-config
check vim
check zip
check zip bash -c 'zip --version 2>&1 | grep -m1 -oE "Zip [0-9.]+"'
# These tools are present in our Linux CI images and in local development
# setups, but not in the macOS CI environment. So check them everywhere
@@ -110,6 +115,23 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
fi
fi
# Rust toolchain. Part of the Nix commonPackages, so available on both Linux
# and macOS. The cargo plugins are invoked through cargo (`cargo <sub>`), which
# resolves the matching `cargo-<sub>` binary on PATH; `--version` is offline and
# does not need a Cargo project.
if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
echo
echo "Rust toolchain:"
check cargo
check cargo-audit cargo audit --version
check cargo-llvm-cov cargo llvm-cov --version
check cargo-nextest cargo nextest --version
check clippy clippy-driver --version
check rust-analyzer
check rustc
check rustfmt
fi
# GCC is the default compiler on Linux. macOS uses the system Apple Clang
# instead, so GCC/g++/gcov are not expected there.
if [ "${os}" = "linux" ]; then

55
bin/pre-commit/Dockerfile Normal file
View File

@@ -0,0 +1,55 @@
ARG BASE_IMAGE=ubuntu:26.04
FROM ${BASE_IMAGE}
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
ENTRYPOINT ["/bin/bash"]
ARG DEBIAN_FRONTEND=noninteractive
RUN <<EOF
pkgs=()
pkgs+=(curl) # Required to install nix.
pkgs+=(doxygen) # Needed for Clio's check-doxygen-docs.sh.
pkgs+=(git) # Required for prepare-runner.
pkgs+=(libatomic1) # Required to run pre-commit provided `node`.
pkgs+=(python3) # Python 3 interpreter.
pkgs+=(python3-pip) # Package manager for Python applications.
pkgs+=(xz-utils) # Required to install nix
apt-get update
apt-get install -y --no-install-recommends "${pkgs[@]}"
apt-get clean
rm -rf /var/lib/apt/lists/*
EOF
ARG PRE_COMMIT_VERSION=4.6.0
RUN pip install --no-cache --break-system-packages \
pre-commit==${PRE_COMMIT_VERSION}
RUN sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon --yes
# Add nix to PATH and set NIX environment variables,
# so nix is available in all shells including non-interactive shells (e.g., GitHub Actions).
ENV PATH="/nix/var/nix/profiles/default/bin:${PATH}"
ENV NIX_PROFILES="/nix/var/nix/profiles/default"
ENV NIX_SSL_CERT_FILE="/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"
# Verify nix installation
RUN nix --version
ENV RUSTUP_HOME="/opt/rust/rustup"
ENV CARGO_HOME="/opt/rust/cargo"
ENV PATH="/opt/rust/cargo/bin:${PATH}"
WORKDIR /tmp
COPY rust-toolchain.toml /tmp/rust-toolchain.toml
RUN <<EOF
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --profile minimal --default-toolchain none
rustup toolchain install
rustup show
cargo fmt --version
EOF
WORKDIR /

View File

@@ -0,0 +1,440 @@
#!/usr/bin/env python3
"""
Check C++ Doxygen comment style.
Enforces the house convention for documentation comments:
* Use ``/** ... */`` blocks, not ``///``, ``//!`` or ``/*! ... */``; a plain
``/* ... */`` that contains Doxygen commands is a doc comment missing its
second star. Trailing member-after comments use ``///<`` (not ``//!<``,
``/*!< ... */`` or ``/**< ... */`` -- the block forms get reflowed and
mis-attached by clang-format on packed enum values, the line form does not).
* ``/**`` sits alone on its line; the closing ``*/`` sits alone on its line.
* Every content line is prefixed with `` * `` (no bare-indented continuation).
* The first content line is flush (not over-indented).
* Doxygen commands use the ``@cmd`` form, not ``\\cmd``.
* Use ``@return`` / ``@throws`` rather than prose ``Returns:`` / ``Throws:``.
* A plain ``//`` comment carrying a block-level ``@command`` (``@param``,
``@return``, ``@see``, ...) is documentation and must be a ``/** ... */``
block (Doxygen ignores ``//``).
* Use canonical command spellings: ``@return`` (not ``@returns``),
``@throws`` (not ``@throw``), ``@see`` (not ``@sa``).
* Order block tags ``@tparam`` -> ``@param`` -> ``@return``. (Whether
``@param`` order matches the signature is not checked here -- too fragile to
parse; Doxygen's WARN_IF_DOC_ERROR covers name mismatches.)
* One-liners are expanded to three lines, EXCEPT bare markers ``@{`` / ``@}``
/ ``@cond [label]`` / ``@endcond`` / ``@file [name]`` which stay on one line.
Left intentionally alone (recognized, valid Doxygen that is not this style's
concern):
* ``///<`` trailing "member-after" comments (the house form).
* Divider lines made only of slashes (``//////////``).
* Plain ``/* ... */`` (non-Doxygen) comments.
Usage:
check_doxygen_style.py [FILE ...] # explicit files
check_doxygen_style.py # default: src/ and include/ trees
Exit status is non-zero if any violation is found.
"""
import argparse
import re
import sys
from collections.abc import Iterable, Iterator
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
class Category(Enum):
"""A kind of style violation: a printed ``label`` and its ``description``.
The description is the default message; a few categories whose wording
depends on the offending text (see ``Finding.detail``) override it.
"""
def __init__(self, label: str, description: str) -> None:
self.label = label
self.description = description
BACKSLASH_COMMAND = ("backslash-command", "use the @cmd form, not \\cmd")
WRONG_COMMAND = ("wrong-command", "use the canonical command spelling")
TRIPLE_SLASH = ("triple-slash", "use a /** ... */ block instead of ///")
QT_MEMBER = ("qt-member", "use ///< instead of //!<")
QT_LINE = ("qt-line", "use a /** ... */ block instead of //!")
BLOCK_MEMBER = ("block-member", "use ///< instead of /**<")
QT_BLOCK_MEMBER = ("qt-block-member", "use ///< instead of /*!<")
DOC_IN_LINE_COMMENT = (
"doc-in-line-comment",
"use a /** ... */ block for documentation, not //",
)
QT_COMMENT = ("qt-comment", "use /** instead of /*!")
SINGLE_LINE_BLOCK = (
"single-line-block",
"expand one-line /** ... */ to a multi-line block "
"(markers @{ @} @cond @endcond @file may stay)",
)
TEXT_ON_OPENER = ("text-on-opener", "move text off the /** opener line")
BARE_CONTINUATION = ("bare-continuation", 'prefix continuation lines with " * "')
OVER_INDENTED = ("over-indented", "first content line is over-indented")
OVER_INDENTED_TAG = (
"over-indented-tag",
'Doxygen tag over-indented; use a single space after "*"',
)
COMBINED_MARKER = (
"combined-marker",
"scope marker @{ / @} should be its own single-line /** @{ */ block",
)
PROSE_LABEL = ("prose-label", "use a Doxygen tag instead of a prose label")
CONTENT_ON_CLOSER = ("content-on-closer", "move content off the closing */ line")
PLAIN_BLOCK_DOC = (
"plain-block-doc",
"documentation comment must open with /** not /*",
)
TAG_ORDER = (
"tag-order",
"block tags out of order; expected @tparam, then @param, then @return",
)
@dataclass(frozen=True)
class Finding:
"""A single style violation at a 1-based line number.
``detail`` overrides the category's default description when the message
depends on the offending text (e.g. which command was misspelled).
"""
line: int
category: Category
detail: str | None = None
@property
def message(self) -> str:
return self.detail if self.detail is not None else self.category.description
DEFAULT_ROOTS = ("src", "include")
EXTS = {".h", ".hpp", ".cpp", ".ipp", ".cxx", ".cc"}
# Every Doxygen command we recognize when written with a backslash (\cmd).
_ALL_COMMANDS = (
"brief|param|tparam|return|returns|retval|note|warning|pre|post|see|sa|ref|"
"throw|throws|exception|deprecated|details|code|endcode|verbatim|endverbatim|"
"li|arg|c|internal|since|todo|attention|remark|remarks|ingroup|defgroup"
)
# Block-level tags whose over-indentation we flag inside a block body.
_BLOCK_TAGS = (
"param|tparam|returns?|retval|brief|throws?|note|warning|"
"pre|post|see|sa|details|deprecated"
)
# Tags that, appearing anywhere in a comment, mark it as documentation.
_ANY_DOC_TAGS = (
"param|tparam|returns?|retval|brief|throws?|note|warning|pre|post|see|sa"
)
# Tags that make a plain // comment a mis-styled doc comment.
_LINE_DOC_TAGS = "brief|param|tparam|returns?|retval|throws?|note|see|pre|post"
# \cmd that should be @cmd.
RE_BACKSLASH_CMD = re.compile(r"\\(" + _ALL_COMMANDS + r")\b")
# Bare markers that may legitimately stay on a single line.
RE_MARKER = re.compile(r"^@(\{|\}|cond(\s.*)?|endcond|file(\s.*)?)$")
# Prose section labels that should be Doxygen tags.
RE_PROSE_LABEL = re.compile(r"^\*\s(Returns|Throws|Exceptions):\s*$")
# An over-indented block tag: "*" followed by 2+ spaces then the tag.
RE_OVERINDENTED_TAG = re.compile(r"^\*\s{2,}@(" + _BLOCK_TAGS + r")\b")
# Any documentation tag (used to spot a doc comment hiding in a plain /* */).
RE_ANY_DOC_TAG = re.compile(r"@(" + _ANY_DOC_TAGS + r")\b")
# A documentation tag inside a // comment.
RE_LINE_DOC_TAG = re.compile(r"@(" + _LINE_DOC_TAGS + r")\b")
# Order-relevant tags, for the @tparam -> @param -> @return ordering check.
RE_ORDER_TAG = re.compile(r"^\*\s*@(param|tparam|returns?|retval)\b")
# First content line indented by 2+ spaces after the "*".
RE_FIRST_OVERINDENT = re.compile(r"^\s*\*\s{2,}\S")
# A scope marker @{ / @} sharing a comment with other text.
RE_COMBINED_MARKER = re.compile(r"^\*\s*@[{}]\s*$")
# Non-canonical command spellings -> the house spelling (bare command names).
# Used both to flag a wrong @form and to suggest the right @form for a \wrong.
CANONICAL_COMMAND = {"returns": "return", "throw": "throws", "sa": "see"}
WRONG_SPELLINGS = [
(re.compile(rf"@{wrong}\b"), f"@{right}")
for wrong, right in CANONICAL_COMMAND.items()
]
# Order block tags should appear in; a body out of this order is a violation.
EXPECTED_TAG_ORDER = ("tparam", "param", "return")
def is_doxy_open(stripped: str) -> bool:
"""True for a line-start Doxygen block opener we should normalize."""
if stripped.startswith("/*!"): # Qt-style Doxygen
return not stripped.startswith("/*!<") # member-after, leave inline
return (
stripped.startswith("/**")
and not stripped.startswith("/***")
and not stripped.startswith("/**/")
and not stripped.startswith("/**<")
)
def _flag_commands(raw_line: str, stripped: str, index: int) -> list[Finding]:
"""Flag \\cmd and misspelled @cmd on a comment line (opener, body, or closer)."""
if not stripped.startswith(("*", "//", "/*")):
return []
findings: list[Finding] = []
backslash = RE_BACKSLASH_CMD.search(raw_line)
if backslash:
command = backslash.group(1)
canonical = CANONICAL_COMMAND.get(command, command)
findings.append(
Finding(
index + 1,
Category.BACKSLASH_COMMAND,
f"use @{canonical} instead of \\{command}",
)
)
for pattern, replacement in WRONG_SPELLINGS:
wrong = pattern.search(raw_line)
if wrong:
findings.append(
Finding(
index + 1,
Category.WRONG_COMMAND,
f"use {replacement} instead of {wrong.group(0)}",
)
)
return findings
def _flag_line_comment(raw_line: str, stripped: str, index: int) -> Finding | None:
"""Return the finding for a single-line comment form (///, //!, /**<, ...), else None."""
if stripped.startswith("///") and not stripped.startswith(("////", "///<")):
return Finding(index + 1, Category.TRIPLE_SLASH)
if "//!<" in raw_line:
return Finding(index + 1, Category.QT_MEMBER)
if stripped.startswith("//!"):
return Finding(index + 1, Category.QT_LINE)
if "/**<" in raw_line:
return Finding(index + 1, Category.BLOCK_MEMBER)
if "/*!<" in raw_line:
return Finding(index + 1, Category.QT_BLOCK_MEMBER)
if stripped.startswith("//") and RE_LINE_DOC_TAG.search(stripped):
return Finding(index + 1, Category.DOC_IN_LINE_COMMENT)
return None
def _flag_single_line_block(stripped: str, line_no: int, is_qt: bool) -> list[Finding]:
"""Findings for a whole /** ... */ or /*! ... */ block on one line."""
inner = re.sub(r"^/\*[*!]", "", stripped)
inner = re.sub(r"\*/\s*$", "", inner).strip()
findings: list[Finding] = []
if is_qt:
findings.append(Finding(line_no, Category.QT_COMMENT))
if inner and not RE_MARKER.match(inner):
findings.append(Finding(line_no, Category.SINGLE_LINE_BLOCK))
return findings
def _canonical_order_tag(body: str) -> str | None:
"""The order-relevant tag (tparam/param/return) a body line opens with, if any."""
match = RE_ORDER_TAG.match(body)
if match is None:
return None
command = match.group(1)
return "return" if command in ("return", "returns", "retval") else command
def _flag_body_line(
body_line: str, line_no: int, is_first_content: bool
) -> list[Finding]:
"""Findings for one interior line of a multi-line block."""
body = body_line.strip()
findings: list[Finding] = []
if body and not body.startswith("*"):
findings.append(Finding(line_no, Category.BARE_CONTINUATION))
if body.startswith("*"):
if is_first_content and RE_FIRST_OVERINDENT.match(body_line):
findings.append(Finding(line_no, Category.OVER_INDENTED))
if RE_OVERINDENTED_TAG.match(body):
findings.append(Finding(line_no, Category.OVER_INDENTED_TAG))
if RE_COMBINED_MARKER.match(body):
findings.append(Finding(line_no, Category.COMBINED_MARKER))
label = RE_PROSE_LABEL.match(body)
if label:
suggested_tag = "@return" if label.group(1) == "Returns" else "@throws"
findings.append(
Finding(
line_no,
Category.PROSE_LABEL,
f'use {suggested_tag} instead of prose "{label.group(1)}:"',
)
)
return findings
def _flag_closer(closer_line: str, line_no: int) -> list[Finding]:
"""Findings for content sharing the closing */ line."""
before = closer_line[: closer_line.index("*/")].strip()
if before and before != "*":
return [Finding(line_no, Category.CONTENT_ON_CLOSER)]
return []
def _flag_tag_order(first_tag_line: dict[str, int]) -> list[Finding]:
"""One finding if the present block tags are not in EXPECTED_TAG_ORDER."""
tag_lines = [
first_tag_line[tag] for tag in EXPECTED_TAG_ORDER if tag in first_tag_line
]
if tag_lines != sorted(tag_lines):
return [Finding(min(tag_lines), Category.TAG_ORDER)]
return []
def _flag_doxy_block(lines: list[str], start: int) -> tuple[int, list[Finding]]:
"""Handle a /** or /*! block opening at ``start``; return (next index, findings)."""
raw_line = lines[start]
stripped = raw_line.lstrip()
open_pos = raw_line.index("/*")
is_qt = stripped.startswith("/*!")
# A whole block on one line: /** ... */.
if "*/" in raw_line[open_pos + 2 :]:
return start + 1, _flag_single_line_block(stripped, start + 1, is_qt)
# Multi-line block: opener, then scan the body to the closer.
findings: list[Finding] = []
if is_qt:
findings.append(Finding(start + 1, Category.QT_COMMENT))
if raw_line[open_pos + 3 :].strip():
findings.append(Finding(start + 1, Category.TEXT_ON_OPENER))
line_count = len(lines)
cursor = start + 1
is_first_content = True
first_tag_line: dict[str, int] = {} # canonical tag -> 1-based first line
while cursor < line_count and "*/" not in lines[cursor]:
body_line = lines[cursor]
body = body_line.strip()
findings.extend(_flag_commands(body_line, body, cursor))
tag = _canonical_order_tag(body)
if tag is not None:
first_tag_line.setdefault(tag, cursor + 1)
findings.extend(_flag_body_line(body_line, cursor + 1, is_first_content))
if body.startswith("*"):
is_first_content = False
cursor += 1
if cursor < line_count:
closer_line = lines[cursor]
findings.extend(_flag_commands(closer_line, closer_line.strip(), cursor))
findings.extend(_flag_closer(closer_line, cursor + 1))
findings.extend(_flag_tag_order(first_tag_line))
return cursor + 1, findings
def _flag_plain_block(lines: list[str], start: int) -> tuple[int, list[Finding]]:
"""Handle a line-start plain /* ... */ block; return (next index, findings).
Only flagged when it hides a documentation command (a missing second star).
"""
line_count = len(lines)
cursor = start
while cursor < line_count and "*/" not in lines[cursor]:
cursor += 1
findings: list[Finding] = []
# The opener (start) is command-checked by check_file; check the rest here.
for i in range(start + 1, min(cursor + 1, line_count)):
findings.extend(_flag_commands(lines[i], lines[i].strip(), i))
block_text = "\n".join(
lines[start : cursor + 1] if cursor < line_count else lines[start:]
)
if RE_ANY_DOC_TAG.search(block_text):
findings.append(Finding(start + 1, Category.PLAIN_BLOCK_DOC))
next_index = cursor + 1 if cursor < line_count else line_count
return next_index, findings
def check_source(text: str) -> list[Finding]:
"""Return all style violations found in the given source text."""
lines = text.split("\n")
findings: list[Finding] = []
line_count = len(lines)
index = 0
in_plain_block = False # inside a mid-line, non-Doxygen /* ... */
while index < line_count:
raw_line = lines[index]
stripped = raw_line.lstrip()
# Skip the interior of a plain block opened on an earlier line.
if in_plain_block:
in_plain_block = "*/" not in raw_line
index += 1
continue
findings.extend(_flag_commands(raw_line, stripped, index))
line_finding = _flag_line_comment(raw_line, stripped, index)
if line_finding is not None:
findings.append(line_finding)
index += 1
elif is_doxy_open(stripped):
index, block_findings = _flag_doxy_block(lines, index)
findings.extend(block_findings)
elif stripped.startswith("/*"):
index, block_findings = _flag_plain_block(lines, index)
findings.extend(block_findings)
else:
# A /* that opens mid-line without closing starts a plain block.
if "/*" in raw_line and not stripped.startswith("//"):
if "*/" not in raw_line[raw_line.index("/*") + 2 :]:
in_plain_block = True
index += 1
return findings
def check_file(path: Path) -> list[Finding]:
"""Return all style violations found in one file."""
return check_source(path.read_text(encoding="utf-8"))
def iter_files(paths: Iterable[str]) -> Iterator[Path]:
"""Yield every C++ source file among the given files and directories."""
for raw_path in paths:
path = Path(raw_path)
if path.is_dir():
for candidate in path.rglob("*"):
if candidate.is_file() and candidate.suffix in EXTS:
yield candidate
elif path.suffix in EXTS:
yield path
def main() -> int:
parser = argparse.ArgumentParser(description="Check Doxygen comment style.")
parser.add_argument(
"files", nargs="*", help="files or directories (default: src/ include/)"
)
parser.add_argument(
"-q", "--quiet", action="store_true", help="only print the summary count"
)
args = parser.parse_args()
roots = args.files or [root for root in DEFAULT_ROOTS if Path(root).is_dir()]
total = 0
for path in sorted(set(iter_files(roots)), key=str):
for finding in check_file(path):
total += 1
if not args.quiet:
print(
f"{path}:{finding.line}: {finding.category.label}: {finding.message}"
)
print(f"\n{total} doxygen-style violation(s)", file=sys.stderr)
return 1 if total else 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,25 +1,46 @@
#!/usr/bin/env python3
"""Pre-commit hook that runs clang-tidy on changed files using run-clang-tidy.
"""Pre-commit hook that runs clang-tidy on staged files using run-clang-tidy.
The set of files is chosen by pre-commit (see .pre-commit-config.yaml), which
filters to C/C++ sources and excludes `.ipp` fragments. Headers are linted
directly: the `verify_headers` build option (ON by default) compiles every
`.h`/`.hpp` on its own, so each header is the main file of its own
compile_commands.json entry and run-clang-tidy can analyse it just like a
`.cpp`.
The script determines the staged files itself (see `pass_filenames: false` in
.pre-commit-config.yaml) so run-clang-tidy is run once and handles parallelism
internally: pre-commit would otherwise split the files across parallel hook
invocations that race when fixes edit a shared header.
Fixes are collected with `-export-fixes` and applied by clang-apply-replacements
in a separate step rather than with run-clang-tidy's `-fix`. The `add_module`
build isolates each module's headers behind a per-module symlink directory
(build/modules/<module>/...), so a header reachable from several translation
units is referenced through different paths that all resolve to the same source
file. clang-apply-replacements deduplicates identical replacements by their
literal path, so those paths must be canonicalised to the real source path
first; otherwise the same fix is applied once per path and corrupts the header.
"""
from __future__ import annotations
import os
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
CLANG_TIDY_VERSION = 22
def find_run_clang_tidy() -> str | None:
for candidate in ("run-clang-tidy-21", "run-clang-tidy"):
# Extensions run-clang-tidy can analyse: `.cpp` translation units and, thanks to
# the `verify_headers` build option, `.h`/`.hpp` headers (each has its own
# compile_commands.json entry). `.ipp` fragments have no entry and are skipped.
TIDY_EXTENSIONS = {".cpp", ".h", ".hpp"}
# A single-quoted `FilePath:` entry in an -export-fixes YAML file, allowing the
# `- ` marker that precedes it inside a `Replacements:` sequence. clang-tidy
# emits paths single-quoted and doubles any embedded quote per YAML rules.
FILEPATH_RE = re.compile(r"^(\s*(?:-\s+)?FilePath:\s*)'((?:[^']|'')*)'\s*$")
def find_tool(name: str) -> str | None:
for candidate in (f"{name}-{CLANG_TIDY_VERSION}", name):
if path := shutil.which(candidate):
return path
return None
@@ -33,23 +54,43 @@ def find_build_dir(repo_root: Path) -> Path | None:
return None
def staged_files(repo_root: Path) -> list[Path]:
"""Return absolute paths of staged, lint-able C/C++ files.
`--diff-filter=d` excludes deletions so we never lint a removed file.
"""
output = subprocess.check_output(
["git", "diff", "--staged", "--name-only", "--diff-filter=d", "--"]
+ [f"*{ext}" for ext in TIDY_EXTENSIONS],
text=True,
cwd=repo_root,
)
return [repo_root / rel for rel in output.splitlines() if rel]
def canonicalize_fix_paths(fixes_dir: Path) -> None:
"""Rewrite every `FilePath` in the exported fixes to its real source path.
A header included through a module's isolation symlink is recorded under that
symlink's path; collapsing all paths to the same real file lets
clang-apply-replacements recognise the per-translation-unit duplicates and
apply each fix once.
"""
for yaml in fixes_dir.glob("*.yaml"):
lines = []
for line in yaml.read_text().splitlines():
if m := FILEPATH_RE.match(line):
path = m.group(2).replace("''", "'")
real = os.path.realpath(path).replace("'", "''")
line = f"{m.group(1)}'{real}'"
lines.append(line)
yaml.write_text("\n".join(lines) + "\n")
def main():
if not os.environ.get("TIDY"):
return 0
files = sys.argv[1:]
if not files:
return 0
run_clang_tidy = find_run_clang_tidy()
if not run_clang_tidy:
print(
"clang-tidy check failed: TIDY is enabled but neither "
"'run-clang-tidy-21' nor 'run-clang-tidy' was found in PATH.",
file=sys.stderr,
)
return 1
repo_root = Path(
subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
@@ -57,6 +98,29 @@ def main():
text=True,
).strip()
)
files = staged_files(repo_root)
if not files:
return 0
run_clang_tidy = find_tool("run-clang-tidy")
clang_apply_replacements = find_tool("clang-apply-replacements")
missing = [
name
for name, path in (
("run-clang-tidy", run_clang_tidy),
("clang-apply-replacements", clang_apply_replacements),
)
if not path
]
if missing:
print(
f"clang-tidy check failed: TIDY is enabled but {' and '.join(missing)} "
f"was not found in PATH (tried the '-{CLANG_TIDY_VERSION}' suffix too).",
file=sys.stderr,
)
return 1
build_dir = find_build_dir(repo_root)
if not build_dir:
print(
@@ -66,11 +130,23 @@ def main():
)
return 1
result = subprocess.run(
[run_clang_tidy, "-quiet", "-p", str(build_dir), "-fix", "-allow-no-checks"]
+ files
)
return result.returncode
with tempfile.TemporaryDirectory() as fixes_dir:
result = subprocess.run(
[
run_clang_tidy,
"-quiet",
"-p",
build_dir,
"-export-fixes",
fixes_dir,
"-allow-no-checks",
]
+ files
)
canonicalize_fix_paths(Path(fixes_dir))
applied = subprocess.run([clang_apply_replacements, fixes_dir])
return result.returncode or applied.returncode
if __name__ == "__main__":

View File

@@ -0,0 +1,406 @@
#!/usr/bin/env python3
"""
Tests for check_doxygen_style.py.
Run directly (no test framework needed):
./bin/pre-commit/test_check_doxygen_style.py
or under pytest:
pytest bin/pre-commit/test_check_doxygen_style.py
"""
import sys
import textwrap
from check_doxygen_style import Finding, check_source
def findings_for(text: str) -> list[Finding]:
"""Return the style violations for the given source text.
The text is dedented and its leading newline stripped, so fixtures can be
written as indented triple-quoted here-docs while keeping honest 1-based
line numbers.
"""
text = textwrap.dedent(text).lstrip("\n")
return check_source(text)
def labels_for(text: str) -> list[str]:
return [f.category.label for f in findings_for(text)]
def messages_for(text: str) -> list[str]:
return [f.message for f in findings_for(text)]
# --- well-formed input produces nothing -------------------------------------
def test_clean_block_ok() -> None:
code = """
/**
* Brief.
*
* @tparam T a type
* @param x the x
* @return the result
*/
"""
assert findings_for(code) == []
def test_blank_lines_inside_block_ok() -> None:
code = """
/**
* a
*
* b
*/
"""
assert findings_for(code) == []
def test_member_and_divider_allowed() -> None:
assert findings_for("int x; ///< ok member\n") == []
assert findings_for("//////////\n") == []
assert findings_for("//// text\n") == []
# --- line-comment forms ------------------------------------------------------
def test_triple_slash() -> None:
code = "/// doc\n"
assert labels_for(code) == ["triple-slash"]
def test_qt_line() -> None:
code = "//! doc\n"
assert labels_for(code) == ["qt-line"]
def test_qt_member() -> None:
code = "int x; //!< doc\n"
assert labels_for(code) == ["qt-member"]
def test_block_member() -> None:
code = "int x; /**< doc */\n"
assert labels_for(code) == ["block-member"]
def test_qt_block_member() -> None:
code = "int x; /*!< doc */\n"
assert labels_for(code) == ["qt-block-member"]
def test_doc_in_line_comment() -> None:
code = "// @param x\n"
assert labels_for(code) == ["doc-in-line-comment"]
# --- block forms -------------------------------------------------------------
def test_qt_comment() -> None:
code = """
/*!
* brief
*/
"""
assert labels_for(code) == ["qt-comment"]
def test_qt_comment_single_line() -> None:
# /*! ... */ on one line -> qt-comment (plus single-line-block)
code = "/*! brief */\n"
assert labels_for(code) == ["qt-comment", "single-line-block"]
def test_single_line_block() -> None:
code = "/** brief */\n"
assert labels_for(code) == ["single-line-block"]
def test_single_line_markers_allowed() -> None:
for marker in ("@{", "@}", "@cond LABEL", "@endcond", "@file foo.h"):
code = f"/** {marker} */\n"
assert findings_for(code) == [], marker
def test_text_on_opener() -> None:
code = """
/** text here
* more
*/
"""
assert labels_for(code) == ["text-on-opener"]
def test_bare_continuation() -> None:
code = """
/**
* a
bare line
*/
"""
assert labels_for(code) == ["bare-continuation"]
def test_over_indented_first_line() -> None:
code = """
/**
* over
*/
"""
assert labels_for(code) == ["over-indented"]
def test_over_indented_tag() -> None:
# a flush first line consumes "first content", isolating the tag check
code = """
/**
* brief
* @param x
*/
"""
assert labels_for(code) == ["over-indented-tag"]
def test_combined_marker() -> None:
code = """
/**
* @{
*/
"""
assert labels_for(code) == ["combined-marker"]
def test_prose_label() -> None:
for word in ("Returns", "Throws", "Exceptions"):
code = f"""
/**
* {word}:
*/
"""
assert labels_for(code) == ["prose-label"], word
def test_content_on_closer() -> None:
code = """
/**
* a
* b */
"""
assert labels_for(code) == ["content-on-closer"]
def test_plain_block_doc() -> None:
assert labels_for("/* @param x */\n") == ["plain-block-doc"]
assert findings_for("/* just an ordinary note */\n") == []
def test_tag_order() -> None:
out_of_order = """
/**
* @param x
* @tparam T
*/
"""
assert labels_for(out_of_order) == ["tag-order"]
correct = """
/**
* @tparam T
* @param x
* @return r
*/
"""
assert findings_for(correct) == []
single = """
/**
* @param x
*/
"""
assert findings_for(single) == [] # single tag: never out of order
# --- command spelling (must work on body/closer lines, not just the opener) --
def test_backslash_command_on_body_line() -> None:
code = r"""
/**
* \brief x
*/
"""
assert labels_for(code) == ["backslash-command"]
def test_backslash_command_suggests_canonical_spelling() -> None:
# a backslash + non-canonical spelling is fixed in one pass, not two:
# \sa -> @see (not @sa), \returns -> @return (not @returns)
sa = r"""
/**
* \sa other
*/
"""
assert messages_for(sa) == [r"use @see instead of \sa"]
returns = r"""
/**
* \returns x
*/
"""
assert messages_for(returns) == [r"use @return instead of \returns"]
def test_wrong_command_on_body_line() -> None:
code = """
/**
* @returns x
*/
"""
assert labels_for(code) == ["wrong-command"]
def test_body_line_commands_regression() -> None:
# regression: these live on body lines of a multi-line block
code = r"""
/**
* @returns bad
* @throw ex
* @sa other
* \param y
*/
"""
assert labels_for(code) == [
"wrong-command",
"wrong-command",
"wrong-command",
"backslash-command",
]
def test_command_on_closer_line() -> None:
code = """
/**
* a
* @sa b */
"""
assert labels_for(code) == ["wrong-command", "content-on-closer"]
def test_no_double_count_across_opener_body_closer() -> None:
code = """
/** @returns opener
* @throw body
* @sa closer */
"""
assert labels_for(code).count("wrong-command") == 3
def test_code_with_word_allowed() -> None:
# @code{.cpp} is valid Doxygen and must not be flagged
code = """
/**
* @code{.cpp}
* int x;
* @endcode
*/
"""
assert findings_for(code) == []
# --- rendered message text ---------------------------------------------------
def test_message_uses_category_description() -> None:
# a static category renders its default description
code = "/// doc\n"
assert messages_for(code) == ["use a /** ... */ block instead of ///"]
def test_message_detail_overrides() -> None:
# dynamic categories render the offending text via Finding.detail
backslash = r"""
/**
* \param y
*/
"""
assert messages_for(backslash) == [r"use @param instead of \param"]
wrong = """
/**
* @returns x
*/
"""
assert messages_for(wrong) == ["use @return instead of @returns"]
prose = """
/**
* Throws:
*/
"""
assert messages_for(prose) == ['use @throws instead of prose "Throws:"']
# --- robustness --------------------------------------------------------------
def test_empty_file_no_crash() -> None:
assert findings_for("") == []
def test_mid_line_plain_block_skipped() -> None:
# a /* opened mid-line (after code) and spanning lines is skipped, so its
# comment-like contents are not analyzed
code = """
int x = 0; /* note: @returns is not a real tag here
* @param also not real
*/
int y = 0;
"""
assert findings_for(code) == []
def test_unclosed_block_scanned_to_eof() -> None:
# an unterminated /** block is still scanned to EOF (no crash, body checked)
code = """
/**
* @returns x
"""
assert labels_for(code) == ["wrong-command"]
def test_banner_and_empty_comment_not_flagged() -> None:
code = """
/***
* banner
***/
"""
assert findings_for(code) == []
assert findings_for("/**/\n") == []
def main() -> int:
tests = sorted(
(name, fn)
for name, fn in globals().items()
if name.startswith("test_") and callable(fn)
)
failed = 0
for name, fn in tests:
try:
fn()
print(f"PASS {name}")
except AssertionError as exc:
failed += 1
print(f"FAIL {name}: {exc!r}")
print(f"\n{len(tests) - failed}/{len(tests)} passed")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -29,6 +29,27 @@ if(CMAKE_GENERATOR STREQUAL "Xcode")
set(is_xcode TRUE)
endif()
# --------------------------------------------------------------------
# Nix toolchain detection
# --------------------------------------------------------------------
# True when the C++ compiler resolves into the Nix store. CMAKE_CXX_COMPILER may
# be referenced through a symlink outside the store (a Nix profile, a /usr/bin
# alternative, ...), so resolve the real path before matching.
set(is_nix_compiler FALSE)
get_filename_component(_cxx_real "${CMAKE_CXX_COMPILER}" REALPATH)
if(_cxx_real MATCHES "^/nix/store/")
set(is_nix_compiler TRUE)
endif()
unset(_cxx_real)
# True inside the Nix CI Docker image, identified by the /nix/ci-env tree it
# ships (see nix/docker/Dockerfile). The dev shell and bare systems don't have
# it, so it distinguishes the CI image from other Nix-compiler environments.
set(is_ci_image FALSE)
if(EXISTS "/nix/ci-env/bin")
set(is_ci_image TRUE)
endif()
# --------------------------------------------------------------------
# Operating system detection
# --------------------------------------------------------------------

View File

@@ -1,26 +1,37 @@
#[===================================================================[
Patch executables to run in non-Nix environments.
The Nix-based CI image links binaries against an ELF interpreter (loader)
that lives in the Nix store, so the resulting binaries don't run elsewhere
(including once installed from the .deb package). `patch_nix_binary` adds a
POST_BUILD step that resets the interpreter to the system default loader and
drops the rpath.
The Nix toolchain links binaries against an ELF interpreter (loader)
that lives in the Nix store, so the resulting binaries don't run elsewhere.
`patch_nix_binary` adds a POST_BUILD step that resets the interpreter
to the system default loader and drops the rpath.
This is only active inside the Nix-based image, detected by the presence of
/tmp/loader-path.sh (shipped by that image, resolves the default loader). It
is skipped for sanitizer builds, whose runtime libraries are resolved through
the rpath. Everywhere else `patch_nix_binary` is a no-op.
This runs by default for Nix-toolchain builds (determined by whether the compiler resolves under /nix/store/).
Those builds are where binaries get a Nix-store loader.
It is opted out of by setting the XRPLD_NO_PATCH_NIX_BINARY environment variable
the plain Nix dev shells set it, since their binaries link a newer glibc
and must not be retargeted to the system loader.
Non-Nix builds (a system compiler, already using the system loader) and sanitizer builds
(runtime libraries resolved through the rpath) are skipped too.
Everywhere else `patch_nix_binary` is a no-op.
The default loader is resolved by bin/default-loader-path.sh.
#]===================================================================]
include_guard(GLOBAL)
include(CompilationEnv)
# Provided by the Nix-based CI image; prints the system default ELF loader path.
set(_loader_path_script "/tmp/loader-path.sh")
# Resolves the system default ELF loader path for the current architecture.
set(_loader_path_script "${CMAKE_SOURCE_DIR}/bin/default-loader-path.sh")
if(is_linux AND NOT SANITIZERS_ENABLED AND EXISTS "${_loader_path_script}")
if(
is_linux
AND NOT SANITIZERS_ENABLED
AND is_nix_compiler
AND NOT DEFINED ENV{XRPLD_NO_PATCH_NIX_BINARY}
)
execute_process(
COMMAND "${_loader_path_script}"
OUTPUT_VARIABLE DEFAULT_LOADER_PATH

View File

@@ -0,0 +1,36 @@
include(isolate_headers)
# Define a benchmark executable for the module `name`.
#
# This follows the same general pattern as other build helpers in this repo
# (e.g. `add_module`): create a target and isolate headers, but here the target
# is a benchmark executable and no `add_test(...)` is registered.
#
# `isolate_headers` exposes only `${CMAKE_CURRENT_SOURCE_DIR}/${name}` on the
# include path, rooted at `src`, so a benchmark's own headers are reached as
# `<benchmarks/.../${name}/...>` and nothing else in the tree leaks in.
function(xrpl_add_benchmark name)
set(target ${PROJECT_NAME}.bench.${name})
file(
GLOB_RECURSE sources
CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp"
)
add_executable(${target} ${ARGN} ${sources})
# Benchmark sources register cases through Google Benchmark's static
# registrars (anonymous-namespace lambdas). Merging several such files into
# one unity translation unit collides those internal-linkage entities, so
# keep benchmarks out of the unity build - mirroring xrpl.libpb in
# XrplCore.cmake. Each file compiles fine on its own.
set_target_properties(${target} PROPERTIES UNITY_BUILD OFF)
isolate_headers(
${target}
"${CMAKE_SOURCE_DIR}/src"
"${CMAKE_CURRENT_SOURCE_DIR}/${name}"
PRIVATE
)
endfunction()

View File

@@ -171,9 +171,8 @@ else()
# Clang wrapper supplies those paths itself (via -nostdinc++), so at compile time the
# flag is unused -> Clang errors under our -Werror. At link time the flag IS consumed
# (it selects the C++ runtime), so we move it there instead of dropping it entirely.
get_filename_component(_cxx_real "${CMAKE_CXX_COMPILER}" REALPATH)
if(
_cxx_real MATCHES "^/nix/store/"
is_nix_compiler
AND is_linux
AND is_clang
AND CMAKE_CXX_FLAGS MATCHES "stdlib=libstdc"

View File

@@ -133,6 +133,12 @@ target_link_libraries(
add_module(xrpl resource)
target_link_libraries(xrpl.libxrpl.resource PUBLIC xrpl.libxrpl.protocol)
add_module(xrpl peerfinder)
target_link_libraries(
xrpl.libxrpl.peerfinder
PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.protocol
)
# Level 08
add_module(xrpl net)
target_link_libraries(
@@ -201,6 +207,16 @@ target_link_libraries(
add_module(xrpl tx)
target_link_libraries(xrpl.libxrpl.tx PUBLIC xrpl.libxrpl.ledger)
add_module(xrpl consensus)
target_link_libraries(
xrpl.libxrpl.consensus
PUBLIC
xrpl.libxrpl.basics
xrpl.libxrpl.json
xrpl.libxrpl.protocol
xrpl.libxrpl.ledger
)
add_library(xrpl.libxrpl)
set_target_properties(xrpl.libxrpl PROPERTIES OUTPUT_NAME xrpl)
@@ -220,6 +236,7 @@ target_link_modules(
beast
conditions
config
consensus
core
crypto
git
@@ -227,6 +244,7 @@ target_link_modules(
ledger
net
nodestore
peerfinder
protocol
protocol_autogen
rdb

View File

@@ -36,6 +36,19 @@ elseif(is_gcc)
endif()
endif()
# A Nix compiler is only meant to be used from a managed environment: the xrpld
# dev shell (which exports XRPL_DEVSHELL) or the CI image. Using one from a bare
# shell usually means a leaked toolchain (picked up via PATH or a Conan profile)
# and leads to confusing breakage, so fail early with guidance.
if(is_nix_compiler AND NOT is_ci_image AND NOT DEFINED ENV{XRPL_DEVSHELL})
message(
FATAL_ERROR
"A Nix compiler (${CMAKE_CXX_COMPILER}) is being used outside the xrpld "
"dev shell. Enter it with `nix develop` (see docs/build/nix.md) before "
"configuring the build."
)
endif()
# check for in-source build and fail
if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
message(

View File

@@ -30,6 +30,8 @@ if(tests)
endif()
endif()
option(benchmark "Build benchmarks" ON)
# Enabled by default so every header is compiled on its own as the main file of
# its own compile_commands.json entry - this is what lets clang-tidy (and clangd
# and IDEs) analyse a header's own includes directly. The per-header objects are

View File

@@ -39,21 +39,12 @@ if(Boost_COMPILER)
target_link_libraries(xrpl_boost INTERFACE Boost::disable_autolinking)
endif()
# GCC 14+ has a false positive -Wuninitialized warning in Boost.Coroutine2's
# state.hpp when compiled with -O3. This is due to GCC's intentional behavior
# change (Bug #98871, #119388) where warnings from inlined system header code
# are no longer suppressed by -isystem. The warning occurs in operator|= in
# boost/coroutine2/detail/state.hpp when inlined from push_control_block::destroy().
# See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119388
if(is_gcc AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14)
target_compile_options(xrpl_boost INTERFACE -Wno-uninitialized)
endif()
# Boost.Context's ucontext backend has ASAN fiber-switching annotations
# (start/finish_switch_fiber) that are compiled in when BOOST_USE_ASAN is defined.
# This tells ASAN about coroutine stack switches, preventing false positive
# stack-use-after-scope errors. BOOST_USE_UCONTEXT ensures the ucontext backend
# is selected (fcontext does not support ASAN annotations).
# This tells ASAN about fiber stack switches used by boost::asio::spawn,
# preventing false positive stack-use-after-scope errors.
# BOOST_USE_UCONTEXT ensures the ucontext backend is selected (fcontext does
# not support ASAN annotations).
# These defines must match what Boost was compiled with (see conan/profiles/sanitizers).
if(enable_asan)
target_compile_definitions(

View File

@@ -177,7 +177,9 @@ ${field['typeData']['setter_type']} ${field['paramName']}${',' if i < len(requir
object_ = *sle;
}
/** @brief Ledger entry-specific field setters */
/**
* @brief Ledger entry-specific field setters
*/
% for field in fields:
/**

View File

@@ -185,7 +185,9 @@ public:
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Transaction-specific field setters
*/
% for field in fields:
/**

View File

@@ -10,22 +10,23 @@
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1782392413.075713",
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1782392402.431897",
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
"openssl/3.6.3#1163d4ddc603907084d08a6a0c6e580f%1782307150.583886",
"openssl/3.6.3#f806de8933e3bf6f01016c6a888cee2e%1783945160.863288",
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1782392402.297166",
"mpt-crypto/0.4.0-rc2#a580f2f9ad0e795de696aa62d54fb9af%1782425834.488828",
"mpt-crypto/0.4.0-rc4#ffdba12f2332357f0d8b0ae944cfff52%1784138702.932355",
"lz4/1.10.0#982d9b673900f665a1da109e09c17cab%1782392402.164188",
"libiconv/1.17#9923bc6dc6f106646d6967e0039a5ada%1782392792.775744",
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1782392402.420732",
"libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1782392403.066892",
"jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228",
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1782392402.791979",
"grpc/1.81.1#5217e6ef0544c42b46f4af35d5e7f649%1782307148.845616",
"grpc/1.81.1#f729f6d75992d20f9c72828e9142d62f%1783945160.094135",
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1782392402.296732",
"boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1782392419.475605",
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1782307147.395833"
"benchmark/1.9.5#b885dc73ad67b40a55d45684d1c88ad1%1782736613.864841",
"abseil/20250127.0#9ef01c1451a8340f9022e46238c0fbb6%1783945159.651047"
],
"build_requires": [
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
@@ -38,7 +39,7 @@
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1782392402.624226",
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1782307147.395833"
"abseil/20250127.0#9ef01c1451a8340f9022e46238c0fbb6%1783945159.651047"
],
"python_requires": [],
"overrides": {

View File

@@ -20,6 +20,22 @@ compiler.libcxx={{ detect_api.detect_libcxx(compiler, version, compiler_exe) }}
{% endif %}
[conf]
{# The Boost recipe builds with b2, which doesn't use Conan's toolchain files. #}
{# Instead it hand-rolls the compiler for user-config.jam, #}
{# and its fallback probes a version-suffixed binary (e.g. `g++-15`) before plain `g++`. #}
{# Inside the Nix shell the wrapper only provides `g++`/`gcc` (no `-15` suffix), #}
{# so on a host that also has a system `g++-15` the probe escapes Nix #}
{# and picks the system compiler, which is mismatched with the Nix libraries #}
{# and breaks the build (e.g. Boost.Stacktrace link checks fail). #}
{# Pinning the executables here short-circuits that probe so Boost (and the rest of the toolchain) #}
{# resolve the same compiler. #}
{# Not part of the package ID, so binaries stay shareable. #}
{% if os != "Windows" %}
{% set cc_exe = {"gcc": "gcc", "clang": "clang", "apple-clang": "clang"}.get(compiler) %}
{% set cxx_exe = {"gcc": "g++", "clang": "clang++", "apple-clang": "clang++"}.get(compiler) %}
tools.build:compiler_executables={'c':'{{ cc_exe }}','cpp':'{{ cxx_exe }}'}
{% endif %}
{# By default, Conan tries to reuse binaries built with different cppstd versions. #}
{# We want to avoid that to improve reproduceability, so we add the cppstd version to the package ID. #}
{# More info: https://docs.conan.io/2/reference/extensions/binary_compatibility.html #}

View File

@@ -15,6 +15,7 @@ class Xrpl(ConanFile):
settings = "os", "compiler", "build_type", "arch"
options = {
"assertions": [True, False],
"benchmark": [True, False],
"coverage": [True, False],
"fPIC": [True, False],
"jemalloc": [True, False],
@@ -46,6 +47,7 @@ class Xrpl(ConanFile):
default_options = {
"assertions": False,
"benchmark": True,
"coverage": False,
"fPIC": True,
"jemalloc": False,
@@ -58,7 +60,7 @@ class Xrpl(ConanFile):
"boost/*:without_cobalt": True,
"boost/*:without_context": False,
"boost/*:without_coroutine": True,
"boost/*:without_coroutine2": False,
"boost/*:without_coroutine2": True,
"date/*:header_only": True,
"ed25519/*:shared": False,
"grpc/*:shared": False,
@@ -129,12 +131,14 @@ class Xrpl(ConanFile):
self.options["boost"].without_cobalt = True
def requirements(self):
if self.options.benchmark:
self.requires("benchmark/1.9.5")
self.requires("boost/1.91.0", force=True, transitive_headers=True)
self.requires("date/3.0.4", transitive_headers=True)
if self.options.jemalloc:
self.requires("jemalloc/5.3.1")
self.requires("lz4/1.10.0", force=True)
self.requires("mpt-crypto/0.4.0-rc2", transitive_headers=True)
self.requires("mpt-crypto/0.4.0-rc4", transitive_headers=True)
self.requires("protobuf/6.33.5", force=True)
if self.options.rocksdb:
self.requires("rocksdb/10.5.1")
@@ -162,6 +166,7 @@ class Xrpl(ConanFile):
def generate(self):
tc = CMakeToolchain(self)
tc.variables["tests"] = self.options.tests
tc.variables["benchmark"] = self.options.benchmark
tc.variables["assert"] = self.options.assertions
tc.variables["coverage"] = self.options.coverage
tc.variables["jemalloc"] = self.options.jemalloc

View File

@@ -33,9 +33,10 @@ with a single command and without installing anything system-wide:
nix --experimental-features 'nix-command flakes' develop
```
On **Linux**, Nix also provides the compiler (GCC). On **macOS**, the shell uses
your **system-wide Apple Clang** as the compiler, so you still need to manage
its version (see below).
On **Linux**, Nix also provides the compiler (GCC); on **macOS**, it provides
Clang. If you instead opt to use your system-wide Apple Clang (via
`nix develop .#apple-clang`), you need to manage its version yourself (see
below).
See [Using the Nix development shell](./nix.md) for installation and usage
details, including how to select a different compiler.
@@ -48,10 +49,10 @@ details, including how to select a different compiler.
### macOS: managing the Apple Clang version
Because the Nix shell uses the system-wide Apple Clang on macOS, the compiler
version is whatever your installed Xcode (or Command Line Tools) provides. The
following command should return a version greater than or equal to the
[minimum required](#tested-compiler-versions):
If you use your system-wide Apple Clang on macOS (via `nix develop .#apple-clang`),
the compiler version is whatever your installed Xcode (or Command Line Tools)
provides. The following command should return a version greater than or equal to
the [minimum required](#tested-compiler-versions):
```bash
clang --version

70
docs/build/nix.md vendored
View File

@@ -9,7 +9,7 @@ This guide explains how to use Nix to set up a reproducible development environm
- **Reproducible environment**: Everyone gets the same versions of tools and compilers
- **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment
- **No system pollution**: Dependencies are isolated and don't affect your system packages
- **Multiple compiler versions**: Easily switch between different GCC and Clang versions
- **Consistent compilers**: The GCC and Clang shells use the same versions as CI
- **Quick setup**: Get started with a single command
- **Works on Linux and macOS**: Consistent experience across platforms
@@ -31,21 +31,23 @@ This will:
- Download and set up all required development tools (CMake, Ninja, Conan, etc.)
- Configure the appropriate compiler for your platform:
- **Linux**: GCC 15.2 (provided by Nix)
- **macOS**: Apple Clang (your system compiler)
- **Linux**: GCC (provided by Nix)
- **macOS**: Clang (provided by Nix)
The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster.
### Platform notes
- **Linux**: `nix develop` gives you a shell with all the tooling necessary to
develop xrpld and with GCC 15.2 (also provided by Nix). There are no caveats.
- **macOS**: `nix develop` gives you a full environment too. The compiler is
your system-wide Apple Clang, while every other tool — including Conan — is
provided by Nix. Conan has no binary in the Nix cache for macOS, so it is
built from source the first time you enter the shell, which makes the initial
setup slower (this is handled automatically; see
[`nix/devshell.nix`](../../nix/devshell.nix)).
- **Linux**: `nix develop` gives you a shell with all the tooling necessary to develop xrpld
and with the same GCC/glibc toolchain that Nix builds for CI.
See [Choosing a different compiler](#choosing-a-different-compiler)
for the custom-vs-plain toolchain trade-off.
- **macOS**: `nix develop` gives you a full environment too, with Clang (and
every other tool, including Conan) provided by Nix. To use your system-wide
Apple Clang instead, enter `nix develop .#apple-clang`. Conan has no binary in
the Nix cache for macOS, so it is built from source the first time you enter
the shell, which makes the initial setup slower (this is handled
automatically; see [`nix/devshell.nix`](../../nix/devshell.nix)).
> [!TIP]
> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`:
@@ -62,7 +64,17 @@ The first time you run this command, it will take a few minutes to download and
### Choosing a different compiler
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`.
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#clang`.
On Linux, `.#gcc` and `.#clang` provide the exact toolchain CI uses:
the compiler (pinned in [`nix/packages.nix`](../../nix/packages.nix))
rebuilt against the pinned custom glibc (see [`nix/compilers.nix`](../../nix/compilers.nix)).
Building that toolchain the first time is slow unless it is fetched from a Nix binary cache.
If you don't need the custom glibc, the Linux-only `.#gcc-plain` and `.#clang-plain`
give you the stock nixpkgs compilers of the same versions.
On macOS there is no custom glibc, so `.#gcc` and `.#clang` are already the plain nixpkgs toolchain,
and the `-plain` variants do not exist.
Use `nix flake show` to see all the available development shells.
Use `nix develop .#no-compiler` to use the compiler from your system.
@@ -70,14 +82,18 @@ Use `nix develop .#no-compiler` to use the compiler from your system.
### Example Usage
```bash
# Use GCC 14
nix develop .#gcc14
# Use GCC — same toolchain as CI (custom glibc on Linux)
nix develop .#gcc
# Use Clang 19
nix develop .#clang19
# Use Clang — same toolchain as CI (custom glibc on Linux)
nix develop .#clang
# Use default for your platform
nix develop
# Stock nixpkgs GCC/Clang, Linux only — skips the custom-glibc build, but does not match CI
nix develop .#gcc-plain
nix develop .#clang-plain
```
### Using a different shell
@@ -108,11 +124,23 @@ nix develop -c "$SHELL"
Once inside the Nix development shell, follow the standard [build instructions](../../BUILD.md#steps). The Nix shell provides all necessary tools (CMake, Ninja, Conan, etc.).
Coverage builds (`-Dcoverage=ON`) work in the `gcc` shell (and `gcc-plain` on Linux):
each ships a `gcov` matching its compiler, since Nix's cc-wrapper does not expose one.
The `clang` shells do not include `llvm-cov`, so use a `gcc` shell for coverage.
## Automatic Activation with direnv
[direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory.
This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. To use it, install direnv for your shell, then add an `.envrc` containing `use flake` at the repository root and run `direnv allow`.
This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`.
The repository already ships an `.envrc` at its root that activates the Nix flake development shell, so you don't need to create one. To use it:
1. [Install direnv](https://direnv.net/docs/installation.html) and [hook it into your shell](https://direnv.net/docs/hook.html) (bash, zsh, fish, …). Installing [nix-direnv](https://github.com/nix-community/nix-direnv) as well is recommended: it caches the shell so that activation is near-instant after the first run.
2. Run `direnv allow` once in the repository root. direnv will then load (and reload) the Nix development shell automatically whenever you enter the directory.
> [!NOTE]
> direnv only caches the `.direnv` directory (already listed in `.gitignore`); no other repository files are affected.
## Conan and Prebuilt Packages
@@ -126,6 +154,14 @@ conan install .. --output-folder . --build '*' --settings build_type=Release
To update `flake.lock` to the latest revision use `nix flake update` command.
## Tooling snapshots
The tool versions in each Nix environment are recorded in
[`nix/check-tools/`](../../nix/check-tools) and verified by CI. If you change the
environment (bump the CI image tag, update `flake.lock`, or edit the tool list in
`bin/check-tools.sh`), CI fails until you regenerate and commit the affected
snapshot — see [`nix/check-tools/README.md`](../../nix/check-tools/README.md).
## Troubleshooting
See [Troubleshooting Nix problems](./nix_troubleshooting.md) for common issues,

23
flake.lock generated
View File

@@ -36,7 +36,28 @@
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"nixpkgs-custom-glibc": "nixpkgs-custom-glibc"
"nixpkgs-custom-glibc": "nixpkgs-custom-glibc",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1784611586,
"narHash": "sha256-OfqgY+0hp/zseZB7uyH0U8kIDPS4scZZCyAurEplvG0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "14f58845249f3552a89b07772626b8d3c632fa86",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},

View File

@@ -10,12 +10,25 @@
url = "github:NixOS/nixpkgs/9cd98386a38891d1074fc18036b842dc4416f562";
flake = false;
};
# Pinned Rust toolchains, delivered from the Nix store. Lets the Nix CI
# image and dev shell honour the single `rust-toolchain.toml` pin (shared
# with the rustup-based non-Nix runners) while staying hermetic — the
# toolchain lands in the image's Nix closure and is locked by flake.lock.
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{ nixpkgs, nixpkgs-custom-glibc, ... }:
{
nixpkgs,
nixpkgs-custom-glibc,
rust-overlay,
...
}:
let
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-custom-glibc; };
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-custom-glibc rust-overlay; };
in
{
devShells = forEachSystem (import ./nix/devshell.nix);

View File

@@ -4,13 +4,14 @@
namespace xrpl {
/** Extract a tar archive compressed with lz4
@param src the path of the archive to be extracted
@param dst the directory to extract to
@throws runtime_error
*/
/**
* Extract a tar archive compressed with lz4
*
* @param src the path of the archive to be extracted
* @param dst the directory to extract to
*
* @throws runtime_error
*/
void
extractTarLz4(boost::filesystem::path const& src, boost::filesystem::path const& dst);

View File

@@ -4,9 +4,10 @@
namespace xrpl {
/** Storage for linear binary data.
Blocks of binary data appear often in various idioms and structures.
*/
/**
* Storage for linear binary data.
* Blocks of binary data appear often in various idioms and structures.
*/
using Blob = std::vector<unsigned char>;
} // namespace xrpl

View File

@@ -10,9 +10,10 @@
namespace xrpl {
/** Like std::vector<char> but better.
Meets the requirements of BufferFactory.
*/
/**
* Like std::vector<char> but better.
* Meets the requirements of BufferFactory.
*/
class Buffer
{
private:
@@ -24,30 +25,37 @@ public:
Buffer() = default;
/** Create an uninitialized buffer with the given size. */
/**
* Create an uninitialized buffer with the given size.
*/
explicit Buffer(std::size_t size)
: p_((size != 0u) ? new std::uint8_t[size] : nullptr), size_(size)
{
}
/** Create a buffer as a copy of existing memory.
@param data a pointer to the existing memory. If
size is non-zero, it must not be null.
@param size size of the existing memory block.
*/
/**
* Create a buffer as a copy of existing memory.
*
* @param data a pointer to the existing memory. If
* size is non-zero, it must not be null.
* @param size size of the existing memory block.
*/
Buffer(void const* data, std::size_t size) : Buffer(size)
{
if (size != 0u)
std::memcpy(p_.get(), data, size);
}
/** Copy-construct */
/**
* Copy-construct
*/
Buffer(Buffer const& other) : Buffer(other.p_.get(), other.size_)
{
}
/** Copy assign */
/**
* Copy assign
*/
Buffer&
operator=(Buffer const& other)
{
@@ -59,17 +67,19 @@ public:
return *this;
}
/** Move-construct.
The other buffer is reset.
*/
/**
* Move-construct.
* The other buffer is reset.
*/
Buffer(Buffer&& other) noexcept : p_(std::move(other.p_)), size_(other.size_)
{
other.size_ = 0;
}
/** Move-assign.
The other buffer is reset.
*/
/**
* Move-assign.
* The other buffer is reset.
*/
Buffer&
operator=(Buffer&& other) noexcept
{
@@ -82,12 +92,16 @@ public:
return *this;
}
/** Construct from a slice */
/**
* Construct from a slice
*/
explicit Buffer(Slice s) : Buffer(s.data(), s.size())
{
}
/** Assign from slice */
/**
* Assign from slice
*/
Buffer&
operator=(Slice s)
{
@@ -101,7 +115,9 @@ public:
return *this;
}
/** Returns the number of bytes in the buffer. */
/**
* Returns the number of bytes in the buffer.
*/
[[nodiscard]] std::size_t
size() const noexcept
{
@@ -121,10 +137,11 @@ public:
return Slice{p_.get(), size_};
}
/** Return a pointer to beginning of the storage.
@note The return type is guaranteed to be a pointer
to a single byte, to facilitate pointer arithmetic.
*/
/**
* Return a pointer to beginning of the storage.
* @note The return type is guaranteed to be a pointer
* to a single byte, to facilitate pointer arithmetic.
*/
/** @{ */
[[nodiscard]] std::uint8_t const*
data() const noexcept
@@ -139,9 +156,10 @@ public:
}
/** @} */
/** Reset the buffer.
All memory is deallocated. The resulting size is 0.
*/
/**
* Reset the buffer.
* All memory is deallocated. The resulting size is 0.
*/
void
clear() noexcept
{
@@ -149,9 +167,10 @@ public:
size_ = 0;
}
/** Reallocate the storage.
Existing data, if any, is discarded.
*/
/**
* Reallocate the storage.
* Existing data, if any, is discarded.
*/
std::uint8_t*
alloc(std::size_t n)
{

View File

@@ -12,7 +12,8 @@
namespace xrpl::compression_algorithms {
/** LZ4 block compression.
/**
* LZ4 block compression.
* @tparam BufferFactory Callable object or lambda.
* Takes the requested buffer size and returns allocated buffer pointer.
* @param in Data to compress
@@ -80,7 +81,8 @@ lz4Decompress(
return decompressedSize;
}
/** LZ4 block decompression.
/**
* LZ4 block decompression.
* @tparam InputStream ZeroCopyInputStream
* @param in Input source stream
* @param inSize Size of compressed data

View File

@@ -9,7 +9,9 @@
namespace xrpl {
/** Manages all counted object types. */
/**
* Manages all counted object types.
*/
class CountedObjects
{
public:
@@ -23,10 +25,11 @@ public:
getCounts(int minimumThreshold) const;
public:
/** Implementation for @ref CountedObject.
@internal
*/
/**
* Implementation for @ref CountedObject.
*
* @internal
*/
class Counter
{
public:
@@ -94,13 +97,14 @@ private:
//------------------------------------------------------------------------------
/** Tracks the number of instances of an object.
Derived classes have their instances counted automatically. This is used
for reporting purposes.
@ingroup basics
*/
/**
* Tracks the number of instances of an object.
*
* Derived classes have their instances counted automatically. This is used
* for reporting purposes.
*
* @ingroup basics
*/
template <class Object>
class CountedObject
{

View File

@@ -6,9 +6,10 @@
namespace xrpl {
/** Sampling function using exponential decay to provide a continuous value.
@tparam The number of seconds in the decay window.
*/
/**
* Sampling function using exponential decay to provide a continuous value.
* @tparam The number of seconds in the decay window.
*/
template <int Window, typename Clock>
class DecayingSample
{
@@ -19,15 +20,16 @@ public:
DecayingSample() = delete;
/**
@param now Start time of DecayingSample.
*/
* @param now Start time of DecayingSample.
*/
explicit DecayingSample(time_point now) : value_(value_type()), when_(now)
{
}
/** Add a new sample.
The value is first aged according to the specified time.
*/
/**
* Add a new sample.
* The value is first aged according to the specified time.
*/
value_type
add(value_type value, time_point now)
{
@@ -36,9 +38,10 @@ public:
return value_ / Window;
}
/** Retrieve the current value in normalized units.
The samples are first aged according to the specified time.
*/
/**
* Retrieve the current value in normalized units.
* The samples are first aged according to the specified time.
*/
value_type
value(time_point now)
{
@@ -87,9 +90,10 @@ private:
//------------------------------------------------------------------------------
/** Sampling function using exponential decay to provide a continuous value.
@tparam HalfLife The half life of a sample, in seconds.
*/
/**
* Sampling function using exponential decay to provide a continuous value.
* @tparam HalfLife The half life of a sample, in seconds.
*/
template <int HalfLife, class Clock>
class DecayWindow
{

View File

@@ -10,33 +10,37 @@ namespace xrpl {
//------------------------------------------------------------------------------
/** Tag to create an intrusive pointer from another intrusive pointer by using a
static cast. This is useful to create an intrusive pointer to a derived
class from an intrusive pointer to a base class.
*/
/**
* Tag to create an intrusive pointer from another intrusive pointer by using a
* static cast. This is useful to create an intrusive pointer to a derived
* class from an intrusive pointer to a base class.
*/
struct StaticCastTagSharedIntrusive
{
};
/** Tag to create an intrusive pointer from another intrusive pointer by using a
dynamic cast. This is useful to create an intrusive pointer to a derived
class from an intrusive pointer to a base class. If the cast fails an empty
(null) intrusive pointer is created.
*/
/**
* Tag to create an intrusive pointer from another intrusive pointer by using a
* dynamic cast. This is useful to create an intrusive pointer to a derived
* class from an intrusive pointer to a base class. If the cast fails an empty
* (null) intrusive pointer is created.
*/
struct DynamicCastTagSharedIntrusive
{
};
/** When creating or adopting a raw pointer, controls whether the strong count
is incremented or not. Use this tag to increment the strong count.
*/
/**
* When creating or adopting a raw pointer, controls whether the strong count
* is incremented or not. Use this tag to increment the strong count.
*/
struct SharedIntrusiveAdoptIncrementStrongTag
{
};
/** When creating or adopting a raw pointer, controls whether the strong count
is incremented or not. Use this tag to leave the strong count unchanged.
*/
/**
* When creating or adopting a raw pointer, controls whether the strong count
* is incremented or not. Use this tag to leave the strong count unchanged.
*/
struct SharedIntrusiveAdoptNoIncrementTag
{
};
@@ -50,20 +54,21 @@ concept CAdoptTag = std::is_same_v<T, SharedIntrusiveAdoptIncrementStrongTag> ||
//------------------------------------------------------------------------------
/** A shared intrusive pointer class that supports weak pointers.
This is meant to be used for SHAMapInnerNodes, but may be useful for other
cases. Since the reference counts are stored on the pointee, the pointee is
not destroyed until both the strong _and_ weak pointer counts go to zero.
When the strong pointer count goes to zero, the "partialDestructor" is
called. This can be used to destroy as much of the object as possible while
still retaining the reference counts. For example, for SHAMapInnerNodes the
children may be reset in that function. Note that std::shared_pointer WILL
run the destructor when the strong count reaches zero, but may not free the
memory used by the object until the weak count reaches zero. In xrpld, we
typically allocate shared pointers with the `make_shared` function. When
that is used, the memory is not reclaimed until the weak count reaches zero.
*/
/**
* A shared intrusive pointer class that supports weak pointers.
*
* This is meant to be used for SHAMapInnerNodes, but may be useful for other
* cases. Since the reference counts are stored on the pointee, the pointee is
* not destroyed until both the strong _and_ weak pointer counts go to zero.
* When the strong pointer count goes to zero, the "partialDestructor" is
* called. This can be used to destroy as much of the object as possible while
* still retaining the reference counts. For example, for SHAMapInnerNodes the
* children may be reset in that function. Note that std::shared_pointer WILL
* run the destructor when the strong count reaches zero, but may not free the
* memory used by the object until the weak count reaches zero. In xrpld, we
* typically allocate shared pointers with the `make_shared` function. When
* that is used, the memory is not reclaimed until the weak count reaches zero.
*/
template <class T>
class SharedIntrusive
{
@@ -111,8 +116,9 @@ public:
operator=(
SharedIntrusive<TT>&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved)
/** Adopt the raw pointer. The strong reference may or may not be
incremented, depending on the TAdoptTag
/**
* Adopt the raw pointer. The strong reference may or may not be
* incremented, depending on the TAdoptTag
*/
template <CAdoptTag TAdoptTag = SharedIntrusiveAdoptIncrementStrongTag>
void
@@ -120,27 +126,31 @@ public:
~SharedIntrusive();
/** Create a new SharedIntrusive by statically casting the pointer
controlled by the rhs param.
*/
/**
* Create a new SharedIntrusive by statically casting the pointer
* controlled by the rhs param.
*/
template <class TT>
SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs);
/** Create a new SharedIntrusive by statically casting the pointer
controlled by the rhs param.
*/
/**
* Create a new SharedIntrusive by statically casting the pointer
* controlled by the rhs param.
*/
template <class TT>
SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs);
/** Create a new SharedIntrusive by dynamically casting the pointer
controlled by the rhs param.
*/
/**
* Create a new SharedIntrusive by dynamically casting the pointer
* controlled by the rhs param.
*/
template <class TT>
SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs);
/** Create a new SharedIntrusive by dynamically casting the pointer
controlled by the rhs param.
*/
/**
* Create a new SharedIntrusive by dynamically casting the pointer
* controlled by the rhs param.
*/
template <class TT>
SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs);
@@ -153,17 +163,22 @@ public:
explicit
operator bool() const noexcept;
/** Set the pointer to null, decrement the strong count, and run the
appropriate release action.
*/
/**
* Set the pointer to null, decrement the strong count, and run the
* appropriate release action.
*/
void
reset();
/** Get the raw pointer */
/**
* Get the raw pointer
*/
[[nodiscard]] T*
get() const;
/** Return the strong count */
/**
* Return the strong count
*/
[[nodiscard]] std::size_t
useCount() const;
@@ -181,43 +196,51 @@ public:
friend class WeakIntrusive;
private:
/** Return the raw pointer held by this object. */
/**
* Return the raw pointer held by this object.
*/
[[nodiscard]] T*
unsafeGetRawPtr() const;
/** Exchange the current raw pointer held by this object with the given
pointer. Decrement the strong count of the raw pointer previously held
by this object and run the appropriate release action.
/**
* Exchange the current raw pointer held by this object with the given
* pointer. Decrement the strong count of the raw pointer previously held
* by this object and run the appropriate release action.
*/
void
unsafeReleaseAndStore(T* next);
/** Set the raw pointer directly. This is wrapped in a function so the class
can support both atomic and non-atomic pointers in a future patch.
/**
* Set the raw pointer directly. This is wrapped in a function so the class
* can support both atomic and non-atomic pointers in a future patch.
*/
void
unsafeSetRawPtr(T* p);
/** Exchange the raw pointer directly.
This sets the raw pointer to the given value and returns the previous
value. This is wrapped in a function so the class can support both
atomic and non-atomic pointers in a future patch.
/**
* Exchange the raw pointer directly.
* This sets the raw pointer to the given value and returns the previous
* value. This is wrapped in a function so the class can support both
* atomic and non-atomic pointers in a future patch.
*/
T*
unsafeExchange(T* p);
/** pointer to the type with an intrusive count */
/**
* pointer to the type with an intrusive count
*/
T* ptr_{nullptr};
};
//------------------------------------------------------------------------------
/** A weak intrusive pointer class for the SharedIntrusive pointer class.
Note that this weak pointer class asks differently from normal weak pointer
classes. When the strong pointer count goes to zero, the "partialDestructor"
is called. See the comment on SharedIntrusive for a fuller explanation.
*/
/**
* A weak intrusive pointer class for the SharedIntrusive pointer class.
*
* Note that this weak pointer class asks differently from normal weak pointer
* classes. When the strong pointer count goes to zero, the "partialDestructor"
* is called. See the comment on SharedIntrusive for a fuller explanation.
*/
template <class T>
class WeakIntrusive
{
@@ -247,54 +270,62 @@ public:
WeakIntrusive&
operator=(SharedIntrusive<TT> const& rhs);
/** Adopt the raw pointer and increment the weak count. */
/**
* Adopt the raw pointer and increment the weak count.
*/
void
adopt(T* ptr);
~WeakIntrusive();
/** Get a strong pointer from the weak pointer, if possible. This will
only return a seated pointer if the strong count on the raw pointer
is non-zero before locking.
/**
* Get a strong pointer from the weak pointer, if possible. This will
* only return a seated pointer if the strong count on the raw pointer
* is non-zero before locking.
*/
SharedIntrusive<T>
lock() const;
/** Return true if the strong count is zero. */
/**
* Return true if the strong count is zero.
*/
[[nodiscard]] bool
expired() const;
/** Set the pointer to null and decrement the weak count.
Note: This may run the destructor if the strong count is zero.
*/
/**
* Set the pointer to null and decrement the weak count.
*
* Note: This may run the destructor if the strong count is zero.
*/
void
reset();
private:
T* ptr_ = nullptr;
/** Decrement the weak count. This does _not_ set the raw pointer to
null.
Note: This may run the destructor if the strong count is zero.
*/
/**
* Decrement the weak count. This does _not_ set the raw pointer to
* null.
*
* Note: This may run the destructor if the strong count is zero.
*/
void
unsafeReleaseNoStore();
};
//------------------------------------------------------------------------------
/** A combination of a strong and a weak intrusive pointer stored in the
space of a single pointer.
This class is similar to a `std::variant<SharedIntrusive,WeakIntrusive>`
with some optimizations. In particular, it uses a low-order bit to
determine if the raw pointer represents a strong pointer or a weak
pointer. It can also be quickly switched between its strong pointer and
weak pointer representations. This class is useful for storing intrusive
pointers in tagged caches.
*/
/**
* A combination of a strong and a weak intrusive pointer stored in the
* space of a single pointer.
*
* This class is similar to a `std::variant<SharedIntrusive,WeakIntrusive>`
* with some optimizations. In particular, it uses a low-order bit to
* determine if the raw pointer represents a strong pointer or a weak
* pointer. It can also be quickly switched between its strong pointer and
* weak pointer representations. This class is useful for storing intrusive
* pointers in tagged caches.
*/
template <class T>
class SharedWeakUnion
@@ -336,69 +367,83 @@ public:
~SharedWeakUnion();
/** Return a strong pointer if this is already a strong pointer (i.e.
don't lock the weak pointer. Use the `lock` method if that's what's
needed)
/**
* Return a strong pointer if this is already a strong pointer (i.e.
* don't lock the weak pointer. Use the `lock` method if that's what's
* needed)
*/
[[nodiscard]] SharedIntrusive<T>
getStrong() const;
/** Return true if this is a strong pointer and the strong pointer is
seated.
/**
* Return true if this is a strong pointer and the strong pointer is
* seated.
*/
explicit
operator bool() const noexcept;
/** Set the pointer to null, decrement the appropriate ref count, and
run the appropriate release action.
/**
* Set the pointer to null, decrement the appropriate ref count, and
* run the appropriate release action.
*/
void
reset();
/** If this is a strong pointer, return the raw pointer. Otherwise
return null.
/**
* If this is a strong pointer, return the raw pointer. Otherwise
* return null.
*/
[[nodiscard]] T*
get() const;
/** If this is a strong pointer, return the strong count. Otherwise
/**
* If this is a strong pointer, return the strong count. Otherwise
* return 0
*/
[[nodiscard]] std::size_t
useCount() const;
/** Return true if there is a non-zero strong count. */
/**
* Return true if there is a non-zero strong count.
*/
[[nodiscard]] bool
expired() const;
/** If this is a strong pointer, return the strong pointer. Otherwise
attempt to lock the weak pointer.
/**
* If this is a strong pointer, return the strong pointer. Otherwise
* attempt to lock the weak pointer.
*/
[[nodiscard]] SharedIntrusive<T>
lock() const;
/** Return true is this represents a strong pointer. */
/**
* Return true is this represents a strong pointer.
*/
[[nodiscard]] bool
isStrong() const;
/** Return true is this represents a weak pointer. */
/**
* Return true is this represents a weak pointer.
*/
[[nodiscard]] bool
isWeak() const;
/** If this is a weak pointer, attempt to convert it to a strong
pointer.
@return true if successfully converted to a strong pointer (or was
already a strong pointer). Otherwise false.
*/
/**
* If this is a weak pointer, attempt to convert it to a strong
* pointer.
*
* @return true if successfully converted to a strong pointer (or was
* already a strong pointer). Otherwise false.
*/
bool
convertToStrong();
/** If this is a strong pointer, attempt to convert it to a weak
pointer.
@return false if the pointer is null. Otherwise return true.
*/
/**
* If this is a strong pointer, attempt to convert it to a weak
* pointer.
*
* @return false if the pointer is null. Otherwise return true.
*/
bool
convertToWeak();
@@ -411,23 +456,27 @@ private:
static constexpr std::uintptr_t kPtrMask = ~kTagMask;
private:
/** Return the raw pointer held by this object.
/**
* Return the raw pointer held by this object.
*/
[[nodiscard]] T*
unsafeGetRawPtr() const;
enum class RefStrength { Strong, Weak };
/** Set the raw pointer and tag bit directly.
/**
* Set the raw pointer and tag bit directly.
*/
void
unsafeSetRawPtr(T* p, RefStrength rs);
/** Set the raw pointer and tag bit to all zeros (strong null pointer).
/**
* Set the raw pointer and tag bit to all zeros (strong null pointer).
*/
void unsafeSetRawPtr(std::nullptr_t);
/** Decrement the appropriate ref count, and run the appropriate release
action. Note: this does _not_ set the raw pointer to null.
/**
* Decrement the appropriate ref count, and run the appropriate release
* action. Note: this does _not_ set the raw pointer to null.
*/
void
unsafeReleaseNoStore();
@@ -435,12 +484,13 @@ private:
//------------------------------------------------------------------------------
/** Create a shared intrusive pointer.
Note: unlike std::shared_ptr, where there is an advantage of allocating
the pointer and control block together, there is no benefit for intrusive
pointers.
*/
/**
* Create a shared intrusive pointer.
*
* Note: unlike std::shared_ptr, where there is an advantage of allocating
* the pointer and control block together, there is no benefit for intrusive
* pointers.
*/
template <class TT, class... Args>
SharedIntrusive<TT>
makeSharedIntrusive(Args&&... args)

View File

@@ -641,6 +641,9 @@ template <class T>
T*
SharedWeakUnion<T>::unsafeGetRawPtr() const
{
// tp_ packs a raw pointer together with a strength bit; recovering the
// pointer inherently requires an integer-to-pointer cast.
// NOLINTNEXTLINE(performance-no-int-to-ptr)
return reinterpret_cast<T*>(tp_ & kPtrMask);
}

View File

@@ -8,35 +8,38 @@
namespace xrpl {
/** Action to perform when releasing a strong pointer.
noop: Do nothing. For example, a `noop` action will occur when a count is
decremented to a non-zero value.
partialDestroy: Run the `partialDestructor`. This action will happen when a
strong count is decremented to zero and the weak count is non-zero.
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.
/**
* Action to perform when releasing a strong pointer.
*
* noop: Do nothing. For example, a `noop` action will occur when a count is
* decremented to a non-zero value.
*
* partialDestroy: Run the `partialDestructor`. This action will happen when a
* strong count is decremented to zero and the weak count is non-zero.
*
* 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 };
/** Action to perform when releasing a weak pointer.
noop: Do nothing. For example, a `noop` action will occur when a count is
decremented to a non-zero value.
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.
/**
* Action to perform when releasing a weak pointer.
*
* noop: Do nothing. For example, a `noop` action will occur when a count is
* decremented to a non-zero value.
*
* 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 };
/** Implement the strong count, weak count, and bit flags for an intrusive
pointer.
A class can satisfy the requirements of an xrpl::IntrusivePointer by
inheriting from this class.
*/
/**
* Implement the strong count, weak count, and bit flags for an intrusive
* pointer.
*
* A class can satisfy the requirements of an xrpl::IntrusivePointer by
* inheriting from this class.
*/
struct IntrusiveRefCounts
{
virtual ~IntrusiveRefCounts() noexcept;
@@ -105,109 +108,123 @@ private:
static constexpr size_t kFieldTypeBits = sizeof(FieldType) * 8;
static constexpr FieldType kOne = 1;
/** `refCounts` consists of four fields that are treated atomically:
1. Strong count. This is a count of the number of shared pointers that
hold a reference to this object. When the strong counts goes to zero,
if the weak count is zero, the destructor is run. If the weak count is
non-zero when the strong count goes to zero then the partialDestructor
is run.
2. Weak count. This is a count of the number of weak pointer that hold
a reference to this object. When the weak count goes to zero and the
strong count is also zero, then the destructor is run.
3. Partial destroy started bit. This bit is set if the
`partialDestructor` function has been started (or is about to be
started). This is used to prevent the destructor from running
concurrently with the partial destructor. This can easily happen when
the last strong pointer release its reference in one thread and starts
the partialDestructor, while in another thread the last weak pointer
goes out of scope and starts the destructor while the partialDestructor
is still running. Both a start and finished bit is needed to handle a
corner-case where the last strong pointer goes out of scope, then then
last `weakPointer` goes out of scope, but this happens before the
`partialDestructor` bit is set. It would be possible to use a single
bit if it could also be set atomically when the strong count goes to
zero and the weak count is non-zero, but that would add complexity (and
likely slow down common cases as well).
4. Partial destroy finished bit. This bit is set when the
`partialDestructor` has finished running. See (3) above for more
information.
*/
/**
* `refCounts` consists of four fields that are treated atomically:
*
* 1. Strong count. This is a count of the number of shared pointers that
* hold a reference to this object. When the strong counts goes to zero,
* if the weak count is zero, the destructor is run. If the weak count is
* non-zero when the strong count goes to zero then the partialDestructor
* is run.
*
* 2. Weak count. This is a count of the number of weak pointer that hold
* a reference to this object. When the weak count goes to zero and the
* strong count is also zero, then the destructor is run.
*
* 3. Partial destroy started bit. This bit is set if the
* `partialDestructor` function has been started (or is about to be
* started). This is used to prevent the destructor from running
* concurrently with the partial destructor. This can easily happen when
* the last strong pointer release its reference in one thread and starts
* the partialDestructor, while in another thread the last weak pointer
* goes out of scope and starts the destructor while the partialDestructor
* is still running. Both a start and finished bit is needed to handle a
* corner-case where the last strong pointer goes out of scope, then then
* last `weakPointer` goes out of scope, but this happens before the
* `partialDestructor` bit is set. It would be possible to use a single
* bit if it could also be set atomically when the strong count goes to
* zero and the weak count is non-zero, but that would add complexity (and
* likely slow down common cases as well).
*
* 4. Partial destroy finished bit. This bit is set when the
* `partialDestructor` has finished running. See (3) above for more
* information.
*/
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
*/
/**
* 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 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
*/
/**
* 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 kWeakDelta = (kOne << kStrongCountNumBits);
/** Flag that is set when the partialDestroy function has started running
(or is about to start running).
See description of the `refCounts` field for a fuller description of
this field.
*/
/**
* Flag that is set when the partialDestroy function has started running
* (or is about to start running).
*
* See description of the `refCounts` field for a fuller description of
* this field.
*/
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.
*/
/**
* 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 kPartialDestroyFinishedMask = (kOne << (kFieldTypeBits - 2));
/** Mask that will zero out all the `count` bits and leave the tag bits
unchanged.
*/
/**
* Mask that will zero out all the `count` bits and leave the tag bits
* unchanged.
*/
static constexpr FieldType kTagMask = kPartialDestroyStartedMask | kPartialDestroyFinishedMask;
/** Mask that will zero out the `tag` bits and leave the count bits
unchanged.
*/
/**
* Mask that will zero out the `tag` bits and leave the count bits
* unchanged.
*/
static constexpr FieldType kValueMask = ~kTagMask;
/** Mask that will zero out everything except the strong count.
/**
* Mask that will zero out everything except the strong count.
*/
static constexpr FieldType kStrongMask = ((kOne << kStrongCountNumBits) - 1) & kValueMask;
/** Mask that will zero out everything except the weak count.
/**
* Mask that will zero out everything except the weak count.
*/
static constexpr FieldType kWeakMask =
(((kOne << kWeakCountNumBits) - 1) << kStrongCountNumBits) & kValueMask;
/** Unpack the count and tag fields from the packed atomic integer form. */
/**
* Unpack the count and tag fields from the packed atomic integer form.
*/
struct RefCountPair
{
CountType strong;
CountType weak;
/** The `partialDestroyStartedBit` is set to on when the partial
destroy function is started. It is not a boolean; it is a uint32
with all bits zero with the possible exception of the
`partialDestroyStartedMask` bit. This is done so it can be directly
masked into the `combinedValue`.
/**
* The `partialDestroyStartedBit` is set to on when the partial
* destroy function is started. It is not a boolean; it is a uint32
* with all bits zero with the possible exception of the
* `partialDestroyStartedMask` bit. This is done so it can be directly
* masked into the `combinedValue`.
*/
FieldType partialDestroyStartedBit{0};
/** The `partialDestroyFinishedBit` is set to on when the partial
destroy function has finished.
/**
* The `partialDestroyFinishedBit` is set to on when the partial
* destroy function has finished.
*/
FieldType partialDestroyFinishedBit{0};
RefCountPair(FieldType v) noexcept;
RefCountPair(CountType s, CountType w) noexcept;
/** Convert back to the packed integer form. */
/**
* Convert back to the packed integer form.
*/
[[nodiscard]] FieldType
combinedValue() const noexcept;
@@ -215,9 +232,10 @@ private:
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).
/**
* 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 kCheckStrongMaxValue = kMaxStrongValue - 32;
static constexpr CountType kCheckWeakMaxValue = kMaxWeakValue - 32;

View File

@@ -70,11 +70,15 @@ public:
{
}
/** Stores instance of T specific to the calling coroutine or thread. */
/**
* Stores instance of T specific to the calling coroutine or thread.
*/
T&
operator*();
/** Stores instance of T specific to the calling coroutine or thread. */
/**
* Stores instance of T specific to the calling coroutine or thread.
*/
T*
operator->()
{

View File

@@ -16,7 +16,9 @@
namespace xrpl {
/** Manages partitions for logging. */
/**
* Manages partitions for logging.
*/
class Logs
{
private:
@@ -40,69 +42,81 @@ private:
writeAlways(beast::Severity level, std::string const& text) override;
};
/** Manages a system file containing logged output.
The system file remains open during program execution. Interfaces
are provided for interoperating with standard log management
tools like logrotate(8):
http://linuxcommand.org/man_pages/logrotate8.html
@note None of the listed interfaces are thread-safe.
*/
/**
* Manages a system file containing logged output.
* The system file remains open during program execution. Interfaces
* are provided for interoperating with standard log management
* tools like logrotate(8):
* http://linuxcommand.org/man_pages/logrotate8.html
* @note None of the listed interfaces are thread-safe.
*/
class File
{
public:
/** Construct with no associated system file.
A system file may be associated later with @ref open.
@see open
*/
/**
* Construct with no associated system file.
* A system file may be associated later with @ref open.
* @see open
*/
File();
/** Destroy the object.
If a system file is associated, it will be flushed and closed.
*/
/**
* Destroy the object.
* If a system file is associated, it will be flushed and closed.
*/
~File() = default;
/** Determine if a system file is associated with the log.
@return `true` if a system file is associated and opened for
writing.
*/
/**
* Determine if a system file is associated with the log.
* @return `true` if a system file is associated and opened for
* writing.
*/
[[nodiscard]] bool
isOpen() const noexcept;
/** Associate a system file with the log.
If the file does not exist an attempt is made to create it
and open it for writing. If the file already exists an attempt is
made to open it for appending.
If a system file is already associated with the log, it is closed
first.
@return `true` if the file was opened.
*/
/**
* Associate a system file with the log.
* If the file does not exist an attempt is made to create it
* and open it for writing. If the file already exists an attempt is
* made to open it for appending.
* If a system file is already associated with the log, it is closed
* first.
* @return `true` if the file was opened.
*/
bool
open(boost::filesystem::path const& path);
/** Close and re-open the system file associated with the log
This assists in interoperating with external log management tools.
@return `true` if the file was opened.
*/
/**
* Close and re-open the system file associated with the log
* This assists in interoperating with external log management tools.
* @return `true` if the file was opened.
*/
bool
closeAndReopen();
/** Close the system file if it is open. */
/**
* Close the system file if it is open.
*/
void
close();
/** write to the log file.
Does nothing if there is no associated system file.
*/
/**
* write to the log file.
* Does nothing if there is no associated system file.
*/
void
write(char const* text);
/** write to the log file and append an end of line marker.
Does nothing if there is no associated system file.
*/
/**
* write to the log file and append an end of line marker.
* Does nothing if there is no associated system file.
*/
void
writeln(char const* text);
/** Write to the log file using std::string. */
/**
* Write to the log file using std::string.
*/
/** @{ */
void
write(std::string const& str)
@@ -223,19 +237,21 @@ private:
//------------------------------------------------------------------------------
// Debug logging:
/** Set the sink for the debug journal.
@param sink unique_ptr to new debug Sink.
@return unique_ptr to the previous Sink. nullptr if there was no Sink.
*/
/**
* Set the sink for the debug journal.
*
* @param sink unique_ptr to new debug Sink.
* @return unique_ptr to the previous Sink. nullptr if there was no Sink.
*/
std::unique_ptr<beast::Journal::Sink>
setDebugLogSink(std::unique_ptr<beast::Journal::Sink> sink);
/** Returns a debug journal.
The journal may drain to a null sink, so its output
may never be seen. Never use it for critical
information.
*/
/**
* Returns a debug journal.
* The journal may drain to a null sink, so its output
* may never be seen. Never use it for critical
* information.
*/
beast::Journal
debugLog();

View File

@@ -6,7 +6,8 @@
namespace xrpl {
/** Calculate one number divided by another number in percentage.
/**
* Calculate one number divided by another number in percentage.
* The result is rounded up to the next integer, and capped in the range [0,100]
* E.g. calculatePercent(1, 100) = 1 because 1/100 = 0.010000
* calculatePercent(1, 99) = 2 because 1/99 = 0.010101
@@ -19,7 +20,7 @@ namespace xrpl {
* @return the percentage, in [0, 100]
*
* @note total cannot be zero.
* */
*/
constexpr std::size_t
calculatePercent(std::size_t count, std::size_t total)
{

View File

@@ -14,7 +14,6 @@
#include <stdexcept>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
namespace xrpl {
@@ -48,46 +47,54 @@ isPowerOfTen(T value)
namespace detail {
/** Builds a table of the powers of 10
/**
* Builds a table of the powers of 10
*
* This function is marked consteval, so it can only be run in
* a constexpr context. This assures that it is and can only be run at
* compile time. Doing it at runtime would be pretty wasteful and
* inefficient.
*/
constexpr std::size_t kInt64Digits = 20;
consteval std::array<std::uint64_t, kInt64Digits>
constexpr std::size_t kUint64Digits = 20;
[[maybe_unused]] constexpr std::size_t kUint128Digits = 39;
template <typename T, std::size_t Digits>
consteval std::array<T, Digits>
buildPowersOfTen()
{
std::array<std::uint64_t, kInt64Digits> result{};
std::array<T, Digits> result{};
std::uint64_t power = 1;
T power = 1;
std::size_t exponent = 0;
// end the loop early so it doesn't overflow;
for (; exponent < result.size() - 1; ++exponent, power *= 10)
{
result[exponent] = power;
if (power > std::numeric_limits<std::uint64_t>::max() / 10)
if (power > std::numeric_limits<T>::max() / 10)
throw std::logic_error("Power of 10 table is too big");
}
result[exponent] = power;
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
if (power < std::numeric_limits<T>::max() / 10)
throw std::logic_error("Power of 10 table is not big enough for the given type");
return result;
}
} // namespace detail
constexpr std::array<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
template <typename T = std::uint64_t, std::size_t Digits = detail::kUint64Digits>
constexpr std::array<T, Digits> kPowerOfTenImpl = detail::buildPowersOfTen<T, Digits>();
constexpr auto kPowerOfTen = kPowerOfTenImpl<std::uint64_t, detail::kUint64Digits>;
static_assert(kPowerOfTen[0] == 1);
static_assert(kPowerOfTen[1] == 10);
static_assert(kPowerOfTen[10] == 10'000'000'000);
static_assert(
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1);
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kUint64Digits - 1);
/** MantissaRange defines a range for the mantissa of a normalized Number.
/**
* MantissaRange defines a range for the mantissa of a normalized Number.
*
* The mantissa is in the range [min, max], where
* * min is a power of 10, and
@@ -124,17 +131,37 @@ struct MantissaRange final
{
using rep = std::uint64_t;
// NOLINTBEGIN(readability-enum-initial-value)
// The values don't matter, except for Large
enum class MantissaScale {
// Small can be removed when either featureSingleAssetVault or featureLendingProtocol are
// retired
Small,
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
LargeLegacy,
Large,
// Large320 can be removed when fixCleanup3_3_0 is retired
Large320,
// If Large330 is ever the only remaining "Large*" entry, it can be renamed to just "Large".
Large330,
// Large is a de-facto alias for "the latest", and is only here for backward compatibility
// in the extremely unlikely case that a downstream project made use of it. Note that
// because the behavior changed, this may still be a breaking change.
Large = Large330,
};
// NOLINTEND(readability-enum-initial-value)
// This entire enum can be removed when fixCleanup3_2_0 is retired
enum class CuspRoundingFix : bool {
Disabled = false,
Enabled = true,
// This entire enum can be removed when the last relevant amendment is retired
enum class CuspRoundingFix : std::uint8_t {
// Disabled can be removed when fixCleanup3_2_0 is retired
Disabled = 0,
// Enabled320 can be removed when fixCleanup3_3_0 is retired
Enabled320 = 1,
// If we ever get to the point that there's only one entry, remove the entire enum
Enabled330 = 2,
// Enabled is a de-facto alias for "the latest", and is only here for backward compatibility
// in the extremely unlikely case that a downstream project made use of it. Note that
// because the behavior changed, this may still be a breaking change.
Enabled = Enabled330,
};
explicit constexpr MantissaRange(MantissaScale sc) : scale(sc)
@@ -145,13 +172,27 @@ struct MantissaRange final
int const log{getExponent(scale)};
rep const min{getMin(scale, log)};
rep const max{(min * 10) - 1};
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
static MantissaRange const&
getMantissaRange(MantissaScale scale);
CuspRoundingFix const cuspRoundingFix{isCuspFixEnabled(scale)};
static std::set<MantissaScale> const&
getAllScales();
getAllScales()
{
static std::set<MantissaRange::MantissaScale> const kScales = {
MantissaRange::MantissaScale::Small,
MantissaRange::MantissaScale::LargeLegacy,
MantissaRange::MantissaScale::Large320,
MantissaRange::MantissaScale::Large330,
};
return kScales;
}
class Access
{
static constexpr MantissaRange const&
mantissaRange(MantissaScale scale);
friend Number;
};
private:
static constexpr int
@@ -162,7 +203,8 @@ private:
case MantissaScale::Small:
return 15;
case MantissaScale::LargeLegacy:
case MantissaScale::Large:
case MantissaScale::Large320:
case MantissaScale::Large330:
return 18;
// LCOV_EXCL_START
default:
@@ -191,24 +233,24 @@ private:
case MantissaScale::Small:
case MantissaScale::LargeLegacy:
return CuspRoundingFix::Disabled;
case MantissaScale::Large:
return CuspRoundingFix::Enabled;
case MantissaScale::Large320:
return CuspRoundingFix::Enabled320;
case MantissaScale::Large330:
return CuspRoundingFix::Enabled330;
default:
// If called in a constexpr context, this throw assures that the build fails if an
// invalid scale is used.
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
}
}
static std::unordered_map<MantissaScale, MantissaRange> const&
getRanges();
};
// Like std::integral, but only 64-bit integral types.
template <class T>
concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
/** Number is a floating point type that can represent a wide range of values.
/**
* Number is a floating point type that can represent a wide range of values.
*
* It can represent all values that can be represented by an STAmount -
* regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least
@@ -304,7 +346,6 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
* disable the amendments that control the mantissa range choice
* (SingleAssetVault and LendingProtocol), and/or check if either of those
* amendments are enabled to determine which result to expect.
*
*/
class Number final
{
@@ -323,6 +364,8 @@ public:
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);
static constexpr internalrep kMaxRepUp = ((kMaxRep / 10) + 1) * 10;
static_assert(kMaxRepUp == 9'223'372'036'854'775'810ULL);
// May need to make unchecked private
struct Unchecked
@@ -390,10 +433,11 @@ public:
static Number
lowest() noexcept;
/** Conversions to Number are implicit and conversions away from Number
* are explicit. This design encourages and facilitates the use of Number
* as the preferred type for floating point arithmetic as it makes
* "mixed mode" more convenient, e.g. MPTAmount + Number.
/**
* Conversions to Number are implicit and conversions away from Number
* are explicit. This design encourages and facilitates the use of Number
* as the preferred type for floating point arithmetic as it makes
* "mixed mode" more convenient, e.g. MPTAmount + Number.
*/
explicit
operator rep() const; // round to nearest, even on tie
@@ -448,7 +492,9 @@ public:
return l.mantissa_ < r.mantissa_;
}
/** Return the sign of the amount */
/**
* Return the sign of the amount
*/
[[nodiscard]] constexpr int
signum() const noexcept
{
@@ -502,14 +548,16 @@ public:
static RoundingMode
setround(RoundingMode inMode);
/** Returns which mantissa scale is currently in use for normalization.
/**
* 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::MantissaScale
getMantissaScale();
/** Changes which mantissa scale is used for normalization.
/**
* Changes which mantissa scale is used for normalization.
*
* If you think you need to call this outside of unit tests, no you don't.
*/
@@ -545,6 +593,13 @@ public:
std::pair<T, int>
normalizeToRange() const;
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
// is negative, returns the positive value. This takes a little extra work
// because converting std::numeric_limits<std::int64_t>::min() flirts with
// UB, and can vary across compilers.
static internalrep
externalToInternal(rep mantissa);
private:
static thread_local RoundingMode mode;
// The available ranges for mantissa
@@ -554,10 +609,17 @@ private:
// changing the values inside the range.
static thread_local std::reference_wrapper<MantissaRange const> kRange;
class Guard;
void
normalize(MantissaRange const& range);
/** Normalize Number components to an arbitrary range.
// Guard has the fields that we need, as well as MantissaRange, so if we have a guard, use that
void
normalize(Guard const& guard);
/**
* Normalize Number components to an arbitrary range.
*
* min/maxMantissa are parameters because this function is used by both
* normalize(), which reads from kRange, and by normalizeToRange,
@@ -571,7 +633,7 @@ private:
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
MantissaRange::CuspRoundingFix cuspRoundingFix);
template <class T>
friend void
@@ -581,7 +643,7 @@ private:
int& exponent,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
MantissaRange::CuspRoundingFix cuspRoundingFix,
bool dropped);
[[nodiscard]] bool
@@ -592,15 +654,6 @@ private:
// exponent could go out of range, so it will be checked.
[[nodiscard]] Number
shiftExponent(int exponentDelta) const;
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
// is negative, returns the positive value. This takes a little extra work
// because converting std::numeric_limits<std::int64_t>::min() flirts with
// UB, and can vary across compilers.
static internalrep
externalToInternal(rep mantissa);
class Guard;
};
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept
@@ -635,7 +688,8 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
{
}
/** Returns the mantissa of the external view of the Number.
/**
* Returns the mantissa of the external view of the Number.
*
* Please see the "---- External Interface ----" section of the class
* documentation for an explanation of why the internal value may be modified.
@@ -656,7 +710,8 @@ Number::mantissa() const noexcept
return sign * static_cast<Number::rep>(m);
}
/** Returns the exponent of the external view of the Number.
/**
* Returns the exponent of the external view of the Number.
*
* Please see the "---- External Interface ----" section of the class
* documentation for an explanation of why the internal value may be modified.
@@ -862,21 +917,11 @@ squelch(Number const& x, Number const& limit) noexcept
return x;
}
inline std::string
to_string(MantissaRange::MantissaScale const& scale)
{
switch (scale)
{
case MantissaRange::MantissaScale::Small:
return "small";
case MantissaRange::MantissaScale::LargeLegacy:
return "largeLegacy";
case MantissaRange::MantissaScale::Large:
return "large";
default:
throw std::runtime_error("Bad scale");
}
}
std::string
to_string(MantissaRange::MantissaScale const& scale);
std::string
to_string(Number::RoundingMode const& round);
class SaveNumberRoundMode
{
@@ -915,10 +960,10 @@ public:
operator=(NumberRoundModeGuard const&) = delete;
};
/** Sets the new scale and restores the old scale when it leaves scope.
/**
* Sets the new scale and restores the old scale when it leaves scope.
*
* If you think you need to use this class outside of unit tests, no you don't.
*
*/
class NumberMantissaScaleGuard
{

View File

@@ -13,23 +13,25 @@
namespace xrpl {
/** A closed interval over the domain T.
For an instance ClosedInterval c, this represents the closed interval
(c.first(), c.last()). A single element interval has c.first() == c.last().
This is simply a type-alias for boost interval container library interval
set, so users should consult that documentation for available supporting
member and free functions.
*/
/**
* A closed interval over the domain T.
*
* For an instance ClosedInterval c, this represents the closed interval
* (c.first(), c.last()). A single element interval has c.first() == c.last().
*
* This is simply a type-alias for boost interval container library interval
* set, so users should consult that documentation for available supporting
* member and free functions.
*/
template <class T>
using ClosedInterval = boost::icl::closed_interval<T>;
/** Create a closed range interval
Helper function to create a closed range interval without having to qualify
the template argument.
*/
/**
* Create a closed range interval
*
* Helper function to create a closed range interval without having to qualify
* the template argument.
*/
template <class T>
ClosedInterval<T>
range(T low, T high)
@@ -37,28 +39,30 @@ range(T low, T high)
return ClosedInterval<T>(low, high);
}
/** A set of closed intervals over the domain T.
Represents a set of values of the domain T using the minimum number
of disjoint ClosedInterval<T>. This is useful to represent ranges of
T where a few instances are missing, e.g. the set 1-5,8-9,11-14.
This is simply a type-alias for boost interval container library interval
set, so users should consult that documentation for available supporting
member and free functions.
*/
/**
* A set of closed intervals over the domain T.
*
* Represents a set of values of the domain T using the minimum number
* of disjoint ClosedInterval<T>. This is useful to represent ranges of
* T where a few instances are missing, e.g. the set 1-5,8-9,11-14.
*
* This is simply a type-alias for boost interval container library interval
* set, so users should consult that documentation for available supporting
* member and free functions.
*/
template <class T>
using RangeSet = boost::icl::interval_set<T, std::less, ClosedInterval<T>>;
/** Convert a ClosedInterval to a styled string
The styled string is
"c.first()-c.last()" if c.first() != c.last()
"c.first()" if c.first() == c.last()
@param ci The closed interval to convert
@return The style string
*/
/**
* Convert a ClosedInterval to a styled string
*
* The styled string is
* "c.first()-c.last()" if c.first() != c.last()
* "c.first()" if c.first() == c.last()
*
* @param ci The closed interval to convert
* @return The style string
*/
template <class T>
std::string
to_string(ClosedInterval<T> const& ci)
@@ -68,14 +72,15 @@ to_string(ClosedInterval<T> const& ci)
return std::to_string(ci.first()) + "-" + std::to_string(ci.last());
}
/** Convert the given RangeSet to a styled string.
The styled string representation is the set of disjoint intervals joined
by commas. The string "empty" is returned if the set is empty.
@param rs The rangeset to convert
@return The styled string
*/
/**
* Convert the given RangeSet to a styled string.
*
* The styled string representation is the set of disjoint intervals joined
* by commas. The string "empty" is returned if the set is empty.
*
* @param rs The rangeset to convert
* @return The styled string
*/
template <class T>
std::string
to_string(RangeSet<T> const& rs)
@@ -91,15 +96,16 @@ to_string(RangeSet<T> const& rs)
return s;
}
/** Convert the given styled string to a RangeSet.
The styled string representation is the set
of disjoint intervals joined by commas.
@param rs The set to be populated
@param s The styled string to convert
@return True on successfully converting styled string
*/
/**
* Convert the given styled string to a RangeSet.
*
* The styled string representation is the set
* of disjoint intervals joined by commas.
*
* @param rs The set to be populated
* @param s The styled string to convert
* @return True on successfully converting styled string
*/
template <class T>
[[nodiscard]] bool
fromString(RangeSet<T>& rs, std::string const& s)
@@ -161,14 +167,15 @@ fromString(RangeSet<T>& rs, std::string const& s)
return result;
}
/** Find the largest value not in the set that is less than a given value.
@param rs The set of interest
@param t The value that must be larger than the result
@param minVal (Default is 0) The smallest allowed value
@return The largest v such that minV <= v < t and !contains(rs, v) or
std::nullopt if no such v exists.
*/
/**
* Find the largest value not in the set that is less than a given value.
*
* @param rs The set of interest
* @param t The value that must be larger than the result
* @param minVal (Default is 0) The smallest allowed value
* @return The largest v such that minV <= v < t and !contains(rs, v) or
* std::nullopt if no such v exists.
*/
template <class T>
std::optional<T>
prevMissing(RangeSet<T> const& rs, T t, T minVal = 0)

View File

@@ -15,22 +15,29 @@ public:
virtual ~Resolver() = 0;
/** Issue an asynchronous stop request. */
/**
* Issue an asynchronous stop request.
*/
virtual void
stopAsync() = 0;
/** Issue a synchronous stop request. */
/**
* Issue a synchronous stop request.
*/
virtual void
stop() = 0;
/** Issue a synchronous start request. */
/**
* Issue a synchronous start request.
*/
virtual void
start() = 0;
/** resolve all hostnames on the list
@param names the names to be resolved
@param handler the handler to call
*/
/**
* resolve all hostnames on the list
* @param names the names to be resolved
* @param handler the handler to call
*/
/** @{ */
template <class Handler>
void

View File

@@ -7,13 +7,14 @@
namespace xrpl {
/** A combination of a std::shared_ptr and a std::weak_pointer.
This class is a wrapper to a `std::variant<std::shared_ptr,std::weak_ptr>`
This class is useful for storing intrusive pointers in tagged caches using less
memory than storing both pointers directly.
*/
/**
* A combination of a std::shared_ptr and a std::weak_pointer.
*
*
* This class is a wrapper to a `std::variant<std::shared_ptr,std::weak_ptr>`
* This class is useful for storing intrusive pointers in tagged caches using less
* memory than storing both pointers directly.
*/
template <class T>
class SharedWeakCachePointer
@@ -48,65 +49,79 @@ public:
~SharedWeakCachePointer();
/** Return a strong pointer if this is already a strong pointer (i.e. don't
lock the weak pointer. Use the `lock` method if that's what's needed)
/**
* Return a strong pointer if this is already a strong pointer (i.e. don't
* lock the weak pointer. Use the `lock` method if that's what's needed)
*/
[[nodiscard]] std::shared_ptr<T> const&
getStrong() const;
/** Return true if this is a strong pointer and the strong pointer is
seated.
/**
* Return true if this is a strong pointer and the strong pointer is
* seated.
*/
explicit
operator bool() const noexcept;
/** Set the pointer to null, decrement the appropriate ref count, and run
the appropriate release action.
/**
* Set the pointer to null, decrement the appropriate ref count, and run
* the appropriate release action.
*/
void
reset();
/** If this is a strong pointer, return the raw pointer. Otherwise return
null.
/**
* If this is a strong pointer, return the raw pointer. Otherwise return
* null.
*/
[[nodiscard]] T*
get() const;
/** If this is a strong pointer, return the strong count. Otherwise return 0
/**
* If this is a strong pointer, return the strong count. Otherwise return 0
*/
[[nodiscard]] std::size_t
useCount() const;
/** Return true if there is a non-zero strong count. */
/**
* Return true if there is a non-zero strong count.
*/
[[nodiscard]] bool
expired() const;
/** If this is a strong pointer, return the strong pointer. Otherwise
attempt to lock the weak pointer.
/**
* If this is a strong pointer, return the strong pointer. Otherwise
* attempt to lock the weak pointer.
*/
[[nodiscard]] std::shared_ptr<T>
lock() const;
/** Return true is this represents a strong pointer. */
/**
* Return true is this represents a strong pointer.
*/
[[nodiscard]] bool
isStrong() const;
/** Return true is this represents a weak pointer. */
/**
* Return true is this represents a weak pointer.
*/
[[nodiscard]] bool
isWeak() const;
/** If this is a weak pointer, attempt to convert it to a strong pointer.
@return true if successfully converted to a strong pointer (or was
already a strong pointer). Otherwise false.
*/
/**
* If this is a weak pointer, attempt to convert it to a strong pointer.
*
* @return true if successfully converted to a strong pointer (or was
* already a strong pointer). Otherwise false.
*/
bool
convertToStrong();
/** If this is a strong pointer, attempt to convert it to a weak pointer.
@return false if the pointer is null. Otherwise return true.
*/
/**
* If this is a strong pointer, attempt to convert it to a weak pointer.
*
* @return false if the pointer is null. Otherwise return true.
*/
bool
convertToWeak();

View File

@@ -33,7 +33,9 @@ class SlabAllocator
static_assert(alignof(Type) == 8 || alignof(Type) == 4);
/** A block of memory that is owned by a slab allocator */
/**
* A block of memory that is owned by a slab allocator
*/
struct SlabBlock
{
// A mutex to protect the freelist for this block:
@@ -80,7 +82,9 @@ class SlabAllocator
SlabBlock&
operator=(SlabBlock&& other) = delete;
/** Determines whether the given pointer belongs to this allocator */
/**
* Determines whether the given pointer belongs to this allocator
*/
bool
own(std::uint8_t const* pIn) const noexcept
{
@@ -107,14 +111,15 @@ class SlabAllocator
return ret;
}
/** Return an item to this allocator's freelist.
@param ptr The pointer to the chunk of memory being deallocated.
@note This is a dangerous, private interface; the item being
returned should belong to this allocator. Debug builds
will check and assert if this is not the case. Release
builds will not.
/**
* Return an item to this allocator's freelist.
*
* @param ptr The pointer to the chunk of memory being deallocated.
*
* @note This is a dangerous, private interface; the item being
* returned should belong to this allocator. Debug builds
* will check and assert if this is not the case. Release
* builds will not.
*/
void
deallocate(std::uint8_t* ptr) noexcept
@@ -145,13 +150,14 @@ private:
std::size_t const slabSize_;
public:
/** Constructs a slab allocator able to allocate objects of a fixed size
@param count the number of items the slab allocator can allocate; note
that a count of 0 is valid and means that the allocator
is, effectively, disabled. This can be very useful in some
contexts (e.g. when minimal memory usage is needed) and
allows for graceful failure.
/**
* Constructs a slab allocator able to allocate objects of a fixed size
*
* @param count the number of items the slab allocator can allocate; note
* that a count of 0 is valid and means that the allocator
* is, effectively, disabled. This can be very useful in some
* contexts (e.g. when minimal memory usage is needed) and
* allows for graceful failure.
*/
constexpr explicit SlabAllocator(
std::size_t extra,
@@ -179,17 +185,20 @@ public:
// shutdown process up could make this possible.
~SlabAllocator() = default;
/** Returns the size of the memory block this allocator returns. */
/**
* Returns the size of the memory block this allocator returns.
*/
[[nodiscard]] constexpr std::size_t
size() const noexcept
{
return itemSize_;
}
/** Returns a suitably aligned pointer, if one is available.
@return a pointer to a block of memory from the allocator, or
nullptr if the allocator can't satisfy this request.
/**
* Returns a suitably aligned pointer, if one is available.
*
* @return a pointer to a block of memory from the allocator, or
* nullptr if the allocator can't satisfy this request.
*/
std::uint8_t*
allocate() noexcept
@@ -250,12 +259,13 @@ public:
return slab->allocate();
}
/** Returns the memory block to the allocator.
@param ptr A pointer to a memory block.
@param size If non-zero, a hint as to the size of the block.
@return true if this memory block belonged to the allocator and has
been released; false otherwise.
/**
* Returns the memory block to the allocator.
*
* @param ptr A pointer to a memory block.
* @param size If non-zero, a hint as to the size of the block.
* @return true if this memory block belonged to the allocator and has
* been released; false otherwise.
*/
bool
deallocate(std::uint8_t* ptr) noexcept
@@ -278,7 +288,9 @@ public:
}
};
/** A collection of slab allocators of various sizes for a given type. */
/**
* A collection of slab allocators of various sizes for a given type.
*/
template <typename Type>
class SlabAllocatorSet
{
@@ -345,13 +357,14 @@ public:
~SlabAllocatorSet() = default;
/** Returns a suitably aligned pointer, if one is available.
@param extra The number of extra bytes, above and beyond the size of
the object, that should be returned by the allocator.
@return a pointer to a block of memory, or nullptr if the allocator
can't satisfy this request.
/**
* Returns a suitably aligned pointer, if one is available.
*
* @param extra The number of extra bytes, above and beyond the size of
* the object, that should be returned by the allocator.
*
* @return a pointer to a block of memory, or nullptr if the allocator
* can't satisfy this request.
*/
std::uint8_t*
allocate(std::size_t extra) noexcept
@@ -368,12 +381,13 @@ public:
return nullptr;
}
/** Returns the memory block to the allocator.
@param ptr A pointer to a memory block.
@return true if this memory block belonged to one of the allocators
in this set and has been released; false otherwise.
/**
* Returns the memory block to the allocator.
*
* @param ptr A pointer to a memory block.
*
* @return true if this memory block belonged to one of the allocators
* in this set and has been released; false otherwise.
*/
bool
deallocate(std::uint8_t* ptr) noexcept

View File

@@ -16,12 +16,13 @@
namespace xrpl {
/** An immutable linear range of bytes.
A fully constructed Slice is guaranteed to be in a valid state.
A Slice is lightweight and copyable, it retains no ownership
of the underlying memory.
*/
/**
* An immutable linear range of bytes.
*
* A fully constructed Slice is guaranteed to be in a valid state.
* A Slice is lightweight and copyable, it retains no ownership
* of the underlying memory.
*/
class Slice
{
private:
@@ -32,30 +33,37 @@ public:
using value_type = std::uint8_t;
using const_iterator = value_type const*;
/** Default constructed Slice has length 0. */
/**
* Default constructed Slice has length 0.
*/
Slice() noexcept = default;
Slice(Slice const&) noexcept = default;
Slice&
operator=(Slice const&) noexcept = default;
/** Create a slice pointing to existing memory. */
/**
* Create a slice pointing to existing memory.
*/
Slice(void const* data, std::size_t size) noexcept
: data_(reinterpret_cast<std::uint8_t const*>(data)), size_(size)
{
}
/** Return `true` if the byte range is empty. */
/**
* Return `true` if the byte range is empty.
*/
[[nodiscard]] bool
empty() const noexcept
{
return size_ == 0;
}
/** Returns the number of bytes in the storage.
This may be zero for an empty range.
*/
/**
* Returns the number of bytes in the storage.
*
* This may be zero for an empty range.
*/
/** @{ */
[[nodiscard]] std::size_t
size() const noexcept
@@ -70,17 +78,20 @@ public:
}
/** @} */
/** Return a pointer to beginning of the storage.
@note The return type is guaranteed to be a pointer
to a single byte, to facilitate pointer arithmetic.
*/
/**
* Return a pointer to beginning of the storage.
* @note The return type is guaranteed to be a pointer
* to a single byte, to facilitate pointer arithmetic.
*/
[[nodiscard]] std::uint8_t const*
data() const noexcept
{
return data_;
}
/** Access raw bytes. */
/**
* Access raw bytes.
*/
std::uint8_t
operator[](std::size_t i) const noexcept
{
@@ -88,7 +99,9 @@ public:
return data_[i];
}
/** Advance the buffer. */
/**
* Advance the buffer.
*/
/** @{ */
Slice&
operator+=(std::size_t n)
@@ -108,7 +121,9 @@ public:
}
/** @} */
/** Shrinks the slice by moving its start forward by n characters. */
/**
* Shrinks the slice by moving its start forward by n characters.
*/
void
removePrefix(std::size_t n)
{
@@ -116,7 +131,9 @@ public:
size_ -= n;
}
/** Shrinks the slice by moving its end backward by n characters. */
/**
* Shrinks the slice by moving its end backward by n characters.
*/
void
removeSuffix(std::size_t n)
{
@@ -147,16 +164,17 @@ public:
return data_ + size_;
}
/** Return a "sub slice" of given length starting at the given position
Note that the subslice encompasses the range [pos, pos + rCount),
where rCount is the smaller of count and size() - pos.
@param pos position of the first character
@count requested length
@returns The requested subslice, if the request is valid.
@throws std::out_of_range if pos > size()
/**
* Return a "sub slice" of given length starting at the given position
*
* Note that the subslice encompasses the range [pos, pos + rCount),
* where rCount is the smaller of count and size() - pos.
*
* @param pos position of the first character
* @count requested length
*
* @return The requested subslice, if the request is valid.
* @throws std::out_of_range if pos > size()
*/
[[nodiscard]] Slice
substr(std::size_t pos, std::size_t count = std::numeric_limits<std::size_t>::max()) const
@@ -211,15 +229,17 @@ operator<<(Stream& s, Slice const& v)
}
template <class T, std::size_t N>
std::enable_if_t<std::is_same_v<T, char> || std::is_same_v<T, unsigned char>, Slice>
Slice
makeSlice(std::array<T, N> const& a)
requires(std::is_same_v<T, char> || std::is_same_v<T, unsigned char>)
{
return Slice(a.data(), a.size());
}
template <class T, class Alloc>
std::enable_if_t<std::is_same_v<T, char> || std::is_same_v<T, unsigned char>, Slice>
Slice
makeSlice(std::vector<T, Alloc> const& v)
requires(std::is_same_v<T, char> || std::is_same_v<T, unsigned char>)
{
return Slice(v.data(), v.size());
}

View File

@@ -17,15 +17,16 @@
namespace xrpl {
/** Format arbitrary binary data as an SQLite "blob literal".
In SQLite, blob literals must be encoded when used in a query. Per
https://sqlite.org/lang_expr.html#literal_values_constants_ they are
encoded as string literals containing hexadecimal data and preceded
by a single 'X' character.
@param blob An arbitrary blob of binary data
@return The input, encoded as a blob literal.
/**
* Format arbitrary binary data as an SQLite "blob literal".
*
* In SQLite, blob literals must be encoded when used in a query. Per
* https://sqlite.org/lang_expr.html#literal_values_constants_ they are
* encoded as string literals containing hexadecimal data and preceded
* by a single 'X' character.
*
* @param blob An arbitrary blob of binary data
* @return The input, encoded as a blob literal.
*/
std::string
sqlBlobLiteral(Blob const& blob);
@@ -130,11 +131,12 @@ trimWhitespace(std::string str);
std::optional<std::uint64_t>
toUInt64(std::string const& s);
/** Determines if the given string looks like a TOML-file hosting domain.
Do not use this function to determine if a particular string is a valid
domain, as this function may reject domains that are otherwise valid and
doesn't check whether the TLD is valid.
/**
* Determines if the given string looks like a TOML-file hosting domain.
*
* Do not use this function to determine if a particular string is a valid
* domain, as this function may reject domains that are otherwise valid and
* doesn't check whether the TLD is valid.
*/
bool
isProperlyFormedTomlDomain(std::string_view domain);

View File

@@ -41,18 +41,19 @@ struct ReplaceDynamically;
} // namespace detail
/** Map/cache combination.
This class implements a cache and a map. The cache keeps objects alive
in the map. The map allows multiple code paths that reference objects
with the same tag to get the same actual object.
So long as data is in the cache, it will stay in memory.
If it stays in memory even after it is ejected from the cache,
the map will track it.
@note Callers must not modify data objects that are stored in the cache
unless they hold their own lock over all cache operations.
*/
/**
* Map/cache combination.
* This class implements a cache and a map. The cache keeps objects alive
* in the map. The map allows multiple code paths that reference objects
* with the same tag to get the same actual object.
*
* So long as data is in the cache, it will stay in memory.
* If it stays in memory even after it is ejected from the cache,
* the map will track it.
*
* @note Callers must not modify data objects that are stored in the cache
* unless they hold their own lock over all cache operations.
*/
template <
class Key,
class T,
@@ -82,11 +83,15 @@ public:
beast::insight::Collector::ptr const& collector = beast::insight::NullCollector::make());
public:
/** Return the clock associated with the cache. */
/**
* Return the clock associated with the cache.
*/
clock_type&
clock();
/** Returns the number of items in the container. */
/**
* Returns the number of items in the container.
*/
std::size_t
size() const;
@@ -105,9 +110,10 @@ public:
void
reset();
/** Refresh the last access time on a key if present.
@return `true` If the key was found.
*/
/**
* Refresh the last access time on a key if present.
* @return `true` If the key was found.
*/
template <class KeyComparable>
bool
touchIfExists(KeyComparable const& key);
@@ -130,14 +136,15 @@ private:
SharedPointerType const&,
SharedPointerType&>;
/** Shared implementation of the canonicalize family.
`policy` selects how a collision is resolved when `key` already exists:
detail::ReplaceCached, detail::ReplaceClient or
detail::ReplaceDynamically. For ReplaceDynamically `replaceCallback` is
invoked with the existing strong pointer and returns whether to replace
the cached value with `data`; for the tag policies it is unused.
*/
/**
* Shared implementation of the canonicalize family.
*
* `policy` selects how a collision is resolved when `key` already exists:
* detail::ReplaceCached, detail::ReplaceClient or
* detail::ReplaceDynamically. For ReplaceDynamically `replaceCallback` is
* invoked with the existing strong pointer and returns whether to replace
* the cached value with `data`; for the tag policies it is unused.
*/
template <class Policy, class Callback = std::nullptr_t>
bool
canonicalizeImpl(
@@ -147,76 +154,82 @@ private:
Callback&& replaceCallback = nullptr);
public:
/** Replace aliased objects with originals.
Due to concurrency it is possible for two separate objects with
the same content and referring to the same unique "thing" to exist.
This routine eliminates the duplicate and performs a replacement
on the callers shared pointer if needed.
`replaceCallback` is a callable taking the existing strong pointer and
returning whether to replace the cached value with `data` (true) or to
keep the cached value and write it back into `data` (false). Because the
write-back case mutates `data`, `data` must be writable.
@param key The key corresponding to the object
@param data A shared pointer to the data corresponding to the object.
@param replaceCallback A callable (existing strong pointer -> bool).
@return `true` if an existing live entry was found and used; `false` if a new entry was
inserted or an expired tracked entry was re-cached.
**/
/**
* Replace aliased objects with originals.
*
* Due to concurrency it is possible for two separate objects with
* the same content and referring to the same unique "thing" to exist.
* This routine eliminates the duplicate and performs a replacement
* on the callers shared pointer if needed.
*
* `replaceCallback` is a callable taking the existing strong pointer and
* returning whether to replace the cached value with `data` (true) or to
* keep the cached value and write it back into `data` (false). Because the
* write-back case mutates `data`, `data` must be writable.
*
* @param key The key corresponding to the object
* @param data A shared pointer to the data corresponding to the object.
* @param replaceCallback A callable (existing strong pointer -> bool).
*
* @return `true` if an existing live entry was found and used; `false` if a new entry was
* inserted or an expired tracked entry was re-cached.
*/
template <class Callback>
bool
canonicalize(key_type const& key, SharedPointerType& data, Callback&& replaceCallback);
/** Insert/update the canonical entry for `key`, always replacing the
cached value with `data`.
If an entry already exists for `key`, the cached value is unconditionally
replaced with `data`; otherwise `data` is inserted. `data` is never
written back, so it may be const.
@param key The key corresponding to the object.
@param data A shared pointer to the data corresponding to the object.
@return `true` if an existing live entry was found and used; `false` if a new entry was
inserted or an expired tracked entry was re-cached.
**/
/**
* Insert/update the canonical entry for `key`, always replacing the
* cached value with `data`.
*
* If an entry already exists for `key`, the cached value is unconditionally
* replaced with `data`; otherwise `data` is inserted. `data` is never
* written back, so it may be const.
*
* @param key The key corresponding to the object.
* @param data A shared pointer to the data corresponding to the object.
*
* @return `true` if an existing live entry was found and used; `false` if a new entry was
* inserted or an expired tracked entry was re-cached.
*/
bool
canonicalizeReplaceCache(key_type const& key, SharedPointerType const& data);
/** Insert the canonical entry for `key`, keeping any existing cached value.
If an entry already exists for `key`, the cached value is kept and
written back into `data` so the caller ends up with the canonical
object; otherwise `data` is inserted. Because `data` may be overwritten
it must be writable.
@param key The key corresponding to the object.
@param data A shared pointer to the data corresponding to the object;
updated to the canonical value when one already exists.
@return `true` if an existing live entry was found and used; `false` if a new entry was
inserted or an expired tracked entry was re-cached.
**/
/**
* Insert the canonical entry for `key`, keeping any existing cached value.
*
* If an entry already exists for `key`, the cached value is kept and
* written back into `data` so the caller ends up with the canonical
* object; otherwise `data` is inserted. Because `data` may be overwritten
* it must be writable.
*
* @param key The key corresponding to the object.
* @param data A shared pointer to the data corresponding to the object;
* updated to the canonical value when one already exists.
*
* @return `true` if an existing live entry was found and used; `false` if a new entry was
* inserted or an expired tracked entry was re-cached.
*/
bool
canonicalizeReplaceClient(key_type const& key, SharedPointerType& data);
SharedPointerType
fetch(key_type const& key);
/** Insert the element into the container.
If the key already exists, nothing happens.
@return `true` If the element was inserted
*/
/**
* Insert the element into the container.
* If the key already exists, nothing happens.
* @return `true` If the element was inserted
*/
template <class ReturnType = bool>
auto
insert(key_type const& key, T const& value) -> std::enable_if_t<!IsKeyCache, ReturnType>;
insert(key_type const& key, T const& value) -> ReturnType
requires(!IsKeyCache);
template <class ReturnType = bool>
auto
insert(key_type const& key) -> std::enable_if_t<IsKeyCache, ReturnType>;
insert(key_type const& key) -> ReturnType
requires IsKeyCache;
// VFALCO NOTE It looks like this returns a copy of the data in
// the output parameter 'data'. This could be expensive.
@@ -233,15 +246,18 @@ public:
getKeys() const;
// CachedSLEs functions.
/** Returns the fraction of cache hits. */
/**
* Returns the fraction of cache hits.
*/
double
rate() const;
/** Fetch an item from the cache.
If the digest was not found, Handler
will be called with this signature:
SLE::const_pointer(void)
*/
/**
* Fetch an item from the cache.
* If the digest was not found, Handler
* will be called with this signature:
* SLE::const_pointer(void)
*/
template <class Handler>
SharedPointerType
fetch(key_type const& digest, Handler const& h);

View File

@@ -3,6 +3,9 @@
#include <xrpl/basics/IntrusivePointer.ipp>
#include <xrpl/basics/Log.h> // IWYU pragma: keep
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/basics/scope.h>
#include <algorithm>
namespace xrpl {
@@ -57,7 +60,10 @@ inline TaggedCache<
beast::insight::Collector::ptr const& collector)
: journal_(journal)
, clock_(clock)
, stats_(name, std::bind(&TaggedCache::collectMetrics, this), collector)
, stats_(
name,
[this] { collectMetrics(); },
collector)
, name_(name)
, targetSize_(size)
, targetAge_(expiration)
@@ -500,7 +506,8 @@ template <
template <class ReturnType>
inline auto
TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash, KeyEqual, Mutex>::
insert(key_type const& key, T const& value) -> std::enable_if_t<!IsKeyCache, ReturnType>
insert(key_type const& key, T const& value) -> ReturnType
requires(!IsKeyCache)
{
static_assert(
std::is_same_v<std::shared_ptr<T>, SharedPointerType> ||
@@ -530,7 +537,8 @@ template <
template <class ReturnType>
inline auto
TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash, KeyEqual, Mutex>::
insert(key_type const& key) -> std::enable_if_t<IsKeyCache, ReturnType>
insert(key_type const& key) -> ReturnType
requires IsKeyCache
{
std::scoped_lock const lock(mutex_);
clock_type::time_point const now(clock_.now());
@@ -596,8 +604,42 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
std::vector<key_type> v;
{
std::scoped_lock const lock(mutex_);
v.reserve(cache_.size());
// Keep track of how many iterations are needed. Exit the loop if the number of retries gets
// absurd. (Note that if this somehow ever happens, one more allocation will be done under
// lock, which is undesirable, but really should be almost impossible.)
std::size_t allocationIterations = 0;
std::unique_lock lock(mutex_);
for (auto size = cache_.size(); v.capacity() < size && allocationIterations < 20;
size = cache_.size())
{
ScopeUnlock const unlock(lock);
if (allocationIterations > 0)
{
JLOG(journal_.info())
<< "getKeys(): Cache grew beyond allocated capacity after "
<< allocationIterations << " prior attempt(s). Have " << v.capacity()
<< ", need " << size << ". Retrying allocation";
}
// Allocate the current size plus a little extra, in case the cache grows while
// allocating. Each time another allocation is needed, the extra also gets bigger until
// it ultimately doubles the size + 1.
constexpr std::size_t baseShift = 5;
auto const bufferOffset = std::min(allocationIterations, std::size_t{baseShift});
auto const bufferShift = baseShift - bufferOffset;
size += (size >> bufferShift) + 1;
v.reserve(size);
++allocationIterations;
}
if (v.capacity() < cache_.size())
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::TaggedCache::getKeys(): failed to allocate sufficient capacity");
v.reserve(cache_.size());
// LCOV_EXCL_STOP
}
XRPL_ASSERT(lock.owns_lock(), "xrpl::TaggedCache::getKeys(): owns lock");
XRPL_ASSERT(
v.capacity() >= cache_.size(), "xrpl::TaggedCache::getKeys(): sufficient capacity");
for (auto const& _ : cache_)
v.push_back(_.first);
}

View File

@@ -5,15 +5,17 @@
namespace xrpl {
/** to_string() generalizes std::to_string to handle bools, chars, and strings.
It's also possible to provide implementation of to_string for a class
which needs a string implementation.
/**
* to_string() generalizes std::to_string to handle bools, chars, and strings.
*
* It's also possible to provide implementation of to_string for a class
* which needs a string implementation.
*/
template <class T>
std::enable_if_t<std::is_arithmetic_v<T>, std::string>
std::string
to_string(T t) // NOLINT(readability-identifier-naming)
requires(std::is_arithmetic_v<T>)
{
return std::to_string(t);
}

View File

@@ -7,12 +7,13 @@
namespace xrpl {
/** Tracks program uptime to seconds precision.
The timer caches the current time as a performance optimization.
This allows clients to query the current time thousands of times
per second.
*/
/**
* Tracks program uptime to seconds precision.
*
* The timer caches the current time as a performance optimization.
* This allows clients to query the current time thousands of times
* per second.
*/
class UptimeClock
{

View File

@@ -63,18 +63,19 @@ struct AlwaysFalseT : std::bool_constant<false>
} // namespace detail
/** Integers of any length that is a multiple of 32-bits
@note This class stores its values internally in big-endian
form and that internal representation is part of the
binary protocol of the XRP Ledger and cannot be changed
arbitrarily without causing breakage.
@tparam Bits The number of bits this integer should have; must
be at least 64 and a multiple of 32.
@tparam Tag An arbitrary type that functions as a tag and allows
the instantiation of "distinct" types that the same
number of bits.
/**
* Integers of any length that is a multiple of 32-bits
*
* @note This class stores its values internally in big-endian
* form and that internal representation is part of the
* binary protocol of the XRP Ledger and cannot be changed
* arbitrarily without causing breakage.
*
* @tparam Bits The number of bits this integer should have; must
* be at least 64 and a multiple of 32.
* @tparam Tag An arbitrary type that functions as a tag and allows
* the instantiation of "distinct" types that the same
* number of bits.
*/
template <std::size_t Bits, class Tag = void>
class BaseUInt
@@ -97,7 +98,7 @@ public:
//
static constexpr std::size_t kBytes = Bits / 8;
static_assert(sizeof(data_) == kBytes, "");
static_assert(sizeof(data_) == kBytes);
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
@@ -154,21 +155,23 @@ public:
return data() + kBytes;
}
/** Value hashing function.
The seed prevents crafted inputs from causing degenerate parent
containers.
*/
/**
* Value hashing function.
* The seed prevents crafted inputs from causing degenerate parent
* containers.
*/
using hasher = HardenedHash<>;
//--------------------------------------------------------------------------
private:
/** Construct from a raw pointer.
The buffer pointed to by `data` must be at least Bits/8 bytes.
@note the structure is used to disambiguate this from the std::uint64_t
constructor: something like base_uint(0) is ambiguous.
*/
/**
* Construct from a raw pointer.
* The buffer pointed to by `data` must be at least Bits/8 bytes.
*
* @note the structure is used to disambiguate this from the std::uint64_t
* constructor: something like base_uint(0) is ambiguous.
*/
// NIKB TODO Remove the need for this constructor.
struct VoidHelper
{
@@ -280,12 +283,11 @@ public:
{
}
template <
class Container,
class = std::enable_if_t<
detail::IsContiguousContainer<Container>::value &&
std::is_trivially_copyable_v<typename Container::value_type>>>
template <class Container>
explicit BaseUInt(Container const& c)
requires(
detail::IsContiguousContainer<Container>::value &&
std::is_trivially_copyable_v<typename Container::value_type>)
{
// Use AlwaysFalseT so the static_assert condition is dependent
// and only triggers when this constructor template is instantiated.
@@ -295,33 +297,38 @@ public:
"Use base_uint::fromRaw instead.");
}
template <
class Container,
class = std::enable_if_t<
detail::IsContiguousContainer<Container>::value &&
std::is_trivially_copyable_v<typename Container::value_type>>>
template <class Container>
static BaseUInt
fromRaw(Container const& c)
requires(
detail::IsContiguousContainer<Container>::value &&
std::is_trivially_copyable_v<typename Container::value_type>)
{
BaseUInt result;
XRPL_ASSERT(
c.size() * sizeof(typename Container::value_type) == size(),
"xrpl::BaseUInt::fromRaw(Container auto) : input size match");
std::memcpy(result.data_.data(), c.data(), size());
std::size_t const canCopy =
std::min(size(), c.size() * sizeof(typename Container::value_type));
std::memcpy(result.data_.data(), c.data(), canCopy);
return result;
}
template <class Container>
std::enable_if_t<
detail::IsContiguousContainer<Container>::value &&
std::is_trivially_copyable_v<typename Container::value_type>,
BaseUInt&>
BaseUInt&
operator=(Container const& c)
requires(
detail::IsContiguousContainer<Container>::value &&
std::is_trivially_copyable_v<typename Container::value_type>)
{
XRPL_ASSERT(
c.size() * sizeof(typename Container::value_type) == size(),
"xrpl::BaseUInt::operator=(Container auto) : input size match");
std::memcpy(data_.data(), c.data(), size());
std::size_t const canCopy =
std::min(size(), c.size() * sizeof(typename Container::value_type));
if (canCopy < size())
*this = beast::kZero;
std::memcpy(data_.data(), c.data(), canCopy);
return *this;
}
@@ -505,13 +512,14 @@ public:
h(a.data_.data(), sizeof(a.data_));
}
/** Parse a hex string into a base_uint
The input must be precisely `2 * bytes` hexadecimal characters
long, with one exception: the value '0'.
@param sv A null-terminated string of hexadecimal characters
@return true if the input was parsed properly; false otherwise.
/**
* Parse a hex string into a base_uint
*
* The input must be precisely `2 * bytes` hexadecimal characters
* long, with one exception: the value '0'.
*
* @param sv A null-terminated string of hexadecimal characters
* @return true if the input was parsed properly; false otherwise.
*/
[[nodiscard]] constexpr bool
parseHex(std::string_view sv)
@@ -597,7 +605,7 @@ template <std::size_t Bits, typename Tag>
[[nodiscard]] constexpr bool
operator==(BaseUInt<Bits, Tag> const& lhs, BaseUInt<Bits, Tag> const& rhs)
{
return (lhs <=> rhs) == 0;
return (lhs <=> rhs) == 0; // NOLINT(modernize-use-nullptr)
}
//------------------------------------------------------------------------------

View File

@@ -21,15 +21,16 @@ using days =
using weeks = std::chrono::duration<int, std::ratio_multiply<days::period, std::ratio<7>>>;
/** Clock for measuring the network time.
The epoch is January 1, 2000
epoch_offset
= date(2000-01-01) - date(1970-0-01)
= days(10957)
= seconds(946684800)
*/
/**
* Clock for measuring the network time.
*
* The epoch is January 1, 2000
*
* epoch_offset
* = date(2000-01-01) - date(1970-0-01)
* = days(10957)
* = seconds(946684800)
*/
static constexpr std::chrono::seconds kEpochOffset =
date::sys_days{date::year{2000} / 1 / 1} - date::sys_days{date::year{1970} / 1 / 1};
@@ -81,16 +82,21 @@ toStringIso(NetClock::time_point tp)
return toStringIso(date::sys_time<NetClock::duration>{tp.time_since_epoch() + kEpochOffset});
}
/** A clock for measuring elapsed time.
The epoch is unspecified.
*/
/**
* A clock for measuring elapsed time.
*
* The epoch is unspecified.
*/
using Stopwatch = beast::AbstractClock<std::chrono::steady_clock>;
/** A manual Stopwatch for unit tests. */
/**
* A manual Stopwatch for unit tests.
*/
using TestStopwatch = beast::ManualClock<std::chrono::steady_clock>;
/** Returns an instance of a wall clock. */
/**
* Returns an instance of a wall clock.
*/
inline Stopwatch&
stopwatch()
{

View File

@@ -1,54 +0,0 @@
#pragma once
#include <functional>
namespace xrpl {
#ifdef _MSC_VER
/*
* MSVC 2019 version 16.9.0 added [[nodiscard]] to the std comparison
* operator() functions. boost::bimap checks that the comparator is a
* BinaryFunction, in part by calling the function and ignoring the value.
* These two things don't play well together. These wrapper classes simply
* strip [[nodiscard]] from operator() for use in boost::bimap.
*
* See also:
* https://www.boost.org/doc/libs/1_75_0/libs/bimap/doc/html/boost_bimap/the_tutorial/controlling_collection_types.html
*/
template <class T = void>
struct less
{
using result_type = bool;
constexpr bool
operator()(T const& left, T const& right) const
{
return std::less<T>()(left, right);
}
};
template <class T = void>
struct equal_to
{
using result_type = bool;
constexpr bool
operator()(T const& left, T const& right) const
{
return std::equal_to<T>()(left, right);
}
};
#else
template <class T = void>
using less = std::less<T>;
template <class T = void>
using equal_to = std::equal_to<T>;
#endif
} // namespace xrpl

View File

@@ -15,20 +15,23 @@ namespace xrpl {
preconditions, postconditions, and invariants.
*/
/** Generates and logs a call stack */
/**
* Generates and logs a call stack
*/
void
logThrow(std::string const& title);
/** Rethrow the exception currently being handled.
When called from within a catch block, it will pass
control to the next matching exception handler, if any.
Otherwise, std::terminate will be called.
ASAN can't handle sudden jumps in control flow very well. This
function is marked as XRPL_NO_SANITIZE_ADDRESS to prevent it from
triggering false positives, since it throws.
*/
/**
* Rethrow the exception currently being handled.
*
* When called from within a catch block, it will pass
* control to the next matching exception handler, if any.
* Otherwise, std::terminate will be called.
*
* ASAN can't handle sudden jumps in control flow very well. This
* function is marked as XRPL_NO_SANITIZE_ADDRESS to prevent it from
* triggering false positives, since it throws.
*/
[[noreturn]] XRPL_NO_SANITIZE_ADDRESS inline void
rethrow()
{
@@ -56,7 +59,9 @@ Throw(Args&&... args)
throw std::move(e);
}
/** Called when faulty logic causes a broken invariant. */
/**
* Called when faulty logic causes a broken invariant.
*/
[[noreturn]] void
logicError(std::string const& how) noexcept;

View File

@@ -39,33 +39,33 @@ makeSeedPair() noexcept
/**
* Seed functor once per construction
A std compatible hash adapter that resists adversarial inputs.
For this to work, T must implement in its own namespace:
@code
template <class Hasher>
void
hash_append (Hasher& h, T const& t) noexcept
{
// hash_append each base and member that should
// participate in forming the hash
using beast::hash_append;
hash_append (h, static_cast<T::base1 const&>(t));
hash_append (h, static_cast<T::base2 const&>(t));
// ...
hash_append (h, t.member1);
hash_append (h, t.member2);
// ...
}
@endcode
Do not use any version of Murmur or CityHash for the Hasher
template parameter (the hashing algorithm). For details
see https://131002.net/siphash/#at
*/
*
* A std compatible hash adapter that resists adversarial inputs.
* For this to work, T must implement in its own namespace:
*
* @code
*
* template <class Hasher>
* void
* hash_append (Hasher& h, T const& t) noexcept
* {
* // hash_append each base and member that should
* // participate in forming the hash
* using beast::hash_append;
* hash_append (h, static_cast<T::base1 const&>(t));
* hash_append (h, static_cast<T::base2 const&>(t));
* // ...
* hash_append (h, t.member1);
* hash_append (h, t.member2);
* // ...
* }
*
* @endcode
*
* Do not use any version of Murmur or CityHash for the Hasher
* template parameter (the hashing algorithm). For details
* see https://131002.net/siphash/#at
*/
template <class HashAlgorithm = beast::Xxhasher>
class HardenedHash

View File

@@ -7,11 +7,15 @@
namespace xrpl {
/** Create a self-signed SSL context that allows anonymous Diffie Hellman. */
/**
* Create a self-signed SSL context that allows anonymous Diffie Hellman.
*/
std::shared_ptr<boost::asio::ssl::context>
makeSslContext(std::string const& cipherList);
/** Create an authenticated SSL context using the specified files. */
/**
* Create an authenticated SSL context using the specified files.
*/
std::shared_ptr<boost::asio::ssl::context>
makeSslContextAuthed(
std::string const& keyFile,

View File

@@ -7,16 +7,16 @@
namespace xrpl {
constexpr auto kMuldivMax = std::numeric_limits<std::uint64_t>::max();
/** Return value*mul/div accurately.
Computes the result of the multiplication and division in
a single step, avoiding overflow and retaining precision.
Throws:
None
Returns:
`std::optional`:
`std::nullopt` if the calculation overflows. Otherwise, `value * mul
/ div`.
*/
/**
* Return value*mul/div accurately.
*
* Computes the result of the multiplication and division in
* a single step, avoiding overflow and retaining precision.
*
* @throws None
* @return `std::nullopt` if the calculation overflows. Otherwise,
* `value * mul / div`.
*/
std::optional<std::uint64_t>
mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div);

View File

@@ -138,11 +138,8 @@ public:
{
}
ConstIterator(Iterator const& orig)
ConstIterator(Iterator const& orig) : map(orig.map), ait(orig.ait), mit(orig.mit)
{
map = orig.map;
ait = orig.ait;
mit = orig.mit;
}
const_reference
@@ -231,11 +228,11 @@ private:
public:
PartitionedUnorderedMap(std::optional<std::size_t> partitions = std::nullopt)
{
// Set partitions to the number of hardware threads if the parameter
// is either empty or set to 0.
partitions_ =
partitions && (*partitions != 0u) ? *partitions : std::thread::hardware_concurrency();
: partitions_(
partitions && (*partitions != 0u) ? *partitions : std::thread::hardware_concurrency())
{
map_.resize(partitions_);
XRPL_ASSERT(
partitions_,

View File

@@ -33,16 +33,17 @@ template <class Engine, class Result = typename Engine::result_type>
using is_engine = std::is_invocable_r<Result, Engine>;
} // namespace detail
/** Return the default random engine.
This engine is guaranteed to be deterministic, but by
default will be randomly seeded. It is NOT cryptographically
secure and MUST NOT be used to generate randomness that
will be used for keys, secure cookies, IVs, padding, etc.
Each thread gets its own instance of the engine which
will be randomly seeded.
*/
/**
* Return the default random engine.
*
* This engine is guaranteed to be deterministic, but by
* default will be randomly seeded. It is NOT cryptographically
* secure and MUST NOT be used to generate randomness that
* will be used for keys, secure cookies, IVs, padding, etc.
*
* Each thread gets its own instance of the engine which
* will be randomly seeded.
*/
inline beast::xor_shift_engine&
defaultPrng()
{
@@ -70,29 +71,31 @@ defaultPrng()
return kEngine;
}
/** Return a uniformly distributed random integer.
@param min The smallest value to return. If not specified
the value defaults to 0.
@param max The largest value to return. If not specified
the value defaults to the largest value that
can be represented.
The randomness is generated by the specified engine (or
the default engine if one is not specified). The result
is cryptographically secure only when the engine passed
into the function is cryptographically secure.
@note The range is always a closed interval, so calling
rand_int(-5, 15) can return any integer in the
closed interval [-5, 15]; similarly, calling
rand_int(7) can return any integer in the closed
interval [0, 7].
*/
/**
* Return a uniformly distributed random integer.
*
* @param min The smallest value to return. If not specified
* the value defaults to 0.
* @param max The largest value to return. If not specified
* the value defaults to the largest value that
* can be represented.
*
* The randomness is generated by the specified engine (or
* the default engine if one is not specified). The result
* is cryptographically secure only when the engine passed
* into the function is cryptographically secure.
*
* @note The range is always a closed interval, so calling
* rand_int(-5, 15) can return any integer in the
* closed interval [-5, 15]; similarly, calling
* rand_int(7) can return any integer in the closed
* interval [0, 7].
*/
/** @{ */
template <class Engine, class Integral>
std::enable_if_t<std::is_integral_v<Integral> && detail::is_engine<Engine>::value, Integral>
Integral
randInt(Engine& engine, Integral min, Integral max)
requires(std::is_integral_v<Integral> && detail::is_engine<Engine>::value)
{
XRPL_ASSERT(max > min, "xrpl::randInt : max over min inputs");
@@ -103,63 +106,73 @@ randInt(Engine& engine, Integral min, Integral max)
}
template <class Integral>
std::enable_if_t<std::is_integral_v<Integral>, Integral>
Integral
randInt(Integral min, Integral max)
requires(std::is_integral_v<Integral>)
{
return randInt(defaultPrng(), min, max);
}
template <class Engine, class Integral>
std::enable_if_t<std::is_integral_v<Integral> && detail::is_engine<Engine>::value, Integral>
Integral
randInt(Engine& engine, Integral max)
requires(std::is_integral_v<Integral> && detail::is_engine<Engine>::value)
{
return randInt(engine, Integral(0), max);
}
template <class Integral>
std::enable_if_t<std::is_integral_v<Integral>, Integral>
Integral
randInt(Integral max)
requires(std::is_integral_v<Integral>)
{
return randInt(defaultPrng(), max);
}
template <class Integral, class Engine>
std::enable_if_t<std::is_integral_v<Integral> && detail::is_engine<Engine>::value, Integral>
Integral
randInt(Engine& engine)
requires(std::is_integral_v<Integral> && detail::is_engine<Engine>::value)
{
return randInt(engine, std::numeric_limits<Integral>::max());
}
template <class Integral = int>
std::enable_if_t<std::is_integral_v<Integral>, Integral>
Integral
randInt()
requires(std::is_integral_v<Integral>)
{
return randInt(defaultPrng(), std::numeric_limits<Integral>::max());
}
/** @} */
/** Return a random byte */
/**
* Return a random byte
*/
/** @{ */
template <class Byte, class Engine>
std::enable_if_t<
(std::is_same_v<Byte, unsigned char> || std::is_same_v<Byte, std::uint8_t>) &&
detail::is_engine<Engine>::value,
Byte>
Byte
randByte(Engine& engine)
requires(
(std::is_same_v<Byte, unsigned char> || std::is_same_v<Byte, std::uint8_t>) &&
detail::is_engine<Engine>::value)
{
return static_cast<Byte>(randInt<Engine, std::uint32_t>(
engine, std::numeric_limits<Byte>::min(), std::numeric_limits<Byte>::max()));
}
template <class Byte = std::uint8_t>
std::enable_if_t<(std::is_same_v<Byte, unsigned char> || std::is_same_v<Byte, std::uint8_t>), Byte>
Byte
randByte()
requires(std::is_same_v<Byte, unsigned char> || std::is_same_v<Byte, std::uint8_t>)
{
return randByte<Byte>(defaultPrng());
}
/** @} */
/** Return a random boolean value */
/**
* Return a random boolean value
*/
/** @{ */
template <class Engine>
inline bool

View File

@@ -1,5 +1,7 @@
#pragma once
#include <xrpl/beast/utility/instrumentation.h> // IWYU pragma: keep
#include <type_traits>
namespace xrpl {
@@ -15,8 +17,9 @@ concept SafeToCast = (std::is_integral_v<Src> && std::is_integral_v<Dest>) &&
: sizeof(Dest) >= sizeof(Src));
template <class Dest, class Src>
constexpr std::enable_if_t<std::is_integral_v<Dest> && std::is_integral_v<Src>, Dest>
constexpr Dest
safeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
{
static_assert(
std::is_signed_v<Dest> || std::is_unsigned_v<Src>, "Cannot cast signed to unsigned");
@@ -28,15 +31,17 @@ safeCast(Src s) noexcept
}
template <class Dest, class Src>
constexpr std::enable_if_t<std::is_enum_v<Dest> && std::is_integral_v<Src>, Dest>
constexpr Dest
safeCast(Src s) noexcept
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
{
return static_cast<Dest>(safeCast<std::underlying_type_t<Dest>>(s));
}
template <class Dest, class Src>
constexpr std::enable_if_t<std::is_integral_v<Dest> && std::is_enum_v<Src>, Dest>
constexpr Dest
safeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
{
return safeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
}
@@ -46,8 +51,9 @@ safeCast(Src s) noexcept
// underlying types become safe, it can be converted to a safe_cast.
template <class Dest, class Src>
constexpr std::enable_if_t<std::is_integral_v<Dest> && std::is_integral_v<Src>, Dest>
constexpr Dest
unsafeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
{
static_assert(
!SafeToCast<Src, Dest>,
@@ -57,15 +63,17 @@ unsafeCast(Src s) noexcept
}
template <class Dest, class Src>
constexpr std::enable_if_t<std::is_enum_v<Dest> && std::is_integral_v<Src>, Dest>
constexpr Dest
unsafeCast(Src s) noexcept
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
{
return static_cast<Dest>(unsafeCast<std::underlying_type_t<Dest>>(s));
}
template <class Dest, class Src>
constexpr std::enable_if_t<std::is_integral_v<Dest> && std::is_enum_v<Src>, Dest>
constexpr Dest
unsafeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
{
return unsafeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
}

View File

@@ -46,11 +46,9 @@ public:
operator=(ScopeExit&&) = delete;
template <class EFP>
explicit ScopeExit(
EFP&& f,
std::enable_if_t<
!std::is_same_v<std::remove_cv_t<EFP>, ScopeExit> &&
std::is_constructible_v<EF, EFP>>* = 0) noexcept
explicit ScopeExit(EFP&& f) noexcept
requires(
!std::is_same_v<std::remove_cv_t<EFP>, ScopeExit> && std::is_constructible_v<EF, EFP>)
: exitFunction_{std::forward<EFP>(f)}
{
static_assert(std::is_nothrow_constructible_v<EF, decltype(std::forward<EFP>(f))>);
@@ -93,11 +91,9 @@ public:
operator=(ScopeFail&&) = delete;
template <class EFP>
explicit ScopeFail(
EFP&& f,
std::enable_if_t<
!std::is_same_v<std::remove_cv_t<EFP>, ScopeFail> &&
std::is_constructible_v<EF, EFP>>* = 0) noexcept
explicit ScopeFail(EFP&& f) noexcept
requires(
!std::is_same_v<std::remove_cv_t<EFP>, ScopeFail> && std::is_constructible_v<EF, EFP>)
: exitFunction_{std::forward<EFP>(f)}
{
static_assert(std::is_nothrow_constructible_v<EF, decltype(std::forward<EFP>(f))>);
@@ -140,12 +136,11 @@ public:
operator=(ScopeSuccess&&) = delete;
template <class EFP>
explicit ScopeSuccess(
EFP&& f,
std::enable_if_t<
explicit ScopeSuccess(EFP&& f) noexcept(
std::is_nothrow_constructible_v<EF, EFP> || std::is_nothrow_constructible_v<EF, EFP&>)
requires(
!std::is_same_v<std::remove_cv_t<EFP>, ScopeSuccess> &&
std::is_constructible_v<EF, EFP>>* =
0) noexcept(std::is_nothrow_constructible_v<EF, EFP> || std::is_nothrow_constructible_v<EF, EFP&>)
std::is_constructible_v<EF, EFP>)
: exitFunction_{std::forward<EFP>(f)}
{
}
@@ -161,41 +156,41 @@ template <class EF>
ScopeSuccess(EF) -> ScopeSuccess<EF>;
/**
Automatically unlocks and re-locks a unique_lock object.
This is the reverse of a std::unique_lock object - instead of locking the
mutex for the lifetime of this object, it unlocks it.
Make sure you don't try to unlock mutexes that aren't actually locked!
This is essentially a less-versatile boost::reverse_lock.
e.g. @code
std::mutex mut;
for (;;)
{
std::unique_lock myScopedLock{mut};
// mut is now locked
... do some stuff with it locked ..
while (xyz)
{
... do some stuff with it locked ..
scope_unlock unlocker{myScopedLock};
// mut is now unlocked for the remainder of this block,
// and re-locked at the end.
...do some stuff with it unlocked ...
} // mut gets locked here.
} // mut gets unlocked here
@endcode
*/
* Automatically unlocks and re-locks a unique_lock object.
*
* This is the reverse of a std::unique_lock object - instead of locking the
* mutex for the lifetime of this object, it unlocks it.
*
* Make sure you don't try to unlock mutexes that aren't actually locked!
*
* This is essentially a less-versatile boost::reverse_lock.
*
* e.g. @code
*
* std::mutex mut;
*
* for (;;)
* {
* std::unique_lock myScopedLock{mut};
* // mut is now locked
*
* ... do some stuff with it locked ..
*
* while (xyz)
* {
* ... do some stuff with it locked ..
*
* scope_unlock unlocker{myScopedLock};
*
* // mut is now unlocked for the remainder of this block,
* // and re-locked at the end.
*
* ...do some stuff with it unlocked ...
* } // mut gets locked here.
*
* } // mut gets unlocked here
* @endcode
*/
template <class Mutex>
class ScopeUnlock

View File

@@ -15,15 +15,16 @@
namespace xrpl {
namespace detail {
/** Inform the processor that we are in a tight spin-wait loop.
Spinlocks caught in tight loops can result in the processor's pipeline
filling up with comparison operations, resulting in a misprediction at
the time the lock is finally acquired, necessitating pipeline flushing
which is ridiculously expensive and results in very high latency.
This function instructs the processor to "pause" for some architecture
specific amount of time, to prevent this.
/**
* Inform the processor that we are in a tight spin-wait loop.
*
* Spinlocks caught in tight loops can result in the processor's pipeline
* filling up with comparison operations, resulting in a misprediction at
* the time the lock is finally acquired, necessitating pipeline flushing
* which is ridiculously expensive and results in very high latency.
*
* This function instructs the processor to "pause" for some architecture
* specific amount of time, to prevent this.
*/
inline void
spinPause() noexcept
@@ -38,37 +39,39 @@ spinPause() noexcept
} // namespace detail
/** @{ */
/** Classes to handle arrays of spinlocks packed into a single atomic integer:
Packed spinlocks allow for tremendously space-efficient lock-sharding
but they come at a cost.
First, the implementation is necessarily low-level and uses advanced
features like memory ordering and highly platform-specific tricks to
maximize performance. This imposes a significant and ongoing cost to
developers.
Second, and perhaps most important, is that the packing of multiple
locks into a single integer which, albeit space-efficient, also has
performance implications stemming from data dependencies, increased
cache-coherency traffic between processors and heavier loads on the
processor's load/store units.
To be sure, these locks can have advantages but they are definitely
not general purpose locks and should not be thought of or used that
way. The use cases for them are likely few and far between; without
a compelling reason to use them, backed by profiling data, it might
be best to use one of the standard locking primitives instead. Note
that in most common platforms, `std::mutex` is so heavily optimized
that it can, usually, outperform spinlocks.
@tparam T An unsigned integral type (e.g. std::uint16_t)
/**
* Classes to handle arrays of spinlocks packed into a single atomic integer:
*
* Packed spinlocks allow for tremendously space-efficient lock-sharding
* but they come at a cost.
*
* First, the implementation is necessarily low-level and uses advanced
* features like memory ordering and highly platform-specific tricks to
* maximize performance. This imposes a significant and ongoing cost to
* developers.
*
* Second, and perhaps most important, is that the packing of multiple
* locks into a single integer which, albeit space-efficient, also has
* performance implications stemming from data dependencies, increased
* cache-coherency traffic between processors and heavier loads on the
* processor's load/store units.
*
* To be sure, these locks can have advantages but they are definitely
* not general purpose locks and should not be thought of or used that
* way. The use cases for them are likely few and far between; without
* a compelling reason to use them, backed by profiling data, it might
* be best to use one of the standard locking primitives instead. Note
* that in most common platforms, `std::mutex` is so heavily optimized
* that it can, usually, outperform spinlocks.
*
* @tparam T An unsigned integral type (e.g. std::uint16_t)
*/
/** A class that grabs a single packed spinlock from an atomic integer.
This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/Lockable
/**
* A class that grabs a single packed spinlock from an atomic integer.
*
* This class meets the requirements of Lockable:
* https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <class T>
class PackedSpinlock
@@ -91,13 +94,14 @@ public:
PackedSpinlock&
operator=(PackedSpinlock const&) = delete;
/** A single spinlock packed inside the specified atomic
@param lock The atomic integer inside which the spinlock is packed.
@param index The index of the spinlock this object acquires.
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
/**
* A single spinlock packed inside the specified atomic
*
* @param lock The atomic integer inside which the spinlock is packed.
* @param index The index of the spinlock this object acquires.
*
* @note For performance reasons, you should strive to have `lock` be
* on a cacheline by itself.
*/
PackedSpinlock(std::atomic<T>& lock, int index) : bits_(lock), mask_(static_cast<T>(1) << index)
{
@@ -133,17 +137,18 @@ public:
}
};
/** A spinlock implemented on top of an atomic integer.
@note Using `packed_spinlock` and `spinlock` against the same underlying
atomic integer can result in `spinlock` not being able to actually
acquire the lock during periods of high contention, because of how
the two locks operate: `spinlock` will spin trying to grab all the
bits at once, whereas any given `packed_spinlock` will only try to
grab one bit at a time. Caveat emptor.
This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/Lockable
/**
* A spinlock implemented on top of an atomic integer.
*
* @note Using `packed_spinlock` and `spinlock` against the same underlying
* atomic integer can result in `spinlock` not being able to actually
* acquire the lock during periods of high contention, because of how
* the two locks operate: `spinlock` will spin trying to grab all the
* bits at once, whereas any given `packed_spinlock` will only try to
* grab one bit at a time. Caveat emptor.
*
* This class meets the requirements of Lockable:
* https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <class T>
class Spinlock
@@ -159,12 +164,13 @@ public:
Spinlock&
operator=(Spinlock const&) = delete;
/** Grabs the
@param lock The atomic integer to spin against.
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
/**
* Grabs the
*
* @param lock The atomic integer to spin against.
*
* @note For performance reasons, you should strive to have `lock` be
* on a cacheline by itself.
*/
Spinlock(std::atomic<T>& lock) : lock_(lock)
{

View File

@@ -12,17 +12,18 @@
namespace xrpl {
/** A type-safe wrap around standard integral types
The tag is used to implement type safety, catching mismatched types at
compile time. Multiple instantiations wrapping the same underlying integral
type are distinct types (distinguished by tag) and will not interoperate. A
tagged_integer supports all the usual assignment, arithmetic, comparison and
shifting operations defined for the underlying type
The tag is not meant as a unit, which would require restricting the set of
allowed arithmetic operations.
*/
/**
* A type-safe wrap around standard integral types
*
* The tag is used to implement type safety, catching mismatched types at
* compile time. Multiple instantiations wrapping the same underlying integral
* type are distinct types (distinguished by tag) and will not interoperate. A
* tagged_integer supports all the usual assignment, arithmetic, comparison and
* shifting operations defined for the underlying type
*
* The tag is not meant as a unit, which would require restricting the set of
* allowed arithmetic operations.
*/
template <class Int, class Tag>
class TaggedInteger : boost::totally_ordered<
TaggedInteger<Int, Tag>,
@@ -43,10 +44,10 @@ public:
TaggedInteger() = default;
template <
class OtherInt,
class = std::enable_if_t<std::is_integral_v<OtherInt> && sizeof(OtherInt) <= sizeof(Int)>>
explicit constexpr TaggedInteger(OtherInt value) noexcept : value_(value)
template <class OtherInt>
explicit constexpr TaggedInteger(OtherInt value) noexcept
requires(std::is_integral_v<OtherInt> && sizeof(OtherInt) <= sizeof(Int))
: value_(value)
{
static_assert(sizeof(TaggedInteger) == sizeof(Int), "tagged_integer is adding padding");
}

View File

@@ -14,7 +14,9 @@
namespace beast {
/** Measures handler latency on an io_context queue. */
/**
* Measures handler latency on an io_context queue.
*/
template <class Clock>
class IOLatencyProbe
{
@@ -42,7 +44,9 @@ public:
cancel(lock, true);
}
/** Return the io_context associated with the latency probe. */
/**
* Return the io_context associated with the latency probe.
*/
/** @{ */
boost::asio::io_context&
getIoContext()
@@ -57,9 +61,10 @@ public:
}
/** @} */
/** Cancel all pending i/o.
Any handlers which have already been queued will still be called.
*/
/**
* Cancel all pending i/o.
* Any handlers which have already been queued will still be called.
*/
/** @{ */
void
cancel()
@@ -76,10 +81,11 @@ public:
}
/** @} */
/** Measure one sample of i/o latency.
Handler will be called with this signature:
void Handler (Duration d);
*/
/**
* Measure one sample of i/o latency.
* Handler will be called with this signature:
* void Handler (Duration d);
*/
template <class Handler>
void
sampleOne(Handler&& handler)
@@ -91,10 +97,11 @@ public:
ios_, SampleOp<Handler>(std::forward<Handler>(handler), Clock::now(), false, this));
}
/** Initiate continuous i/o latency sampling.
Handler will be called with this signature:
void Handler (std::chrono::milliseconds);
*/
/**
* Initiate continuous i/o latency sampling.
* Handler will be called with this signature:
* void Handler (std::chrono::milliseconds);
*/
template <class Handler>
void
sample(Handler&& handler)

View File

@@ -2,34 +2,35 @@
namespace beast {
/** Abstract interface to a clock.
This makes now() a member function instead of a static member, so
an instance of the class can be dependency injected, facilitating
unit tests where time may be controlled.
An abstract_clock inherits all the nested types of the Clock
template parameter.
Example:
@code
struct Implementation
{
using clock_type = abstract_clock <std::chrono::steady_clock>;
clock_type& clock_;
explicit Implementation (clock_type& clock)
: clock_(clock)
{
}
};
@endcode
@tparam Clock A type meeting these requirements:
http://en.cppreference.com/w/cpp/concept/Clock
*/
/**
* Abstract interface to a clock.
*
* This makes now() a member function instead of a static member, so
* an instance of the class can be dependency injected, facilitating
* unit tests where time may be controlled.
*
* An abstract_clock inherits all the nested types of the Clock
* template parameter.
*
* Example:
*
* @code
*
* struct Implementation
* {
* using clock_type = abstract_clock <std::chrono::steady_clock>;
* clock_type& clock_;
* explicit Implementation (clock_type& clock)
* : clock_(clock)
* {
* }
* };
*
* @endcode
*
* @tparam Clock A type meeting these requirements:
* http://en.cppreference.com/w/cpp/concept/Clock
*/
template <class Clock>
class AbstractClock
{
@@ -46,7 +47,9 @@ public:
AbstractClock() = default;
AbstractClock(AbstractClock const&) = default;
/** Returns the current time. */
/**
* Returns the current time.
*/
[[nodiscard]] virtual time_point
now() const = 0;
};
@@ -74,11 +77,12 @@ struct AbstractClockWrapper : public AbstractClock<Facade>
//------------------------------------------------------------------------------
/** Returns a global instance of an abstract clock.
@tparam Facade A type meeting these requirements:
http://en.cppreference.com/w/cpp/concept/Clock
@tparam Clock The actual concrete clock to use.
*/
/**
* Returns a global instance of an abstract clock.
* @tparam Facade A type meeting these requirements:
* http://en.cppreference.com/w/cpp/concept/Clock
* @tparam Clock The actual concrete clock to use.
*/
template <class Facade, class Clock = Facade>
AbstractClock<Facade>&
getAbstractClock()

View File

@@ -4,15 +4,16 @@
namespace beast {
/** A clock whose minimum resolution is one second.
The purpose of this class is to optimize the performance of the now()
member function call. It uses a dedicated thread that wakes up at least
once per second to sample the requested trivial clock.
@tparam Clock A type meeting these requirements:
http://en.cppreference.com/w/cpp/concept/Clock
*/
/**
* A clock whose minimum resolution is one second.
*
* The purpose of this class is to optimize the performance of the now()
* member function call. It uses a dedicated thread that wakes up at least
* once per second to sample the requested trivial clock.
*
* @tparam Clock A type meeting these requirements:
* http://en.cppreference.com/w/cpp/concept/Clock
*/
class BasicSecondsClock
{
public:

Some files were not shown because too many files have changed in this diff Show More