Compare commits

...

8 Commits

Author SHA1 Message Date
Bart
599a651e9e Use the correct workflow 2026-07-09 09:46:47 -07:00
Bart
dd425ffc6b ci: Do not run conflict checker when label is applied 2026-07-09 09:39:00 -07: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
43 changed files with 297 additions and 269 deletions

View File

@@ -9,37 +9,24 @@ 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,
@@ -63,13 +50,7 @@ Checks: "-*,
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,

1
.envrc Normal file
View File

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

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

@@ -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

40
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,8 +31,8 @@ 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.
@@ -40,12 +40,12 @@ The first time you run this command, it will take a few minutes to download and
- **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)).
- **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 +62,9 @@ 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`.
The `.#gcc` and `.#clang` shells provide the same GCC and Clang versions used in CI
(pinned in [`nix/packages.nix`](../../nix/packages.nix)).
Use `nix flake show` to see all the available development shells.
Use `nix develop .#no-compiler` to use the compiler from your system.
@@ -70,11 +72,11 @@ Use `nix develop .#no-compiler` to use the compiler from your system.
### Example Usage
```bash
# Use GCC 14
nix develop .#gcc14
# Use GCC (same version as CI)
nix develop .#gcc
# Use Clang 19
nix develop .#clang19
# Use Clang (same version as CI)
nix develop .#clang
# Use default for your platform
nix develop
@@ -112,7 +114,15 @@ Once inside the Nix development shell, follow the standard [build instructions](
[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

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

@@ -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

@@ -359,6 +359,7 @@ private:
deleteElement(Element const* p)
{
ElementAllocatorTraits::destroy(config_.alloc(), p);
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
ElementAllocatorTraits::deallocate(config_.alloc(), const_cast<Element*>(p), 1);
}

View File

@@ -528,6 +528,7 @@ private:
deleteElement(Element const* p)
{
ElementAllocatorTraits::destroy(config_.alloc(), p);
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
ElementAllocatorTraits::deallocate(config_.alloc(), const_cast<Element*>(p), 1);
}

View File

@@ -23,6 +23,7 @@ typeName()
if (auto s = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, nullptr))
{
name = s;
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
std::free(s);
}
#endif

View File

@@ -34,11 +34,11 @@ public:
createObject();
private:
bool success_;
bool success_{false};
void const* key_;
NodeObjectType objectType_;
unsigned char const* objectData_;
NodeObjectType objectType_{NodeObjectType::Unknown};
unsigned char const* objectData_{nullptr};
int dataBytes_;
};

View File

@@ -240,6 +240,9 @@ private:
inline STPathElement::STPathElement() : type_(TypeNone), isOffer_(true)
{
// hashValue_ is derived from the whole object, so it is computed in the body
// once every other member is initialized (as in the other constructors).
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
hashValue_ = getHash(*this);
}
@@ -315,6 +318,9 @@ inline STPathElement::STPathElement(
assetID_.visit(
[&](Currency const&) { type_ = type_ & (~Type::TypeMpt); },
[&](MPTID const&) { type_ = type_ & (~Type::TypeCurrency); });
// hashValue_ must be computed after type_ is adjusted above, so this cannot
// be a member initializer.
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
hashValue_ = getHash(*this);
}

View File

@@ -50,14 +50,13 @@ public:
STVar&
operator=(STVar&& rhs);
STVar(STBase&& t) // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved)
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
STVar(STBase&& t) : p_(t.move(kMaxSize, &d_))
{
p_ = t.move(kMaxSize, &d_);
}
STVar(STBase const& t)
STVar(STBase const& t) : p_(t.copy(kMaxSize, &d_))
{
p_ = t.copy(kMaxSize, &d_);
}
STVar(DefaultObjectT, SField const& name);

View File

@@ -138,6 +138,7 @@ intrusive_ptr_release(SHAMapItem const* x)
// If the slabber doesn't claim this pointer, it was allocated
// manually, so we free it manually.
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
if (!detail::gSlabber.deallocate(const_cast<std::uint8_t*>(p)))
delete[] p;
}

View File

@@ -507,6 +507,9 @@ TaggedPointer::operator=(TaggedPointer&& other)
[[nodiscard]] inline std::pair<std::uint8_t, void*>
TaggedPointer::decode() const
{
// tp_ packs a raw pointer together with the tag bits; recovering the
// pointer inherently requires an integer-to-pointer cast.
// NOLINTNEXTLINE(performance-no-int-to-ptr)
return {tp_ & kTagMask, reinterpret_cast<void*>(tp_ & kPtrMask)};
}
@@ -535,6 +538,9 @@ TaggedPointer::getHashesAndChildren() const
[[nodiscard]] inline SHAMapHash*
TaggedPointer::getHashes() const
{
// tp_ packs a raw pointer together with the tag bits; recovering the
// pointer inherently requires an integer-to-pointer cast.
// NOLINTNEXTLINE(performance-no-int-to-ptr)
return reinterpret_cast<SHAMapHash*>(tp_ & kPtrMask);
};

View File

@@ -92,6 +92,7 @@ public:
[[nodiscard]] TOffer<TIn, TOut>&
tip() const
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
return const_cast<TOfferStreamBase*>(this)->offer_;
}

View File

@@ -4,14 +4,16 @@
...
}:
let
inherit (import ./packages.nix { inherit pkgs; }) commonPackages;
inherit (pkgs) lib;
inherit (import ./packages.nix { inherit pkgs; })
commonPackages
gccPackage
llvmPackages
llvmVersion
;
# Underlying compiler toolchains to wrap. Bump these in one place to
# roll the whole environment forward.
customGccPackage = pkgs.gcc15;
customLlvmPackages = pkgs.llvmPackages_22;
customClangMajor = lib.versions.major (lib.getVersion customLlvmPackages.clang-unwrapped);
# Underlying compiler toolchains to wrap (versions pinned in packages.nix).
customGccPackage = gccPackage;
customLlvmPackages = llvmPackages;
# binutils wrapped to emit binaries that reference the custom glibc
# (dynamic linker path, library search path, RPATH).
@@ -90,7 +92,7 @@ let
extraBuildCommands = ''
rsrc="$out/resource-root"
mkdir "$rsrc"
ln -s "${customLlvmPackages.clang-unwrapped.lib}/lib/clang/${customClangMajor}/include" "$rsrc/include"
ln -s "${customLlvmPackages.clang-unwrapped.lib}/lib/clang/${toString llvmVersion}/include" "$rsrc/include"
ln -s "${customCompilerRt.out}/lib" "$rsrc/lib"
ln -s "${customCompilerRt.out}/share" "$rsrc/share" || true
echo "-resource-dir=$rsrc" >> $out/nix-support/cc-cflags

View File

@@ -1,127 +1,57 @@
{ pkgs, ... }:
let
inherit (import ./packages.nix { inherit pkgs; }) commonPackages;
inherit (import ./packages.nix { inherit pkgs; })
commonPackages
gccVersion
llvmPackages
;
# Supported compiler versions
gccVersion = pkgs.lib.range 13 15;
clangVersions = pkgs.lib.range 18 21;
# Plain nixpkgs stdenvs — no custom glibc, unlike ci-env.nix.
gccStdenv = pkgs."gcc${toString gccVersion}Stdenv";
clangStdenv = llvmPackages.stdenv;
defaultCompiler = if pkgs.stdenv.isDarwin then "apple-clang" else "gcc";
defaultGccVersion = pkgs.lib.last gccVersion;
defaultClangVersion = pkgs.lib.last clangVersions;
strToCompilerEnv =
compiler: version:
(
if compiler == "gcc" then
let
gccPkg = pkgs."gcc${toString version}Stdenv" or null;
in
if gccPkg != null && builtins.elem version gccVersion then
gccPkg
else
throw "Invalid GCC version: ${toString version}. Must be one of: ${toString gccVersion}"
else if compiler == "clang" then
let
clangPkg = pkgs."llvmPackages_${toString version}".stdenv or null;
in
if clangPkg != null && builtins.elem version clangVersions then
clangPkg
else
throw "Invalid Clang version: ${toString version}. Must be one of: ${toString clangVersions}"
else if compiler == "apple-clang" || compiler == "none" then
pkgs.stdenvNoCC
else
throw "Invalid compiler: ${compiler}. Must be one of: gcc, clang, apple-clang, none"
);
# Helper function to create a shell with a specific compiler
# compilerName is the command used to print the version, or null for none.
makeShell =
{
compiler ? defaultCompiler,
version ? (
if compiler == "gcc" then
defaultGccVersion
else if compiler == "clang" then
defaultClangVersion
else
null
),
stdenv,
compilerName,
}:
let
compilerStdEnv = strToCompilerEnv compiler version;
compilerName =
if compiler == "apple-clang" then
"clang"
else if compiler == "none" then
null
else
compiler;
gccOnMacWarning =
if pkgs.stdenv.isDarwin && compiler == "gcc" then
''
echo "WARNING: Using GCC on macOS with Conan may not work."
echo " Consider using 'nix develop .#clang' or the default shell instead."
echo ""
''
else
"";
compilerVersion =
if compilerName != null then
if compilerName == null then
''echo "No compiler specified - using system compiler"''
else
''
echo "Compiler: "
${compilerName} --version
''
else
''
echo "No compiler specified - using system compiler"
'';
shellAttrs = {
packages = commonPackages;
shellHook = ''
echo "Welcome to xrpld development shell";
${gccOnMacWarning}${compilerVersion}
'';
};
in
pkgs.mkShell.override { stdenv = compilerStdEnv; } shellAttrs;
# Generate shells for each compiler version
gccShells = builtins.listToAttrs (
map (version: {
name = "gcc${toString version}";
value = makeShell {
compiler = "gcc";
version = version;
};
}) gccVersion
);
clangShells = builtins.listToAttrs (
map (version: {
name = "clang${toString version}";
value = makeShell {
compiler = "clang";
version = version;
};
}) clangVersions
);
(pkgs.mkShell.override { inherit stdenv; }) {
packages = commonPackages;
shellHook = ''
echo "Welcome to xrpld development shell";
${compilerVersion}
'';
};
in
gccShells
// clangShells
// {
# Default shells
default = makeShell { };
gcc = makeShell { compiler = "gcc"; };
clang = makeShell { compiler = "clang"; };
rec {
# macOS: Nix Clang. Linux: Nix GCC.
default = if pkgs.stdenv.isDarwin then clang else gcc;
# No compiler
no-compiler = makeShell { compiler = "none"; };
apple-clang = makeShell { compiler = "apple-clang"; };
gcc = makeShell {
stdenv = gccStdenv;
compilerName = "gcc";
};
clang = makeShell {
stdenv = clangStdenv;
compilerName = "clang";
};
# Nix provides no compiler; use the one from your system (e.g. Apple Clang).
no-compiler = makeShell {
stdenv = pkgs.stdenvNoCC;
compilerName = null;
};
apple-clang = no-compiler;
}

View File

@@ -1,15 +1,33 @@
{ pkgs }:
let
# Compiler versions used across the dev shell and the CI environment.
gccVersion = 15;
llvmVersion = 22;
gccPackage = pkgs."gcc${toString gccVersion}";
llvmPackages = pkgs."llvmPackages_${toString llvmVersion}";
# Bound explicitly so it tracks llvmPackages above, not the `with pkgs` default.
clangTools = llvmPackages.clang-tools;
# In LLVM 22, run-clang-tidy.py moved from share/clang/ to bin/, so nixpkgs
# clang-tools no longer links it. Wrap it manually.
runClangTidy = pkgs.writeShellScriptBin "run-clang-tidy" ''
exec ${pkgs.python3}/bin/python3 ${pkgs.llvmPackages_22.clang-unwrapped}/bin/run-clang-tidy "$@"
exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@"
'';
in
{
inherit
gccVersion
llvmVersion
gccPackage
llvmPackages
;
commonPackages = with pkgs; [
ccache
clangbuildanalyzer
clangTools
cmake
conan
curlMinimal # needed for codecov/codecov-action
@@ -23,7 +41,6 @@ in
gnumake
gnupg # needed for signing commits & codecov/codecov-action
graphviz
llvmPackages_22.clang-tools
less # needed for git diff
mold
nettools # provides netstat, used to debug failures in CI

View File

@@ -230,9 +230,8 @@ Writer::~Writer()
impl_->finishAll();
}
Writer::Writer(Writer&& w) noexcept
Writer::Writer(Writer&& w) noexcept : impl_(std::move(w.impl_))
{
impl_ = std::move(w.impl_);
}
Writer&

View File

@@ -5,12 +5,13 @@
#include <algorithm>
#include <cctype>
#include <charconv>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <istream>
#include <stdexcept>
#include <string>
#include <system_error>
namespace json {
// Implementation of class Reader
@@ -605,34 +606,17 @@ bool
Reader::decodeDouble(Token& token)
{
double value = 0;
int const bufferSize = 32;
int count = 0;
int const length = int(token.end - token.start);
// Sanity check to avoid buffer overflow exploits.
if (length < 0)
{
return addError("Unable to parse token length", token);
}
// Avoid using a string constant for the format control string given to
// sscanf, as this can cause hard to debug crashes on OS X. See here for
// more info:
//
// http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html
char format[] = "%lf";
if (length <= bufferSize)
{
Char buffer[bufferSize + 1];
memcpy(buffer, token.start, length);
buffer[length] = 0;
count = sscanf(buffer, format, &value);
}
else
{
std::string const buffer(token.start, token.end);
count = sscanf(buffer.c_str(), format, &value);
}
if (count != 1)
auto const [ptr, ec] = std::from_chars(token.start, token.end, value);
// Reject anything from_chars could not turn into a finite double:
// - ec != std::errc{}: no valid conversion, or an out-of-range magnitude
// (e.g. 1e400).
// - ptr != token.end: readNumber() is permissive about which characters
// it collects into a token (it will, for example, keep a '+' mid-token),
// but from_chars() will stop at the first character it cannot parse.
if (ec != std::errc{} || ptr != token.end)
return addError("'" + std::string(token.start, token.end) + "' is not a number.", token);
currentValue() = value;
return true;
}

View File

@@ -48,6 +48,7 @@ public:
if (length == kUnknown)
length = (value != nullptr) ? (unsigned int)strlen(value) : 0;
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
char* newString = static_cast<char*>(malloc(length + 1));
if (value != nullptr)
memcpy(newString, value, length);
@@ -59,7 +60,10 @@ public:
releaseStringValue(char* value) override
{
if (value != nullptr)
{
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
free(value);
}
}
};
@@ -120,7 +124,10 @@ Value::CZString::CZString(CZString const& other)
Value::CZString::~CZString()
{
if ((cstr_ != nullptr) && index_ == static_cast<int>(DuplicationPolicy::Duplicate))
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
valueAllocator()->releaseMemberName(const_cast<char*>(cstr_));
}
}
bool
@@ -241,6 +248,7 @@ Value::Value(std::string const& value) : type_(ValueType::String), allocated_(tr
Value::Value(StaticString const& value) : type_(ValueType::String)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
value_.stringVal = const_cast<char*>(value.cStr());
}

View File

@@ -4,13 +4,14 @@
#include <xrpl/json/json_forwards.h>
#include <xrpl/json/json_value.h>
#include <cstdio>
#include <charconv>
#include <cstring>
#include <iomanip>
#include <ios>
#include <ostream>
#include <sstream>
#include <string>
#include <system_error>
#include <utility>
namespace json {
@@ -76,19 +77,18 @@ valueToString(UInt value)
std::string
valueToString(double value)
{
// Allocate a buffer that is more than large enough to store the 16 digits
// of precision requested below.
// Format with 16 significant digits.
// We need not request the alternative representation that always has a
// decimal point because JSON doesn't distinguish the concepts of reals and integers.
// A double never needs more than 32 characters in this form,
// so to_chars cannot actually run out of room here.
char buffer[32];
// Print into the buffer. We need not request the alternative representation
// that always has a decimal point because JSON doesn't distinguish the
// concepts of reals and integers.
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005
// to avoid warning.
sprintf_s(buffer, sizeof(buffer), "%.16g", value);
#else
snprintf(buffer, sizeof(buffer), "%.16g", value);
#endif
return buffer;
auto const [ptr, ec] =
std::to_chars(buffer, buffer + sizeof(buffer), value, std::chars_format::general, 16);
XRPL_ASSERT(ec == std::errc{}, "json::valueToString(double) : conversion fits buffer");
if (ec != std::errc{})
return {};
return std::string(buffer, ptr);
}
std::string

View File

@@ -12,7 +12,7 @@
namespace xrpl::NodeStore {
DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes)
DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) : key_(key)
{
/* Data format:
@@ -23,10 +23,6 @@ DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes)
9...end The body of the object data
*/
success_ = false;
key_ = key;
objectType_ = NodeObjectType::Unknown;
objectData_ = nullptr;
dataBytes_ = std::max(0, valueBytes - 9);
// VFALCO NOTE What about bytes 4 through 7 inclusive?

View File

@@ -23,7 +23,7 @@ namespace {
//------------------------------------------------------------------------------
// clang-format off
// NOLINTNEXTLINE(readability-identifier-naming)
char const* const versionString = "3.3.0-b0"
char const* const versionString = "3.3.0-b1"
// clang-format on
;

View File

@@ -152,7 +152,12 @@ std::uint64_t
getQuality(uint256 const& uBase)
{
// VFALCO [base_uint] This assumes a certain storage format
return boost::endian::big_to_native(((std::uint64_t*)uBase.end())[-1]);
//
// Load the final 8 bytes as a big-endian integer. load_big_u64 reads
// through unaligned byte storage (via memcpy) and applies the endian
// conversion, avoiding the alignment/strict-aliasing UB of casting the
// unsigned char* returned by end() to a std::uint64_t*.
return boost::endian::load_big_u64(uBase.end() - 8);
}
uint256
@@ -278,8 +283,11 @@ quality(Keylet const& k, std::uint64_t q) noexcept
// for indexes.
uint256 x = k.key;
// FIXME This is ugly and we can and should do better...
((std::uint64_t*)x.end())[-1] = boost::endian::native_to_big(q);
// Store the quality as a big-endian integer in the final 8 bytes.
// store_big_u64 writes through unaligned byte storage (via memcpy) and
// applies the endian conversion, avoiding the alignment/strict-aliasing UB
// of casting the unsigned char* returned by end() to a std::uint64_t*.
boost::endian::store_big_u64(x.end() - 8, q);
return {ltDIR_NODE, x};
}

View File

@@ -21,11 +21,12 @@ SOTemplate::SOTemplate(
}
SOTemplate::SOTemplate(std::vector<SOElement> uniqueFields, std::vector<SOElement> commonFields)
: indices_(SField::getNumFields() + 1, -1) // Unmapped indices == -1
: elements_(std::move(uniqueFields))
, indices_(SField::getNumFields() + 1, -1) // Unmapped indices == -1
{
// Add all SOElements.
//
elements_ = std::move(uniqueFields);
std::ranges::move(commonFields, std::back_inserter(elements_));
// Validate and index elements_.

View File

@@ -69,6 +69,17 @@ toUnsigned(U2 value)
return static_cast<U1>(value);
}
static std::string
joinName(std::string const& jsonName, std::string const& fieldName)
{
std::string result;
result.reserve(jsonName.size() + 1 + fieldName.size());
result += jsonName;
result += '.';
result += fieldName;
return result;
}
// LCOV_EXCL_START
static inline std::string
makeName(std::string const& object, std::string const& field)
@@ -76,7 +87,7 @@ makeName(std::string const& object, std::string const& field)
if (field.empty())
return object;
return object + "." + field;
return joinName(object, field);
}
static inline json::Value
@@ -1036,8 +1047,8 @@ parseObject(
try
{
auto ret =
parseObject(jsonName + "." + fieldName, value, field, depth + 1, error);
auto ret = parseObject(
joinName(jsonName, fieldName), value, field, depth + 1, error);
if (!ret)
return std::nullopt;
data.emplaceBack(std::move(*ret));
@@ -1054,8 +1065,8 @@ parseObject(
case STI_ARRAY:
try
{
auto array =
parseArray(jsonName + "." + fieldName, value, field, depth + 1, error);
auto array = parseArray(
joinName(jsonName, fieldName), value, field, depth + 1, error);
if (!array.has_value())
return std::nullopt;
data.emplaceBack(std::move(*array));

View File

@@ -66,9 +66,9 @@ getTxFormat(TxType type)
return format;
}
STTx::STTx(STObject&& object) : STObject(std::move(object))
STTx::STTx(STObject&& object)
: STObject(std::move(object)), txType_(safeCast<TxType>(getFieldU16(sfTransactionType)))
{
txType_ = safeCast<TxType>(getFieldU16(sfTransactionType));
applyTemplate(getTxFormat(txType_)->getSOTemplate()); // may throw
tid_ = getHash(HashPrefix::TransactionId);
buildBatchTxnIds();
@@ -100,6 +100,9 @@ STTx::STTx(TxType type, std::function<void(STObject&)> assembler) : STObject(sfT
assembler(*this);
// txType_ must be read after the object is assembled, so this cannot be a
// member initializer.
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
txType_ = safeCast<TxType>(getFieldU16(sfTransactionType));
if (txType_ != type)

View File

@@ -99,7 +99,7 @@ int
Serializer::addRaw(void const* ptr, int len)
{
int const ret = data_.size();
data_.insert(data_.end(), (char const*)ptr, ((char const*)ptr) + len);
data_.insert(data_.end(), static_cast<char const*>(ptr), static_cast<char const*>(ptr) + len);
return ret;
}

View File

@@ -23,16 +23,16 @@
namespace xrpl {
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
: transactionID_(txid), ledgerSeq_(ledger), nodes_(obj.getFieldArray(sfAffectedNodes))
: transactionID_(txid)
, ledgerSeq_(ledger)
, index_(obj.getFieldU32(sfTransactionIndex))
, result_(obj.getFieldU8(sfTransactionResult))
, nodes_([&obj] {
auto const affectedNodes = dynamic_cast<STArray const*>(obj.peekAtPField(sfAffectedNodes));
XRPL_ASSERT(affectedNodes, "xrpl::TxMeta::TxMeta(STObject) : type cast succeeded");
return affectedNodes != nullptr ? *affectedNodes : obj.getFieldArray(sfAffectedNodes);
}())
{
result_ = obj.getFieldU8(sfTransactionResult);
index_ = obj.getFieldU32(sfTransactionIndex);
auto affectedNodes = dynamic_cast<STArray const*>(obj.peekAtPField(sfAffectedNodes));
XRPL_ASSERT(affectedNodes, "xrpl::TxMeta::TxMeta(STObject) : type cast succeeded");
if (affectedNodes != nullptr)
nodes_ = *affectedNodes;
setAdditionalFields(obj);
}

View File

@@ -213,6 +213,12 @@ public:
if (auto [conn, keepAlive] = getConnection(); conn)
{
(void)keepAlive;
// The checkpointer is identified to the C callback by an integer id
// (resolved via checkpointerFromId) rather than a raw `this`, so it
// cannot dangle if the checkpointer is destroyed. Passing the id
// through sqlite's void* user-data requires an integer-to-pointer
// cast.
// NOLINTNEXTLINE(performance-no-int-to-ptr)
sqlite_api::sqlite3_wal_hook(conn, &sqliteWALHook, reinterpret_cast<void*>(id_));
}
}

View File

@@ -838,6 +838,7 @@ SHAMap::getHash() const
auto hash = root_->getHash();
if (hash.isZero())
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
const_cast<SHAMap&>(*this).unshare();
hash = root_->getHash();
}

View File

@@ -1157,7 +1157,11 @@ toClaim(STTx const& tx)
try
{
STObject o{tx};
// Copy just the field bag out of the transaction (explicitly, via the
// STObject base) so it can be reinterpreted as a cross-chain attestation
// below, with sfAccount replaced by sfOtherChainSource. STTx-specific
// state (txType_, tid_) is intentionally not needed here.
STObject o{static_cast<STObject const&>(tx)};
o.setAccountID(sfAccount, o[sfOtherChainSource]);
return TAttestation(o);
}

View File

@@ -40,7 +40,6 @@
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <initializer_list>
#include <optional>
#include <stdexcept>
@@ -309,31 +308,26 @@ struct ExistingElementPool
currencyNames.clear();
currencyNames.reserve(numCur);
static constexpr size_t kBufSize = 32;
char buf[kBufSize];
for (size_t id = 0; id < numAct; ++id)
{
snprintf(buf, kBufSize, "A%zu", id);
accounts.emplace_back(buf);
}
accounts.emplace_back("A" + std::to_string(id));
for (size_t id = 0; id < numCur; ++id)
{
std::string name;
if (id < 10)
{
snprintf(buf, kBufSize, "CC%zu", id);
name = "CC" + std::to_string(id);
}
else if (id < 100)
{
snprintf(buf, kBufSize, "C%zu", id);
name = "C" + std::to_string(id);
}
else
{
snprintf(buf, kBufSize, "%zu", id);
name = std::to_string(id);
}
currencies.emplace_back(toCurrency(buf));
currencyNames.emplace_back(buf);
currencies.emplace_back(toCurrency(name));
currencyNames.emplace_back(name);
}
for (auto const& a : accounts)

View File

@@ -245,9 +245,9 @@ struct Balance
T& env;
STAmount startAmount;
Balance(T& env, jtx::Account const& account) : account(account), env(env)
Balance(T& env, jtx::Account const& account)
: account(account), env(env), startAmount(env.balance(account))
{
startAmount = env.balance(account);
}
[[nodiscard]] STAmount

View File

@@ -1475,6 +1475,7 @@ r.ripple.com:51235
std::string toLoad(R"xrpldConfig(
[amendment_majority_time]
)xrpldConfig");
// NOLINTNEXTLINE(performance-inefficient-string-concatenation)
toLoad += std::to_string(val) + space + unit;
space = space.empty() ? " " : "";

View File

@@ -135,9 +135,8 @@ class PowerLawDistribution
public:
using result_type = double;
PowerLawDistribution(double xmin, double a) : xmin_{xmin}, a_{a}
PowerLawDistribution(double xmin, double a) : xmin_{xmin}, a_{a}, inv_(1.0 / (1.0 - a_))
{
inv_ = 1.0 / (1.0 - a_);
}
template <class Generator>

View File

@@ -323,12 +323,11 @@ class Validator
using Links = std::unordered_map<Peer::id_t, LinkSPtr>;
public:
Validator() : pkey_(std::get<0>(randomKeyPair(KeyType::Ed25519)))
Validator() : pkey_(std::get<0>(randomKeyPair(KeyType::Ed25519))), id_(sid++)
{
protocol::TMValidation v;
v.set_validation("validation");
message_ = std::make_shared<Message>(v, protocol::mtVALIDATION, pkey_);
id_ = sid++;
}
Validator(Validator const&) = default;
Validator(Validator&&) = default;
@@ -457,7 +456,6 @@ public:
using id_t = Peer::id_t;
PeerSim(Overlay& overlay, beast::Journal journal) : overlay_(overlay), squelch_(journal)
{
id_ = sid++;
}
~PeerSim() override = default;
@@ -512,7 +510,7 @@ public:
private:
inline static id_t sid = 0;
std::string fingerprint_;
id_t id_;
id_t id_{sid++};
Overlay& overlay_;
reduce_relay::Squelch<ManualClock> squelch_;
};

View File

@@ -1366,6 +1366,7 @@ public:
{
fail("Unable to build object from json");
}
// NOLINTNEXTLINE(cppcoreguidelines-slicing)
else if (STObject(j) != parsed.object)
{
log << "ORIG: " << j.getJson(JsonOptions::Values::None) << '\n'

View File

@@ -80,7 +80,9 @@ TxTest::TxTest(std::optional<FeatureBitset> features)
std::vector<uint256>{featureSet_.begin(), featureSet_.end()},
registry_.getNodeFamily());
// Initialize time from the genesis ledger
// Initialize time from the genesis ledger. closedLedger_ is created above
// in the body, so this cannot be a member initializer.
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
now_ = closedLedger_->header().closeTime;
// Create an open view on top of the genesis ledger

View File

@@ -13,6 +13,7 @@
#include <exception>
#include <limits>
#include <numbers>
#include <optional>
#include <regex>
#include <sstream>
#include <string>
@@ -592,6 +593,57 @@ TEST(json_value, bad_json)
EXPECT_TRUE(r.parse(s, j));
}
namespace {
std::optional<json::Value>
parseValue(std::string const& doc)
{
json::Value j;
json::Reader r;
if (!r.parse("{\"v\":" + doc + "}", j))
return std::nullopt;
return j["v"];
}
} // namespace
TEST(json_value, parse_double_valid)
{
// 1e300 is large but still representable, so it parses (unlike the out-of-range cases below).
for (auto const& [text, expected] :
{std::pair{"2.5", 2.5},
std::pair{"-3.25e2", -325.0},
std::pair{"0.0", 0.0},
std::pair{"1E3", 1000.0},
std::pair{"-0.5e-1", -0.05},
std::pair{"1e300", 1e300}})
{
auto const v = parseValue(text);
ASSERT_TRUE(v.has_value()) << text;
// NOLINTBEGIN(bugprone-unchecked-optional-access)
EXPECT_TRUE(v->isDouble()) << text;
EXPECT_EQ(v->asDouble(), expected) << text;
// NOLINTEND(bugprone-unchecked-optional-access)
}
}
TEST(json_value, parse_double_out_of_range)
{
// Magnitudes with no finite double representation are rejected.
for (char const* oor : {"1e400", "-1e400", "0.001e500", "1e-400", "-1e-400", "123e-500"})
EXPECT_FALSE(parseValue(oor).has_value()) << oor;
}
TEST(json_value, parse_double_malformed)
{
// readNumber() collects any run of digits and '.eE+-' into a single Double
// token, so these malformed tokens reach decodeDouble. Each has a valid
// leading prefix that from_chars would accept on its own; requiring the
// entire token be consumed rejects them instead of silently truncating.
for (char const* bad : {"1+2", "1-2", "1.2.3", "1e5e6", "1..2", "++5", "1e", "1e+", ".", "-"})
EXPECT_FALSE(parseValue(bad).has_value()) << bad;
}
TEST(json_value, edge_cases)
{
std::uint32_t const maxUInt = std::numeric_limits<std::uint32_t>::max();

View File

@@ -643,6 +643,7 @@ AmendmentState*
AmendmentTableImpl::get(uint256 const& amendmentHash, std::scoped_lock<std::mutex> const& lock)
{
// Forward to the const version of get.
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
return const_cast<AmendmentState*>(std::as_const(*this).get(amendmentHash, lock));
}