Compare commits

..

44 Commits

Author SHA1 Message Date
Ed Hennis
e762b0ce6d Merge remote-tracking branch 'XRPLF/pratik/Fix_asan_lsan_flagged_issues' into ximinez/number_asan
* XRPLF/pratik/Fix_asan_lsan_flagged_issues:
  fixed asan escape macro before return type
  added comments and haltonerror=false
  remove suppressions
  fix memory leak in LocalValue
  run parallel tests
  Don't copy the Json::StaticString
  fixes to static variable destruction issue.
  fix: Deletes expired NFToken offers from ledger (5707)
  chore: Add .zed editor config directory to .gitignore (6317)
  clang-format updates
  ssl context cleanup.
  docs: Update API changelog, add APIv2+APIv3 version documentation (6308)
  fix: Restore config changes that broke standalone mode (6301)
  chore: Add upper-case match for ARM64 in CompilationEnv (6315)
  ci: Update hashes of XRPLF/actions (6316)
  chore: Format all cmake files without comments (6294)
  chore: Add cmake-format pre-commit hook (6279)
  chore: Remove unnecessary `boost::system` requirement from conanfile (6290)
  chore: Set ColumnLimit to 120 in clang-format (6288)
  run tests in serial
2026-02-04 16:37:15 -05:00
Pratik Mankawde
bbb03e153e fixed asan escape macro before return type
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-02-04 15:54:50 +00:00
Pratik Mankawde
63b6ec98ea added comments and haltonerror=false
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-02-04 15:45:03 +00:00
Pratik Mankawde
7ef9fb4290 remove suppressions
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-02-04 14:31:03 +00:00
Pratik Mankawde
49dcb6b60b fix memory leak in LocalValue
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-02-04 13:17:31 +00:00
Pratik Mankawde
26dd1fafe3 run parallel tests
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-02-04 11:28:24 +00:00
Pratik Mankawde
4b99771021 Don't copy the Json::StaticString
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-02-04 11:24:54 +00:00
Pratik Mankawde
e6664fe4cf Merge branch 'develop' into pratik/Fix_asan_lsan_flagged_issues 2026-02-03 18:27:52 +00:00
Pratik Mankawde
b5001bc258 fixes to static variable destruction issue.
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-02-03 18:27:00 +00:00
Pratik Mankawde
5dacfa1938 clang-format updates
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-02-03 15:04:16 +00:00
Pratik Mankawde
394b256f02 Merge branch 'develop' into pratik/Fix_asan_lsan_flagged_issues
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-02-03 15:03:34 +00:00
Pratik Mankawde
5bfa38f6c5 ssl context cleanup.
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-02-03 15:00:37 +00:00
Ed Hennis
44e87b8277 Fix double negative 2026-01-29 20:07:09 -05:00
Ed Hennis
ddbd78c67a Fix namespaces 2026-01-29 12:48:57 -05:00
Ed Hennis
822023d8a4 Add unit tests for normalizeToRange
- Steal changes from @pratik's #6150 to avoid UB
2026-01-28 20:16:07 -05:00
Ed Hennis
5ab62f4422 Clean-ups and tweaks 2026-01-28 20:10:42 -05:00
Ed Hennis
5d9011084c Reduce expensive(?) accesses to thread_local MantissaRange 2026-01-28 20:10:42 -05:00
Ed Hennis
060feb4e37 Fix bugs
- Simplify shiftExponent().
- Clean up to_string() to prevent integers from including "e0".
- Fix root() and root2() computations by ensuring the mantissas have
  a consistent length.
2026-01-28 20:10:31 -05:00
Ed Hennis
295816f21d Convert "bool negative_ & uint64_t mantissa_" combo back to "rep mantissa_" 2026-01-28 20:09:08 -05:00
Ed Hennis
bf8db0555e Remove the _ suffixes from doNormalize function parameters 2026-01-28 17:36:31 -05:00
Ed Hennis
c8c5207675 Use 2^63-1 as maxMantissa for large range
- That makes minMantissa 2^63/10+1.
- Simplifies many of the existing operations, and removes the need for
  the accessors (mantissa() & exponent()) to do any math.
2026-01-28 17:36:12 -05:00
Pratik Mankawde
7c6c49bf98 run tests in serial
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-28 16:35:45 +00:00
Pratik Mankawde
6334be1ff0 Merge branch 'develop' into pratik/Fix_asan_lsan_flagged_issues
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-28 15:33:49 +00:00
Pratik Mankawde
a9c3bb84ba fixes to Number. run et even when st fails
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-28 15:30:05 +00:00
Pratik Mankawde
ca99e40290 fix memory leak.
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-28 14:49:40 +00:00
Pratik Mankawde
7612c1af0c suppress leaks in boost
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-28 14:39:43 +00:00
Pratik Mankawde
67e40be1ab run embedded test even if separate tests fails.
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-28 13:56:24 +00:00
Pratik Mankawde
0132174a7b remove boost from supps.
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-28 13:42:36 +00:00
Pratik Mankawde
8773cc4bbf Merge branch 'develop' into pratik/Fix_asan_lsan_flagged_issues 2026-01-28 13:21:47 +00:00
Pratik Mankawde
dabdadfff5 Merge branch 'develop' into pratik/Fix_asan_lsan_flagged_issues 2026-01-27 15:20:10 +00:00
Pratik Mankawde
bfe2cd7893 comment out assert
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-27 15:17:48 +00:00
Pratik Mankawde
0584c20f36 updated flags
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-27 13:43:03 +00:00
Pratik Mankawde
3ced0b27b7 not using -fsanitize-recover
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-27 13:20:46 +00:00
Pratik Mankawde
f83b27f7dd putting assert back since this is the cause of asan issue
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-27 12:27:11 +00:00
Pratik Mankawde
cdb41b5376 comment out assert in Number.cpp to verify if asan is strugling with asserts
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-27 11:54:26 +00:00
Pratik Mankawde
f223c89a9f replaced throw with Throw and supp static_assert
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-27 10:50:38 +00:00
Pratik Mankawde
efe07c09f3 assert supp
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-26 19:14:58 +00:00
Pratik Mankawde
79cde8b199 skip assert
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-26 17:40:04 +00:00
Pratik Mankawde
2078ce01cf try shamap fix
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-26 15:50:47 +00:00
Pratik Mankawde
2770a9cdf3 fixes to asan errors
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-26 14:55:18 +00:00
Pratik Mankawde
05ef3b1ad8 more fixes
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-23 18:28:09 +00:00
Pratik Mankawde
7dd4dbe285 fixes
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-23 17:13:03 +00:00
Pratik Mankawde
b32a5f2c08 supressions and jsonvalue fixes
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-23 16:20:50 +00:00
Pratik Mankawde
df76002a44 fixed asan issues
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-01-23 15:39:21 +00:00
52 changed files with 1748 additions and 1131 deletions

View File

@@ -89,6 +89,7 @@ words:
- endmacro
- exceptioned
- Falco
- fcontext
- finalizers
- firewalled
- fmtdur
@@ -101,6 +102,7 @@ words:
- gpgcheck
- gpgkey
- hotwallet
- hwaddress
- hwrap
- ifndef
- inequation
@@ -217,6 +219,7 @@ words:
- soci
- socidb
- sslws
- stackful
- statsd
- STATSDCOLLECTOR
- stissue

View File

@@ -70,7 +70,7 @@ that `test` code should _never_ be included in `xrpl` or `xrpld` code.)
## Validation
The [levelization](generate.py) script takes no parameters,
The [levelization](generate.sh) script takes no parameters,
reads no environment variables, and can be run from any directory,
as long as it is in the expected location in the rippled repo.
It can be run at any time from within a checked out repo, and will
@@ -104,7 +104,7 @@ It generates many files of [results](results):
Github Actions workflow to test that levelization loops haven't
changed. Unfortunately, if changes are detected, it can't tell if
they are improvements or not, so if you have resolved any issues or
done anything else to improve levelization, run `generate.py`,
done anything else to improve levelization, run `levelization.sh`,
and commit the updated results.
The `loops.txt` and `ordering.txt` files relate the modules
@@ -128,7 +128,7 @@ The committed files hide the detailed values intentionally, to
prevent false alarms and merging issues, and because it's easy to
get those details locally.
1. Run `generate.py`
1. Run `levelization.sh`
2. Grep the modules in `paths.txt`.
- For example, if a cycle is found `A ~= B`, simply `grep -w
A .github/scripts/levelization/results/paths.txt | grep -w B`

View File

@@ -1,369 +0,0 @@
#!/usr/bin/env python3
"""
Usage: generate.py
This script takes no parameters, and can be run from any directory,
as long as it is in the expected.
location in the repo.
"""
import os
import re
import subprocess
import sys
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Tuple, Set, Optional
# Compile regex patterns once at module level
INCLUDE_PATTERN = re.compile(r"^\s*#include.*/.*\.h")
INCLUDE_PATH_PATTERN = re.compile(r'[<"]([^>"]+)[>"]')
def dictionary_sort_key(s: str) -> str:
"""
Create a sort key that mimics 'sort -d' (dictionary order).
Dictionary order only considers blanks and alphanumeric characters.
This means punctuation like '.' is ignored during sorting.
"""
# Keep only alphanumeric characters and spaces
return "".join(c for c in s if c.isalnum() or c.isspace())
def get_level(file_path: str) -> str:
"""
Extract the level from a file path (second and third directory components).
Equivalent to bash: cut -d/ -f 2,3
Examples:
src/xrpld/app/main.cpp -> xrpld.app
src/libxrpl/protocol/STObject.cpp -> libxrpl.protocol
include/xrpl/basics/base_uint.h -> xrpl.basics
"""
parts = file_path.split("/")
# Get fields 2 and 3 (indices 1 and 2 in 0-based indexing)
if len(parts) >= 3:
level = f"{parts[1]}/{parts[2]}"
elif len(parts) >= 2:
level = f"{parts[1]}/toplevel"
else:
level = file_path
# If the "level" indicates a file, cut off the filename
if "." in level.split("/")[-1]: # Avoid Path object creation
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
level = level.rsplit("/", 1)[0] + "/toplevel"
return level.replace("/", ".")
def extract_include_level(include_line: str) -> Optional[str]:
"""
Extract the include path from an #include directive.
Gets the first two directory components from the include path.
Equivalent to bash: cut -d/ -f 1,2
Examples:
#include <xrpl/basics/base_uint.h> -> xrpl.basics
#include "xrpld/app/main/Application.h" -> xrpld.app
"""
# Remove everything before the quote or angle bracket
match = INCLUDE_PATH_PATTERN.search(include_line)
if not match:
return None
include_path = match.group(1)
parts = include_path.split("/")
# Get first two fields (indices 0 and 1)
if len(parts) >= 2:
include_level = f"{parts[0]}/{parts[1]}"
else:
include_level = include_path
# If the "includelevel" indicates a file, cut off the filename
if "." in include_level.split("/")[-1]: # Avoid Path object creation
include_level = include_level.rsplit("/", 1)[0] + "/toplevel"
return include_level.replace("/", ".")
def find_repo_root(start_path: Path, depth_limit: int = 10) -> Path:
"""
Find the repository root by looking for .git directory or src/include folders.
Walks up the directory tree from the start path.
"""
current = start_path.resolve()
# Walk up the directory tree
for _ in range(depth_limit): # Limit search depth to prevent infinite loops
# Check if this directory has src or include folders
has_src = (current / "src").exists()
has_include = (current / "include").exists()
if has_src or has_include:
return current
# Check if this is a git repository root
if (current / ".git").exists():
# Check if it has src or include nearby
if has_src or has_include:
return current
# Move up one level
parent = current.parent
if parent == current: # Reached filesystem root
break
current = parent
# If we couldn't find it, raise an error
raise RuntimeError(
"Could not find repository root. "
"Expected to find a directory containing 'src' and/or 'include' folders."
)
def get_scan_directories(repo_root: Path) -> List[Path]:
"""
Get the list of directories to scan for include files.
Returns paths that actually exist.
"""
directories = []
for dir_name in ["include", "src"]:
dir_path = repo_root / dir_name
if dir_path.exists() and dir_path.is_dir():
directories.append(dir_path)
if not directories:
raise RuntimeError(f"No 'src' or 'include' directories found in {repo_root}")
return directories
def main():
# Change to the script's directory
script_dir = Path(__file__).parent.resolve()
os.chdir(script_dir)
# If the shell is interactive, clean up any flotsam before analyzing
# Match bash behavior: check if PS1 is set (indicates interactive shell)
# When running a script, PS1 is not set even if stdin/stdout are TTYs
if os.environ.get("PS1"):
try:
subprocess.run(["git", "clean", "-ix"], check=False, timeout=30)
except (subprocess.TimeoutExpired, KeyboardInterrupt):
print("Skipping git clean...")
except Exception:
# If git clean fails for any reason, just continue
pass
# Clean up and create results directory
results_dir = script_dir / "results"
if results_dir.exists():
import shutil
shutil.rmtree(results_dir)
results_dir.mkdir()
# Find the repository root by searching for src and include directories
try:
repo_root = find_repo_root(script_dir)
scan_dirs = get_scan_directories(repo_root)
print(f"Found repository root: {repo_root}")
print(f"Scanning directories:")
for scan_dir in scan_dirs:
print(f" - {scan_dir.relative_to(repo_root)}")
except RuntimeError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
print("\nScanning for raw includes...")
# Find all #include directives
raw_includes: List[Tuple[str, str]] = []
rawincludes_file = results_dir / "rawincludes.txt"
# Write to file as we go to avoid storing everything in memory
with open(rawincludes_file, "w", buffering=8192) as raw_f:
for dir_path in scan_dirs:
print(f" Scanning {dir_path.relative_to(repo_root)}...")
for file_path in dir_path.rglob("*"):
if not file_path.is_file():
continue
try:
rel_path_str = str(file_path.relative_to(repo_root))
# Read file with larger buffer for better performance
with open(
file_path,
"r",
encoding="utf-8",
errors="ignore",
buffering=8192,
) as f:
for line in f:
# Quick check before regex
if "#include" not in line or "boost" in line:
continue
if INCLUDE_PATTERN.match(line):
line_stripped = line.strip()
entry = f"{rel_path_str}:{line_stripped}\n"
print(entry, end="")
raw_f.write(entry)
raw_includes.append((rel_path_str, line_stripped))
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
# Build levelization paths and count directly (no need to sort first)
print("Build levelization paths")
path_counts: Dict[Tuple[str, str], int] = defaultdict(int)
for file_path, include_line in raw_includes:
level = get_level(file_path)
include_level = extract_include_level(include_line)
if include_level and level != include_level:
path_counts[(level, include_level)] += 1
# Sort and deduplicate paths (using dictionary order like bash 'sort -d')
print("Sort and deduplicate paths")
paths_file = results_dir / "paths.txt"
with open(paths_file, "w") as f:
# Sort using dictionary order: only alphanumeric and spaces matter
sorted_items = sorted(
path_counts.items(),
key=lambda x: (dictionary_sort_key(x[0][0]), dictionary_sort_key(x[0][1])),
)
for (level, include_level), count in sorted_items:
line = f"{count:7} {level} {include_level}\n"
print(line.rstrip())
f.write(line)
# Split into flat-file database
print("Split into flat-file database")
includes_dir = results_dir / "includes"
included_by_dir = results_dir / "included_by"
includes_dir.mkdir()
included_by_dir.mkdir()
# Batch writes by grouping data first to avoid repeated file opens
includes_data: Dict[str, List[Tuple[str, int]]] = defaultdict(list)
included_by_data: Dict[str, List[Tuple[str, int]]] = defaultdict(list)
# Process in sorted order to match bash script behavior (dictionary order)
sorted_items = sorted(
path_counts.items(),
key=lambda x: (dictionary_sort_key(x[0][0]), dictionary_sort_key(x[0][1])),
)
for (level, include_level), count in sorted_items:
includes_data[level].append((include_level, count))
included_by_data[include_level].append((level, count))
# Write all includes files in sorted order (dictionary order)
for level in sorted(includes_data.keys(), key=dictionary_sort_key):
entries = includes_data[level]
with open(includes_dir / level, "w") as f:
for include_level, count in entries:
line = f"{include_level} {count}\n"
print(line.rstrip())
f.write(line)
# Write all included_by files in sorted order (dictionary order)
for include_level in sorted(included_by_data.keys(), key=dictionary_sort_key):
entries = included_by_data[include_level]
with open(included_by_dir / include_level, "w") as f:
for level, count in entries:
line = f"{level} {count}\n"
print(line.rstrip())
f.write(line)
# Search for loops
print("Search for loops")
loops_file = results_dir / "loops.txt"
ordering_file = results_dir / "ordering.txt"
loops_found: Set[Tuple[str, str]] = set()
# Pre-load all include files into memory to avoid repeated I/O
# This is the biggest optimization - we were reading files repeatedly in nested loops
# Use list of tuples to preserve file order
includes_cache: Dict[str, List[Tuple[str, int]]] = {}
includes_lookup: Dict[str, Dict[str, int]] = {} # For fast lookup
# Note: bash script uses 'for source in *' which uses standard glob sorting,
# NOT dictionary order. So we use standard sorted() here, not dictionary_sort_key.
for include_file in sorted(includes_dir.iterdir(), key=lambda p: p.name):
if not include_file.is_file():
continue
includes_cache[include_file.name] = []
includes_lookup[include_file.name] = {}
with open(include_file, "r") as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 2:
include_name = parts[0]
include_count = int(parts[1])
includes_cache[include_file.name].append(
(include_name, include_count)
)
includes_lookup[include_file.name][include_name] = include_count
with open(loops_file, "w", buffering=8192) as loops_f, open(
ordering_file, "w", buffering=8192
) as ordering_f:
# Use standard sorting to match bash glob expansion 'for source in *'
for source in sorted(includes_cache.keys()):
source_includes = includes_cache[source]
for include, include_freq in source_includes:
# Check if include file exists and references source
if include not in includes_lookup:
continue
source_freq = includes_lookup[include].get(source)
if source_freq is not None:
# Found a loop
loop_key = tuple(sorted([source, include]))
if loop_key in loops_found:
continue
loops_found.add(loop_key)
loops_f.write(f"Loop: {source} {include}\n")
# If the counts are close, indicate that the two modules are
# on the same level, though they shouldn't be
diff = include_freq - source_freq
if diff > 3:
loops_f.write(f" {source} > {include}\n\n")
elif diff < -3:
loops_f.write(f" {include} > {source}\n\n")
elif source_freq == include_freq:
loops_f.write(f" {include} == {source}\n\n")
else:
loops_f.write(f" {include} ~= {source}\n\n")
else:
ordering_f.write(f"{source} > {include}\n")
# Print results
print("\nOrdering:")
with open(ordering_file, "r") as f:
print(f.read(), end="")
print("\nLoops:")
with open(loops_file, "r") as f:
print(f.read(), end="")
if __name__ == "__main__":
main()

130
.github/scripts/levelization/generate.sh vendored Executable file
View File

@@ -0,0 +1,130 @@
#!/bin/bash
# Usage: generate.sh
# This script takes no parameters, reads no environment variables,
# and can be run from any directory, as long as it is in the expected
# location in the repo.
pushd $( dirname $0 )
if [ -v PS1 ]
then
# if the shell is interactive, clean up any flotsam before analyzing
git clean -ix
fi
# Ensure all sorting is ASCII-order consistently across platforms.
export LANG=C
rm -rfv results
mkdir results
includes="$( pwd )/results/rawincludes.txt"
pushd ../../..
echo Raw includes:
grep -r '^[ ]*#include.*/.*\.h' include src | \
grep -v boost | tee ${includes}
popd
pushd results
oldifs=${IFS}
IFS=:
mkdir includes
mkdir included_by
echo Build levelization paths
exec 3< ${includes} # open rawincludes.txt for input
while read -r -u 3 file include
do
level=$( echo ${file} | cut -d/ -f 2,3 )
# If the "level" indicates a file, cut off the filename
if [[ "${level##*.}" != "${level}" ]]
then
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
level="$( dirname ${level} )/toplevel"
fi
level=$( echo ${level} | tr '/' '.' )
includelevel=$( echo ${include} | sed 's/.*["<]//; s/[">].*//' | \
cut -d/ -f 1,2 )
if [[ "${includelevel##*.}" != "${includelevel}" ]]
then
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
includelevel="$( dirname ${includelevel} )/toplevel"
fi
includelevel=$( echo ${includelevel} | tr '/' '.' )
if [[ "$level" != "$includelevel" ]]
then
echo $level $includelevel | tee -a paths.txt
fi
done
echo Sort and deduplicate paths
sort -ds paths.txt | uniq -c | tee sortedpaths.txt
mv sortedpaths.txt paths.txt
exec 3>&- #close fd 3
IFS=${oldifs}
unset oldifs
echo Split into flat-file database
exec 4<paths.txt # open paths.txt for input
while read -r -u 4 count level include
do
echo ${include} ${count} | tee -a includes/${level}
echo ${level} ${count} | tee -a included_by/${include}
done
exec 4>&- #close fd 4
loops="$( pwd )/loops.txt"
ordering="$( pwd )/ordering.txt"
pushd includes
echo Search for loops
# Redirect stdout to a file
exec 4>&1
exec 1>"${loops}"
for source in *
do
if [[ -f "$source" ]]
then
exec 5<"${source}" # open for input
while read -r -u 5 include includefreq
do
if [[ -f $include ]]
then
if grep -q -w $source $include
then
if grep -q -w "Loop: $include $source" "${loops}"
then
continue
fi
sourcefreq=$( grep -w $source $include | cut -d\ -f2 )
echo "Loop: $source $include"
# If the counts are close, indicate that the two modules are
# on the same level, though they shouldn't be
if [[ $(( $includefreq - $sourcefreq )) -gt 3 ]]
then
echo -e " $source > $include\n"
elif [[ $(( $sourcefreq - $includefreq )) -gt 3 ]]
then
echo -e " $include > $source\n"
elif [[ $sourcefreq -eq $includefreq ]]
then
echo -e " $include == $source\n"
else
echo -e " $include ~= $source\n"
fi
else
echo "$source > $include" >> "${ordering}"
fi
fi
done
exec 5>&- #close fd 5
fi
done
exec 1>&4 #close fd 1
exec 4>&- #close fd 4
cat "${ordering}"
cat "${loops}"
popd
popd
popd

View File

@@ -153,7 +153,6 @@ tests.libxrpl > xrpl.json
tests.libxrpl > xrpl.net
xrpl.core > xrpl.basics
xrpl.core > xrpl.json
xrpl.core > xrpl.ledger
xrpl.json > xrpl.basics
xrpl.ledger > xrpl.basics
xrpl.ledger > xrpl.protocol

View File

@@ -51,20 +51,22 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
# Only generate a subset of configurations in PRs.
if not all:
# Debian:
# - Bookworm using GCC 13: Release on linux/amd64, set the reference
# fee to 500.
# - Bookworm using GCC 15: Debug on linux/amd64, enable code
# coverage (which will be done below).
# - Bookworm using Clang 16: Debug on linux/arm64, enable voidstar.
# - Bookworm using Clang 17: Release on linux/amd64, set the
# reference fee to 1000.
# - Bookworm using Clang 20: Debug on linux/amd64.
# - Bookworm using GCC 13: Release and Unity on linux/amd64, set
# the reference fee to 500.
# - Bookworm using GCC 15: Debug and no Unity on linux/amd64, enable
# code coverage (which will be done below).
# - Bookworm using Clang 16: Debug and no Unity on linux/arm64,
# enable voidstar.
# - Bookworm using Clang 17: Release and no Unity on linux/amd64,
# set the reference fee to 1000.
# - Bookworm using Clang 20: Debug and Unity on linux/amd64.
if os["distro_name"] == "debian":
skip = True
if os["distro_version"] == "bookworm":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
and build_type == "Release"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=500 {cmake_args}"
@@ -72,12 +74,14 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
and build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-16"
and build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/arm64"
):
cmake_args = f"-Dvoidstar=ON {cmake_args}"
@@ -85,6 +89,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-17"
and build_type == "Release"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=1000 {cmake_args}"
@@ -92,6 +97,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20"
and build_type == "Debug"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
@@ -99,14 +105,15 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
continue
# RHEL:
# - 9 using GCC 12: Debug on linux/amd64.
# - 10 using Clang: Release on linux/amd64.
# - 9 using GCC 12: Debug and Unity on linux/amd64.
# - 10 using Clang: Release and no Unity on linux/amd64.
if os["distro_name"] == "rhel":
skip = True
if os["distro_version"] == "9":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type == "Debug"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
@@ -114,6 +121,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-any"
and build_type == "Release"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
@@ -121,16 +129,17 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
continue
# Ubuntu:
# - Jammy using GCC 12: Debug on linux/arm64.
# - Noble using GCC 14: Release on linux/amd64.
# - Noble using Clang 18: Debug on linux/amd64.
# - Noble using Clang 19: Release on linux/arm64.
# - Jammy using GCC 12: Debug and no Unity on linux/arm64.
# - Noble using GCC 14: Release and Unity on linux/amd64.
# - Noble using Clang 18: Debug and no Unity on linux/amd64.
# - Noble using Clang 19: Release and Unity on linux/arm64.
if os["distro_name"] == "ubuntu":
skip = True
if os["distro_version"] == "jammy":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/arm64"
):
skip = False
@@ -138,18 +147,21 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14"
and build_type == "Release"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-18"
and build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-19"
and build_type == "Release"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/arm64"
):
skip = False
@@ -157,16 +169,20 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
continue
# MacOS:
# - Debug on macos/arm64.
# - Debug and no Unity on macos/arm64.
if os["distro_name"] == "macos" and not (
build_type == "Debug" and architecture["platform"] == "macos/arm64"
build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "macos/arm64"
):
continue
# Windows:
# - Release on windows/amd64.
# - Release and Unity on windows/amd64.
if os["distro_name"] == "windows" and not (
build_type == "Release" and architecture["platform"] == "windows/amd64"
build_type == "Release"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "windows/amd64"
):
continue
@@ -193,17 +209,18 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
):
continue
# Enable code coverage for Debian Bookworm using GCC 15 in Debug on
# linux/amd64
# Enable code coverage for Debian Bookworm using GCC 15 in Debug and no
# Unity on linux/amd64
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
and build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0 {cmake_args}"
# Generate a unique name for the configuration, e.g. macos-arm64-debug
# or debian-bookworm-gcc-12-amd64-release.
# or debian-bookworm-gcc-12-amd64-release-unity.
config_name = os["distro_name"]
if (n := os["distro_version"]) != "":
config_name += f"-{n}"
@@ -217,16 +234,20 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
config_name += f"-{build_type.lower()}"
if "-Dcoverage=ON" in cmake_args:
config_name += "-coverage"
if "-Dunity=ON" in cmake_args:
config_name += "-unity"
# Add the configuration to the list, with the most unique fields first,
# so that they are easier to identify in the GitHub Actions UI, as long
# names get truncated.
# Add Address and Thread (both coupled with UB) sanitizers for specific bookworm distros.
# GCC-Asan rippled-embedded tests are failing because of https://github.com/google/sanitizers/issues/856
if (
os["distro_version"] == "bookworm"
and f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20"
):
if os[
"distro_version"
] == "bookworm" and f"{os['compiler_name']}-{os['compiler_version']}" in [
"clang-20",
"gcc-13",
]:
# Add ASAN + UBSAN configuration.
configurations.append(
{

View File

@@ -208,5 +208,5 @@
}
],
"build_type": ["Debug", "Release"],
"cmake_args": [""]
"cmake_args": ["-Dunity=OFF", "-Dunity=ON"]
}

View File

@@ -15,5 +15,8 @@
}
],
"build_type": ["Debug", "Release"],
"cmake_args": ["-DCMAKE_POLICY_VERSION_MINIMUM=3.5"]
"cmake_args": [
"-Dunity=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5",
"-Dunity=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5"
]
}

View File

@@ -15,5 +15,5 @@
}
],
"build_type": ["Debug", "Release"],
"cmake_args": [""]
"cmake_args": ["-Dunity=OFF", "-Dunity=ON"]
}

View File

@@ -205,14 +205,18 @@ jobs:
- name: Set sanitizer options
if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }}
run: |
echo "ASAN_OPTIONS=print_stacktrace=1:detect_container_overflow=0:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp" >> ${GITHUB_ENV}
echo "TSAN_OPTIONS=second_deadlock_stack=1:halt_on_error=0:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV}
echo "UBSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >> ${GITHUB_ENV}
echo "LSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >> ${GITHUB_ENV}
echo "ASAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp" >> ${GITHUB_ENV}
echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV}
echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >> ${GITHUB_ENV}
echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >> ${GITHUB_ENV}
- name: Run the separate tests
# We continue on error here because we want to try the Embedded tests before
# failing. This will give us details on all the failures at once.
continue-on-error: true
if: ${{ !inputs.build_only }}
working-directory: ${{ env.BUILD_DIR }}
id: separate_tests
# Windows locks some of the build files while running tests, and parallel jobs can collide
env:
BUILD_TYPE: ${{ inputs.build_type }}
@@ -228,8 +232,14 @@ jobs:
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
env:
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
PARALLELISM: ${{ env.SANITIZERS_ENABLED == 'true' && '1' || steps.nproc.outputs.nproc }}
run: |
./xrpld --unittest --unittest-jobs "${BUILD_NPROC}"
./xrpld --unittest --unittest-jobs "${PARALLELISM}"
# Pipeline should fail if the separate tests failed.
- name: Check results of the SeparateTests
if: ${{ !inputs.build_only && steps.separate_tests.outcome == 'failure' }}
run: exit 1
- name: Debug failure (Linux)
if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }}

View File

@@ -20,7 +20,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Check levelization
run: python .github/scripts/levelization/generate.py
run: .github/scripts/levelization/generate.sh
- name: Check for differences
env:
MESSAGE: |
@@ -32,7 +32,7 @@ jobs:
removed from loops.txt, it's probably an improvement, while if
something was added, it's probably a regression.
Run '.github/scripts/levelization/generate.py' in your repo, commit
Run '.github/scripts/levelization/generate.sh' in your repo, commit
and push the changes. See .github/scripts/levelization/README.md for
more info.
run: |

3
.gitignore vendored
View File

@@ -71,6 +71,3 @@ DerivedData
/.augment
/.claude
/CLAUDE.md
# Python
__pycache__

View File

@@ -368,36 +368,6 @@ The workaround for this error is to add two lines to your profile:
tools.build:cxxflags=['-DBOOST_ASIO_DISABLE_CONCEPTS']
```
### Set Up Ccache
To speed up repeated compilations, we recommend that you install
[ccache](https://ccache.dev), a tool that wraps your compiler so that it can
cache build objects locally.
#### Linux
You can install it using the package manager, e.g. `sudo apt install ccache`
(Ubuntu) or `sudo dnf install ccache` (RHEL).
#### macOS
You can install it using Homebrew, i.e. `brew install ccache`.
#### Windows
You can install it using Chocolatey, i.e. `choco install ccache`. If you already
have Ccache installed, then `choco upgrade ccache` will update it to the latest
version. However, if you see an error such as:
```
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VC\v170\Microsoft.CppCommon.targets(617,5): error MSB6006: "cl.exe" exited with code 3.
```
then please install a specific version of Ccache that we know works, via: `choco
install ccache --version 4.11.3 --allow-downgrade`.
### Build and Test
1. Create a build directory and move into it.
@@ -575,10 +545,16 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
| `assert` | OFF | Enable assertions. |
| `coverage` | OFF | Prepare the coverage report. |
| `tests` | OFF | Build tests. |
| `unity` | OFF | Configure a unity build. |
| `xrpld` | OFF | Build the xrpld application, and not just the libxrpl library. |
| `werr` | OFF | Treat compilation warnings as errors |
| `wextra` | OFF | Enable additional compilation warnings |
[Unity builds][5] may be faster for the first build
(at the cost of much more memory) since they concatenate sources into fewer
translation units. Non-unity builds may be faster for incremental builds,
and can be helpful for detecting `#include` omissions.
## Troubleshooting
### Conan
@@ -645,6 +621,7 @@ If you want to experiment with a new package, follow these steps:
[1]: https://github.com/conan-io/conan-center-index/issues/13168
[2]: https://en.cppreference.com/w/cpp/compiler_support/20
[3]: https://docs.conan.io/en/latest/getting_started.html
[5]: https://en.wikipedia.org/wiki/Unity_build
[6]: https://github.com/boostorg/beast/issues/2648
[7]: https://github.com/boostorg/beast/issues/2661
[gcovr]: https://gcovr.com/en/stable/getting-started.html

View File

@@ -9,5 +9,8 @@ function (xrpl_add_test name)
isolate_headers(${target} "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/tests/${name}" PRIVATE)
# Make sure the test isn't optimized away in unity builds
set_target_properties(${target} PROPERTIES UNITY_BUILD_MODE GROUP UNITY_BUILD_BATCH_SIZE 0) # Adjust as needed
add_test(NAME ${target} COMMAND ${target})
endfunction ()

View File

@@ -4,7 +4,12 @@
include(target_protobuf_sources)
# Protocol buffers cannot participate in a unity build,
# because all the generated sources
# define a bunch of `static const` variables with the same names,
# so we just build them as a separate library.
add_library(xrpl.libpb)
set_target_properties(xrpl.libpb PROPERTIES UNITY_BUILD OFF)
target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto
PROTOS include/xrpl/proto/xrpl.proto)
@@ -155,4 +160,12 @@ if (xrpld)
# antithesis_instrumentation.h, which is not exported as INTERFACE
target_include_directories(xrpld PRIVATE ${CMAKE_SOURCE_DIR}/external/antithesis-sdk)
endif ()
# any files that don't play well with unity should be added here
if (tests)
set_source_files_properties(
# these two seem to produce conflicts in beast teardown template methods
src/test/rpc/ValidatorRPC_test.cpp src/test/ledger/Invariants_test.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION
TRUE)
endif ()
endif ()

View File

@@ -30,6 +30,14 @@ if (tests)
endif ()
endif ()
option(unity "Creates a build using UNITY support in cmake." OFF)
if (unity)
if (NOT is_ci)
set(CMAKE_UNITY_BUILD_BATCH_SIZE 15 CACHE STRING "")
endif ()
set(CMAKE_UNITY_BUILD ON CACHE BOOL "Do a unity build")
endif ()
if (is_clang AND is_linux)
option(voidstar "Enable Antithesis instrumentation." OFF)
endif ()

View File

@@ -32,13 +32,13 @@ target_link_libraries(
if (Boost_COMPILER)
target_link_libraries(xrpl_boost INTERFACE Boost::disable_autolinking)
endif ()
if (SANITIZERS_ENABLED AND is_clang)
# TODO: gcc does not support -fsanitize-blacklist...can we do something else for gcc ?
if (NOT Boost_INCLUDE_DIRS AND TARGET Boost::headers)
get_target_property(Boost_INCLUDE_DIRS Boost::headers INTERFACE_INCLUDE_DIRECTORIES)
endif ()
message(STATUS "Adding [${Boost_INCLUDE_DIRS}] to sanitizer blacklist")
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt "src:${Boost_INCLUDE_DIRS}/*")
target_compile_options(opts INTERFACE # ignore boost headers for sanitizing
-fsanitize-blacklist=${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt)
endif ()
# if (SANITIZERS_ENABLED AND is_clang)
# # TODO: gcc does not support -fsanitize-blacklist...can we do something else for gcc ?
# if (NOT Boost_INCLUDE_DIRS AND TARGET Boost::headers)
# get_target_property(Boost_INCLUDE_DIRS Boost::headers INTERFACE_INCLUDE_DIRECTORIES)
# endif ()
# message(STATUS "Adding [${Boost_INCLUDE_DIRS}] to sanitizer blacklist")
# file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt "src:${Boost_INCLUDE_DIRS}/*")
# target_compile_options(opts INTERFACE # ignore boost headers for sanitizing
# -fsanitize-blacklist=${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt)
# endif ()

View File

@@ -1,4 +1,5 @@
import re
import os
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
@@ -23,6 +24,7 @@ class Xrpl(ConanFile):
"shared": [True, False],
"static": [True, False],
"tests": [True, False],
"unity": [True, False],
"xrpld": [True, False],
}
@@ -54,6 +56,7 @@ class Xrpl(ConanFile):
"shared": False,
"static": True,
"tests": False,
"unity": False,
"xrpld": False,
"date/*:header_only": True,
"ed25519/*:shared": False,
@@ -124,6 +127,12 @@ class Xrpl(ConanFile):
if self.settings.compiler in ["clang", "gcc"]:
self.options["boost"].without_cobalt = True
# Check if environment variable exists
if "SANITIZERS" in os.environ:
sanitizers = os.environ["SANITIZERS"]
if "Address" in sanitizers:
self.default_options["fPIC"] = False
def requirements(self):
# Conan 2 requires transitive headers to be specified
transitive_headers_opt = (
@@ -166,6 +175,7 @@ class Xrpl(ConanFile):
tc.variables["rocksdb"] = self.options.rocksdb
tc.variables["BUILD_SHARED_LIBS"] = self.options.shared
tc.variables["static"] = self.options.static
tc.variables["unity"] = self.options.unity
tc.variables["xrpld"] = self.options.xrpld
tc.generate()

View File

@@ -89,8 +89,8 @@ cmake --build . --parallel 4
**IMPORTANT**: ASAN with Boost produces many false positives. Use these options:
```bash
export ASAN_OPTIONS="print_stacktrace=1:detect_container_overflow=0:suppressions=path/to/asan.supp:halt_on_error=0:log_path=asan.log"
export LSAN_OPTIONS="suppressions=path/to/lsan.supp:halt_on_error=0:log_path=lsan.log"
export ASAN_OPTIONS="include=sanitizers/suppressions/runtime-asan-options.txt:suppressions=sanitizers/suppressions/asan.supp"
export LSAN_OPTIONS="include=sanitizers/suppressions/runtime-lsan-options.txt:suppressions=sanitizers/suppressions/lsan.supp"
# Run tests
./xrpld --unittest --unittest-jobs=5
@@ -108,7 +108,7 @@ export LSAN_OPTIONS="suppressions=path/to/lsan.supp:halt_on_error=0:log_path=lsa
### ThreadSanitizer (TSan)
```bash
export TSAN_OPTIONS="suppressions=path/to/tsan.supp halt_on_error=0 log_path=tsan.log"
export TSAN_OPTIONS="include=sanitizers/suppressions/runtime-tsan-options.txt:suppressions=sanitizers/suppressions/tsan.supp"
# Run tests
./xrpld --unittest --unittest-jobs=5
@@ -129,7 +129,7 @@ More details [here](https://github.com/google/sanitizers/wiki/AddressSanitizerLe
### UndefinedBehaviorSanitizer (UBSan)
```bash
export UBSAN_OPTIONS="suppressions=path/to/ubsan.supp:print_stacktrace=1:halt_on_error=0:log_path=ubsan.log"
export UBSAN_OPTIONS="include=sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=sanitizers/suppressions/ubsan.supp"
# Run tests
./xrpld --unittest --unittest-jobs=5

View File

@@ -1,8 +1,6 @@
#ifndef XRPL_BASICS_LOCALVALUE_H_INCLUDED
#define XRPL_BASICS_LOCALVALUE_H_INCLUDED
#include <boost/thread/tss.hpp>
#include <memory>
#include <unordered_map>
@@ -42,21 +40,63 @@ struct LocalValues
// Keys are the address of a LocalValue.
std::unordered_map<void const*, std::unique_ptr<BasicValue>> values;
};
static inline void
cleanup(LocalValues* lvs)
// Wrapper to ensure proper cleanup when thread exits
struct LocalValuesHolder
{
LocalValues* ptr = nullptr;
~LocalValuesHolder()
{
if (lvs && !lvs->onCoro)
delete lvs;
if (ptr && !ptr->onCoro)
delete ptr;
}
};
template <class = void>
boost::thread_specific_ptr<detail::LocalValues>&
getLocalValues()
inline LocalValuesHolder&
getLocalValuesHolder()
{
static boost::thread_specific_ptr<detail::LocalValues> tsp(&detail::LocalValues::cleanup);
return tsp;
thread_local LocalValuesHolder holder;
return holder;
}
inline LocalValues*&
getLocalValuesPtr()
{
return getLocalValuesHolder().ptr;
}
inline LocalValues*
getOrCreateLocalValues()
{
auto& ptr = getLocalValuesPtr();
if (!ptr)
{
ptr = new LocalValues();
ptr->onCoro = false;
}
return ptr;
}
// For coroutine support, we need explicit swap functions
inline LocalValues*
releaseLocalValues()
{
auto& ptr = getLocalValuesPtr();
auto* result = ptr;
ptr = nullptr;
return result;
}
inline void
resetLocalValues(LocalValues* lvs)
{
auto& ptr = getLocalValuesPtr();
// Clean up old value if it's not a coroutine's LocalValues
if (ptr && !ptr->onCoro)
delete ptr;
ptr = lvs;
}
} // namespace detail
@@ -89,19 +129,10 @@ template <class T>
T&
LocalValue<T>::operator*()
{
auto lvs = detail::getLocalValues().get();
if (!lvs)
{
lvs = new detail::LocalValues();
lvs->onCoro = false;
detail::getLocalValues().reset(lvs);
}
else
{
auto const iter = lvs->values.find(this);
if (iter != lvs->values.end())
return *reinterpret_cast<T*>(iter->second->get());
}
auto lvs = detail::getOrCreateLocalValues();
auto const iter = lvs->values.find(this);
if (iter != lvs->values.end())
return *reinterpret_cast<T*>(iter->second->get());
return *reinterpret_cast<T*>(
lvs->values.emplace(this, std::make_unique<detail::LocalValues::Value<T>>(t_)).first->second->get());

View File

@@ -10,6 +10,10 @@
#include <ostream>
#include <string>
#ifdef _MSC_VER
#include <boost/multiprecision/cpp_int.hpp>
#endif // !defined(_MSC_VER)
namespace xrpl {
class Number;
@@ -17,18 +21,37 @@ class Number;
std::string
to_string(Number const& amount);
/** Returns a rough estimate of log10(value).
*
* The return value is a pair (log, rem), where log is the estimated log10,
* and rem is value divided by 10^log. If rem is 1, then value is an exact
* power of ten, and log is the exact log10(value).
*
* This function only works for positive values.
*/
template <typename T>
constexpr std::pair<int, T>
logTenEstimate(T value)
{
int log = 0;
T remainder = value;
while (value >= 10)
{
if (value % 10 == 0)
remainder = remainder / 10;
value /= 10;
++log;
}
return {log, remainder};
}
template <typename T>
constexpr std::optional<int>
logTen(T value)
{
int log = 0;
while (value >= 10 && value % 10 == 0)
{
value /= 10;
++log;
}
if (value == 1)
return log;
auto const est = logTenEstimate(value);
if (est.second == 1)
return est.first;
return std::nullopt;
}
@@ -42,12 +65,10 @@ isPowerOfTen(T value)
/** 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
* * max = min * 10 - 1.
*
* The mantissa_scale enum indicates whether the range is "small" or "large".
* This intentionally restricts the number of MantissaRanges that can be
* instantiated to two: one for each scale.
* used to two: one for each scale.
*
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min
* value of 10^15, and a max value of 10^16-1. This was sufficient for
@@ -61,8 +82,8 @@ isPowerOfTen(T value)
* "large" scale.
*
* The "large" scale is intended to represent all values that can be represented
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
* value of 10^19-1.
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 2^63/10+1
* (truncated), and a max value of 2^63-1.
*
* Note that if the mentioned amendments are eventually retired, this class
* should be left in place, but the "small" scale option should be removed. This
@@ -74,25 +95,50 @@ struct MantissaRange
enum mantissa_scale { small, large };
explicit constexpr MantissaRange(mantissa_scale scale_)
: min(getMin(scale_)), max(min * 10 - 1), log(logTen(min).value_or(-1)), scale(scale_)
: max(getMax(scale_))
, min(computeMin(max))
, referenceMin(getReferenceMin(scale_, min))
, log(computeLog(min))
, scale(scale_)
{
// Since this is constexpr, if any of these throw, it won't compile
if (min * 10 <= max)
throw std::out_of_range("min * 10 <= max");
if (max / 10 >= min)
throw std::out_of_range("max / 10 >= min");
if ((min - 1) * 10 > max)
throw std::out_of_range("(min - 1) * 10 > max");
// This is a little hacky
if ((max + 10) / 10 < min)
throw std::out_of_range("(max + 10) / 10 < min");
}
rep min;
// Explicitly delete copy and move operations
MantissaRange(MantissaRange const&) = delete;
MantissaRange(MantissaRange&&) = delete;
MantissaRange&
operator=(MantissaRange const&) = delete;
MantissaRange&
operator=(MantissaRange&&) = delete;
rep max;
rep min;
// This is not a great name. Used to determine if mantissas are in range,
// but have fewer digits than max
rep referenceMin;
int log;
mantissa_scale scale;
private:
static constexpr rep
getMin(mantissa_scale scale_)
getMax(mantissa_scale scale)
{
switch (scale_)
switch (scale)
{
case small:
return 1'000'000'000'000'000ULL;
return 9'999'999'999'999'999ULL;
case large:
return 1'000'000'000'000'000'000ULL;
return std::numeric_limits<std::int64_t>::max();
default:
// Since this can never be called outside a non-constexpr
// context, this throw assures that the build fails if an
@@ -100,12 +146,53 @@ private:
throw std::runtime_error("Unknown mantissa scale");
}
}
static constexpr rep
computeMin(rep max)
{
return max / 10 + 1;
}
static constexpr rep
getReferenceMin(mantissa_scale scale, rep min)
{
switch (scale)
{
case large:
return 1'000'000'000'000'000'000ULL;
default:
if (isPowerOfTen(min))
return min;
throw std::runtime_error("Unknown/bad mantissa scale");
}
}
static constexpr rep
computeLog(rep min)
{
auto const estimate = logTenEstimate(min);
return estimate.first + (estimate.second == 1 ? 0 : 1);
}
};
// 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>;
namespace detail {
#ifdef _MSC_VER
using uint128_t = boost::multiprecision::uint128_t;
using int128_t = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
using int128_t = __int128_t;
#endif // !defined(_MSC_VER)
template <class T>
concept UnsignedMantissa =
std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
} // namespace detail
/** 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 -
@@ -133,9 +220,7 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
* 1. Normalization can be disabled by using the "unchecked" ctor tag. This
* should only be used at specific conversion points, some constexpr
* values, and in unit tests.
* 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that
* fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and
* 10^20-1 > 2^64-1). This avoids under- and overflows.
* 2. The max of the "large" range, 2^63-1, TODO: explain the large range.
*
* ---- External Interface ----
*
@@ -149,7 +234,7 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
*
* Note:
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large"
* mantissa range.
* mantissa range. TODO: update this explanation.
* 2. The functions mantissa() and exponent() return the external view of the
* Number value, specifically using a signed 63-bit mantissa. This may
* require altering the internal representation to fit into that range
@@ -209,8 +294,7 @@ class Number
using rep = std::int64_t;
using internalrep = MantissaRange::rep;
bool negative_{false};
internalrep mantissa_{0};
rep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
public:
@@ -218,9 +302,11 @@ public:
constexpr static int minExponent = -32768;
constexpr static int maxExponent = 32768;
#if MAXREP
constexpr static internalrep maxRep = std::numeric_limits<rep>::max();
static_assert(maxRep == 9'223'372'036'854'775'807);
static_assert(-maxRep == std::numeric_limits<rep>::min() + 1);
#endif
// May need to make unchecked private
struct unchecked
@@ -295,7 +381,7 @@ public:
friend constexpr bool
operator==(Number const& x, Number const& y) noexcept
{
return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
}
friend constexpr bool
@@ -309,8 +395,8 @@ public:
{
// If the two amounts have different signs (zero is treated as positive)
// then the comparison is true iff the left is negative.
bool const lneg = x.negative_;
bool const rneg = y.negative_;
bool const lneg = x.mantissa_ < 0;
bool const rneg = y.mantissa_ < 0;
if (lneg != rneg)
return lneg;
@@ -338,7 +424,7 @@ public:
constexpr int
signum() const noexcept
{
return negative_ ? -1 : (mantissa_ ? 1 : 0);
return mantissa_ < 0 ? -1 : (mantissa_ ? 1 : 0);
}
Number
@@ -377,6 +463,9 @@ public:
friend Number
root2(Number f);
friend Number
power(Number const& f, unsigned n, unsigned d);
// Thread local rounding control. Default is to_nearest
enum rounding_mode { to_nearest, towards_zero, downward, upward };
static rounding_mode
@@ -441,22 +530,48 @@ private:
static_assert(isPowerOfTen(smallRange.min));
static_assert(smallRange.min == 1'000'000'000'000'000LL);
static_assert(smallRange.max == 9'999'999'999'999'999LL);
static_assert(smallRange.referenceMin == smallRange.min);
static_assert(smallRange.log == 15);
#if MAXREP
static_assert(smallRange.min < maxRep);
static_assert(smallRange.max < maxRep);
#endif
constexpr static MantissaRange largeRange{MantissaRange::large};
static_assert(isPowerOfTen(largeRange.min));
static_assert(largeRange.min == 1'000'000'000'000'000'000ULL);
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
static_assert(!isPowerOfTen(largeRange.min));
static_assert(largeRange.min == 922'337'203'685'477'581ULL);
static_assert(largeRange.max == internalrep(9'223'372'036'854'775'807ULL));
static_assert(largeRange.max == std::numeric_limits<rep>::max());
static_assert(largeRange.referenceMin == 1'000'000'000'000'000'000ULL);
static_assert(largeRange.log == 18);
// There are 2 values that will not fit in largeRange without some extra
// work
// * 9223372036854775808
// * 9223372036854775809
// They both end up < min, but with a leftover. If they round up, everything
// will be fine. If they don't, well need to bring them up into range.
// Guard::bringIntoRange handles this situation.
#if MAXREP
static_assert(largeRange.min < maxRep);
static_assert(largeRange.max > maxRep);
#endif
// The range for the mantissa when normalized.
// Use reference_wrapper to avoid making copies, and prevent accidentally
// changing the values inside the range.
static thread_local std::reference_wrapper<MantissaRange const> range_;
// And one is needed because it needs to choose between oneSmall and
// oneLarge based on the current range
static Number
one(MantissaRange const& range);
static Number
root(MantissaRange const& range, Number f, unsigned d);
void
normalize(MantissaRange const& range);
void
normalize();
@@ -479,11 +594,14 @@ private:
friend void
doNormalize(
bool& negative,
T& mantissa_,
int& exponent_,
T& mantissa,
int& exponent,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa);
bool
isnormal(MantissaRange const& range) const noexcept;
bool
isnormal() const noexcept;
@@ -500,11 +618,73 @@ private:
static internalrep
externalToInternal(rep mantissa);
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep = internalrep>
std::tuple<bool, Rep, int>
toInternal(MantissaRange const& range) const;
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep = internalrep>
std::tuple<bool, Rep, int>
toInternal() const;
/** Rebuilds the number from components.
*
* If "normalized" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "normalized" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <
bool expectNormal = true,
detail::UnsignedMantissa Rep = internalrep>
void
fromInternal(
bool negative,
Rep mantissa,
int exponent,
MantissaRange const* pRange);
/** Rebuilds the number from components.
*
* If "normalized" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "normalized" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <
bool expectNormal = true,
detail::UnsignedMantissa Rep = internalrep>
void
fromInternal(bool negative, Rep mantissa, int exponent);
class Guard;
public:
constexpr static internalrep largestMantissa = largeRange.max;
};
inline constexpr Number::Number(bool negative, internalrep mantissa, int exponent, unchecked) noexcept
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
inline constexpr Number::Number(
bool negative,
internalrep mantissa,
int exponent,
unchecked) noexcept
: mantissa_{(negative ? -1 : 1) * static_cast<rep>(mantissa)}
, exponent_{exponent}
{
}
@@ -515,13 +695,8 @@ inline constexpr Number::Number(internalrep mantissa, int exponent, unchecked) n
constexpr static Number numZero{};
inline Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
: Number(negative, mantissa, exponent, unchecked{})
{
normalize();
}
inline Number::Number(internalrep mantissa, int exponent, normalized) : Number(false, mantissa, exponent, normalized{})
inline Number::Number(internalrep mantissa, int exponent, normalized)
: Number(false, mantissa, exponent, normalized{})
{
}
@@ -542,17 +717,7 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
inline constexpr Number::rep
Number::mantissa() const noexcept
{
auto m = mantissa_;
if (m > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (m % 10 == 0 && m / 10 <= maxRep),
"xrpl::Number::mantissa",
"large normalized mantissa has no remainder");
m /= 10;
}
auto const sign = negative_ ? -1 : 1;
return sign * static_cast<Number::rep>(m);
return mantissa_;
}
/** Returns the exponent of the external view of the Number.
@@ -563,16 +728,7 @@ Number::mantissa() const noexcept
inline constexpr int
Number::exponent() const noexcept
{
auto e = exponent_;
if (mantissa_ > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep),
"xrpl::Number::exponent",
"large normalized mantissa has no remainder");
++e;
}
return e;
return exponent_;
}
inline constexpr Number
@@ -587,7 +743,7 @@ Number::operator-() const noexcept
if (mantissa_ == 0)
return Number{};
auto x = *this;
x.negative_ = !x.negative_;
x.mantissa_ = -1 * x.mantissa_;
return x;
}
@@ -668,39 +824,61 @@ Number::min() noexcept
inline Number
Number::max() noexcept
{
return Number{false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
return Number{false, range_.get().max, maxExponent, unchecked{}};
}
inline Number
Number::lowest() noexcept
{
return Number{true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
return Number{true, range_.get().max, maxExponent, unchecked{}};
}
inline bool
Number::isnormal(MantissaRange const& range) const noexcept
{
auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_;
return *this == Number{} ||
(range.min <= abs_m && abs_m <= range.max && //
minExponent <= exponent_ && exponent_ <= maxExponent);
}
inline bool
Number::isnormal() const noexcept
{
MantissaRange const& range = range_;
auto const abs_m = mantissa_;
return *this == Number{} ||
(range.min <= abs_m && abs_m <= range.max && (abs_m <= maxRep || abs_m % 10 == 0) && minExponent <= exponent_ &&
exponent_ <= maxExponent);
return isnormal(range_);
}
template <Integral64 T>
std::pair<T, int>
Number::normalizeToRange(T minMantissa, T maxMantissa) const
{
bool negative = negative_;
internalrep mantissa = mantissa_;
bool negative = mantissa_ < 0;
auto const sign = negative ? -1 : 1;
internalrep mantissa = sign * mantissa_;
int exponent = exponent_;
if constexpr (std::is_unsigned_v<T>)
XRPL_ASSERT_PARTS(!negative, "xrpl::Number::normalizeToRange", "Number is non-negative for unsigned range.");
{
XRPL_ASSERT_PARTS(
!negative,
"xrpl::Number::normalizeToRange",
"Number is non-negative for unsigned range.");
// To avoid logical errors in release builds, throw if the Number is
// negative for an unsigned range.
if (negative)
throw std::runtime_error(
"Number::normalizeToRange: Number is negative for "
"unsigned range.");
}
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa);
auto const sign = negative ? -1 : 1;
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
// Cast mantissa to signed type first (if T is a signed type) to avoid
// unsigned integer overflow when multiplying by negative sign
T signedMantissa = static_cast<T>(mantissa);
if (negative)
signedMantissa = -signedMantissa;
return std::make_pair(signedMantissa, exponent);
}
inline constexpr Number

View File

@@ -358,6 +358,7 @@ public:
base_uint&
operator&=(base_uint const& b)
{
XRPL_ASSERT(WIDTH == b.WIDTH, "input size mismatch");
for (int i = 0; i < WIDTH; i++)
data_[i] &= b.data_[i];

View File

@@ -1,6 +1,7 @@
#ifndef XRPL_BASICS_CONTRACT_H_INCLUDED
#define XRPL_BASICS_CONTRACT_H_INCLUDED
#include <xrpl/basics/sanitizers.h>
#include <xrpl/beast/type_name.h>
#include <exception>
@@ -25,7 +26,7 @@ LogThrow(std::string const& title);
control to the next matching exception handler, if any.
Otherwise, std::terminate will be called.
*/
[[noreturn]] inline void
[[noreturn]] XRPL_NO_SANITIZE_ADDRESS inline void
Rethrow()
{
LogThrow("Re-throwing exception");
@@ -33,7 +34,7 @@ Rethrow()
}
template <class E, class... Args>
[[noreturn]] inline void
[[noreturn]] XRPL_NO_SANITIZE_ADDRESS inline void
Throw(Args&&... args)
{
static_assert(std::is_convertible<E*, std::exception*>::value, "Exception must derive from std::exception.");

View File

@@ -1,5 +1,5 @@
#ifndef XRPL_BASICS_ROCKSDB_H_INCLUDED
#define XRPL_BASICS_ROCKSDB_H_INCLUDED
#ifndef XRPL_UNITY_ROCKSDB_H_INCLUDED
#define XRPL_UNITY_ROCKSDB_H_INCLUDED
#if XRPL_ROCKSDB_AVAILABLE
// #include <rocksdb2/port/port_posix.h>

View File

@@ -0,0 +1,6 @@
// Helper to disable ASan/HwASan for specific functions
#if defined(__GNUC__) || defined(__clang__)
#define XRPL_NO_SANITIZE_ADDRESS __attribute__((no_sanitize("address", "hwaddress")))
#else
#define XRPL_NO_SANITIZE_ADDRESS
#endif

View File

@@ -73,13 +73,16 @@ JobQueue::Coro::resume()
std::lock_guard lock(jq_.m_mutex);
--jq_.nSuspend_;
}
auto saved = detail::getLocalValues().release();
detail::getLocalValues().reset(&lvs_);
auto saved = detail::releaseLocalValues();
detail::resetLocalValues(&lvs_);
std::lock_guard lock(mutex_);
XRPL_ASSERT(static_cast<bool>(coro_), "xrpl::JobQueue::Coro::resume : is runnable");
coro_();
detail::getLocalValues().release();
detail::getLocalValues().reset(saved);
// Restore the thread's original LocalValues
detail::releaseLocalValues();
detail::resetLocalValues(saved);
std::lock_guard lk(mutex_run_);
running_ = false;
cv_.notify_all();

View File

@@ -1,202 +0,0 @@
#ifndef XRPL_CORE_SERVICEREGISTRY_H_INCLUDED
#define XRPL_CORE_SERVICEREGISTRY_H_INCLUDED
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/SHAMapHash.h>
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/ledger/CachedSLEs.h>
namespace xrpl {
// Forward declarations
namespace NodeStore {
class Database;
}
namespace Resource {
class Manager;
}
namespace perf {
class PerfLog;
}
class AcceptedLedger;
class AmendmentTable;
class Cluster;
class CollectorManager;
class DatabaseCon;
class Family;
class HashRouter;
class InboundLedgers;
class InboundTransactions;
class JobQueue;
class LedgerCleaner;
class LedgerMaster;
class LedgerReplayer;
class LoadFeeTrack;
class LoadManager;
class ManifestCache;
class NetworkOPs;
class OpenLedger;
class OrderBookDB;
class Overlay;
class PathRequests;
class PeerReservationTable;
class PendingSaves;
class RelationalDatabase;
class ServerHandler;
class SHAMapStore;
class TimeKeeper;
class TransactionMaster;
class TxQ;
class ValidatorList;
class ValidatorSite;
template <class Adaptor>
class Validations;
class RCLValidationsAdaptor;
using RCLValidations = Validations<RCLValidationsAdaptor>;
using NodeCache = TaggedCache<SHAMapHash, Blob>;
/** Service registry for dependency injection.
This abstract interface provides access to various services and components
used throughout the application. It separates the service locator pattern
from the Application lifecycle management.
Components that need access to services can hold a reference to
ServiceRegistry rather than Application when they only need service
access and not lifecycle management.
*/
class ServiceRegistry
{
public:
ServiceRegistry() = default;
virtual ~ServiceRegistry() = default;
// Core infrastructure services
virtual CollectorManager&
getCollectorManager() = 0;
virtual Family&
getNodeFamily() = 0;
virtual TimeKeeper&
timeKeeper() = 0;
virtual JobQueue&
getJobQueue() = 0;
virtual NodeCache&
getTempNodeCache() = 0;
virtual CachedSLEs&
cachedSLEs() = 0;
// Protocol and validation services
virtual AmendmentTable&
getAmendmentTable() = 0;
virtual HashRouter&
getHashRouter() = 0;
virtual LoadFeeTrack&
getFeeTrack() = 0;
virtual LoadManager&
getLoadManager() = 0;
virtual RCLValidations&
getValidations() = 0;
virtual ValidatorList&
validators() = 0;
virtual ValidatorSite&
validatorSites() = 0;
virtual ManifestCache&
validatorManifests() = 0;
virtual ManifestCache&
publisherManifests() = 0;
// Network services
virtual Overlay&
overlay() = 0;
virtual Cluster&
cluster() = 0;
virtual PeerReservationTable&
peerReservations() = 0;
virtual Resource::Manager&
getResourceManager() = 0;
// Storage services
virtual NodeStore::Database&
getNodeStore() = 0;
virtual SHAMapStore&
getSHAMapStore() = 0;
virtual RelationalDatabase&
getRelationalDatabase() = 0;
// Ledger services
virtual InboundLedgers&
getInboundLedgers() = 0;
virtual InboundTransactions&
getInboundTransactions() = 0;
virtual TaggedCache<uint256, AcceptedLedger>&
getAcceptedLedgerCache() = 0;
virtual LedgerMaster&
getLedgerMaster() = 0;
virtual LedgerCleaner&
getLedgerCleaner() = 0;
virtual LedgerReplayer&
getLedgerReplayer() = 0;
virtual PendingSaves&
pendingSaves() = 0;
virtual OpenLedger&
openLedger() = 0;
virtual OpenLedger const&
openLedger() const = 0;
// Transaction and operation services
virtual NetworkOPs&
getOPs() = 0;
virtual OrderBookDB&
getOrderBookDB() = 0;
virtual TransactionMaster&
getMasterTransaction() = 0;
virtual TxQ&
getTxQ() = 0;
virtual PathRequests&
getPathRequests() = 0;
// Server services
virtual ServerHandler&
getServerHandler() = 0;
virtual perf::PerfLog&
getPerfLog() = 0;
};
} // namespace xrpl
#endif

View File

@@ -30,6 +30,9 @@ public:
bool sslVerify,
beast::Journal j);
static void
cleanupSSLContext();
static void
get(bool bSSL,
boost::asio::io_context& io_context,

View File

@@ -230,7 +230,7 @@ missing_field_error(std::string const& name)
}
inline Json::Value
missing_field_error(Json::StaticString name)
missing_field_error(Json::StaticString const& name)
{
return missing_field_error(std::string(name));
}
@@ -248,7 +248,7 @@ object_field_error(std::string const& name)
}
inline Json::Value
object_field_error(Json::StaticString name)
object_field_error(Json::StaticString const& name)
{
return object_field_error(std::string(name));
}
@@ -260,7 +260,7 @@ invalid_field_message(std::string const& name)
}
inline std::string
invalid_field_message(Json::StaticString name)
invalid_field_message(Json::StaticString const& name)
{
return invalid_field_message(std::string(name));
}
@@ -272,7 +272,7 @@ invalid_field_error(std::string const& name)
}
inline Json::Value
invalid_field_error(Json::StaticString name)
invalid_field_error(Json::StaticString const& name)
{
return invalid_field_error(std::string(name));
}
@@ -284,7 +284,7 @@ expected_field_message(std::string const& name, std::string const& type)
}
inline std::string
expected_field_message(Json::StaticString name, std::string const& type)
expected_field_message(Json::StaticString const& name, std::string const& type)
{
return expected_field_message(std::string(name), type);
}
@@ -296,7 +296,7 @@ expected_field_error(std::string const& name, std::string const& type)
}
inline Json::Value
expected_field_error(Json::StaticString name, std::string const& type)
expected_field_error(Json::StaticString const& name, std::string const& type)
{
return expected_field_error(std::string(name), type);
}

View File

@@ -232,7 +232,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024;
/** The maximum amount of MPTokenIssuance */
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
static_assert(Number::maxRep >= maxMPTokenAmount);
static_assert(Number::largestMantissa >= maxMPTokenAmount);
/** The maximum length of Data payload */
std::size_t constexpr maxDataPayloadLength = 256;

View File

@@ -522,7 +522,12 @@ STAmount::fromNumber(A const& a, Number const& number)
return STAmount{asset, intValue, 0, negative};
}
auto const [mantissa, exponent] = working.normalizeToRange(cMinValue, cMaxValue);
XRPL_ASSERT_PARTS(
working.signum() >= 0,
"xrpl::STAmount::fromNumber",
"non-negative Number to normalize");
auto const [mantissa, exponent] =
working.normalizeToRange(cMinValue, cMaxValue);
return STAmount{asset, mantissa, exponent, negative};
}

View File

@@ -24,7 +24,7 @@ systemName()
/** Number of drops in the genesis account. */
constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP};
static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000);
static_assert(Number::maxRep >= INITIAL_XRP.drops());
static_assert(Number::largestMantissa >= INITIAL_XRP.drops());
/** Returns true if the amount does not exceed the initial XRP in existence. */
inline bool

View File

@@ -578,7 +578,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
// The unrounded true total value of the loan.
//
// - TrueTotalPrincipalOutstanding can be computed using the algorithm
// in the ripple::detail::loanPrincipalFromPeriodicPayment function.
// in the xrpl::detail::loanPrincipalFromPeriodicPayment function.
//
// - TrueTotalInterestOutstanding = TrueTotalLoanValue -
// TrueTotalPrincipalOutstanding

View File

@@ -47,6 +47,17 @@ public:
return id_;
}
/**
* Get the SHAMapNodeID of a child node at the specified branch.
*
* @param m The branch number (0-15) indicating which child to descend to.
* In the SHAMap's 16-way radix tree, each inner node has up to
* 16 children, indexed by the corresponding nibble (4 bits) of
* the key at the current depth.
* @return SHAMapNodeID of the child node at branch m.
* @throws std::logic_error if this node is at the maximum leaf depth (64)
* or if the node's id doesn't match its depth mask.
*/
SHAMapNodeID
getChildNodeID(unsigned int m) const;

View File

@@ -1,29 +1,25 @@
# The idea is to empty this file gradually by fixing the underlying issues and removing suppressions.
# The idea is to empty this file gradually by fixing the underlying issues and removing suppresions.
#
# ASAN_OPTIONS="print_stacktrace=1:detect_container_overflow=0:suppressions=sanitizers/suppressions/asan.supp:halt_on_error=0"
# ASAN_OPTIONS="suppressions=sanitizers/suppressions/asan.supp:halt_on_error=0:detect_stack_use_after_return=0"
#
# The detect_container_overflow=0 option disables false positives from:
# - Boost intrusive containers (slist_iterator.hpp, hashtable.hpp, aged_unordered_container.h)
# - Boost context/coroutine stack switching (Workers.cpp, thread.h)
# Boost coroutines cause multiple ASAN false positives due to swapcontext/fiber stack switching.
# ASAN cannot correctly track stack memory across coroutine context switches, leading to:
# - stack-use-after-return errors
# - stack-use-after-scope errors
# - stack-buffer-overflow errors in seemingly unrelated code (e.g., std::chrono::steady_clock::now())
# - stack-buffer-underflow errors in seemingly unrelated code (e.g., xxhasher::retrieveHash(), clock_gettime)
#
# See: https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow
# These are suppressed via:
# 1. Runtime option: detect_stack_use_after_return=0 (in ASAN_OPTIONS in CI workflow)
# 2. Compile-time flag: -fno-sanitize-address-use-after-scope (in cmake/XrplSanitizers.cmake)
#
# Note: stack-buffer-overflow false positives from coroutines cannot be fully suppressed
# without disabling ASAN entirely for Boost. Clang builds use -fsanitize-blacklist to
# exclude Boost headers, but GCC does not support this feature.
#
# See: https://github.com/google/sanitizers/issues/189
# Boost
interceptor_name:boost/asio
# Leaks in Doctest tests: xrpl.test.*
interceptor_name:src/libxrpl/net/HTTPClient.cpp
interceptor_name:src/libxrpl/net/RegisterSSLCerts.cpp
interceptor_name:src/tests/libxrpl/net/HTTPClient.cpp
interceptor_name:xrpl/net/AutoSocket.h
interceptor_name:xrpl/net/HTTPClient.h
interceptor_name:xrpl/net/HTTPClientSSLContext.h
interceptor_name:xrpl/net/RegisterSSLCerts.h
# Suppress false positive stack-buffer errors in thread stack allocation
# Related to ASan's __asan_handle_no_return warnings (github.com/google/sanitizers/issues/189)
# These occur during multi-threaded test initialization on macOS
interceptor_name:memcpy
# Boost - false positives from stackful coroutines
interceptor_name:clock_gettime
interceptor_name:__bzero
interceptor_name:__asan_memset
interceptor_name:__asan_memcpy
interceptor_name:nudb

View File

@@ -1,16 +1,13 @@
# The idea is to empty this file gradually by fixing the underlying issues and removing suppresions.
# Suppress leaks detected by asan in rippled code.
leak:src/libxrpl/net/HTTPClient.cpp
leak:src/libxrpl/net/RegisterSSLCerts.cpp
leak:src/tests/libxrpl/net/HTTPClient.cpp
leak:xrpl/net/AutoSocket.h
leak:xrpl/net/HTTPClient.h
leak:xrpl/net/HTTPClientSSLContext.h
leak:xrpl/net/RegisterSSLCerts.h
leak:ripple::HTTPClient
leak:ripple::HTTPClientImp
# Suppress leaks detected by asan in boost code.
leak:boost::asio
leak:boost/asio
# These are false positives from Boost.Asio SSL internals that use OpenSSL BIO structures.
# The BIO structures are managed by OpenSSL's internal reference counting and freed at process exit.
#leak:boost::asio
#leak:boost/asio
# OpenSSL BIO memory is managed internally and freed at process exit
leak:CRYPTO_malloc
leak:bio_make_pair
leak:BIO_new_bio_pair

View File

@@ -0,0 +1,4 @@
detect_container_overflow=0
detect_stack_use_after_return=0
debug=true
halt_on_error=false

View File

@@ -0,0 +1 @@
halt_on_error=false

View File

@@ -0,0 +1,3 @@
halt_on_error=false
verbosity=1
second_deadlock_stack=1

View File

@@ -0,0 +1 @@
halt_on_error=false

View File

@@ -27,3 +27,8 @@ src:core/JobQueue.cpp
src:libxrpl/beast/utility/beast_Journal.cpp
src:test/beast/beast_PropertyStream_test.cpp
src:src/test/app/Invariants_test.cpp
# Boost coroutines cause false positive stack-buffer-underflow in xxhasher
# This is a known ASAN limitation with stackful coroutines
# See: https://github.com/google/sanitizers/issues/189
src:beast/hash/xxhasher.h

View File

@@ -140,6 +140,7 @@ unsigned-integer-overflow:src/libxrpl/protocol/tokens.cpp
unsigned-integer-overflow:src/libxrpl/shamap/SHAMap.cpp
unsigned-integer-overflow:src/test/app/Batch_test.cpp
unsigned-integer-overflow:src/test/app/Invariants_test.cpp
unsigned-integer-overflow:src/test/app/Loan_test.cpp
unsigned-integer-overflow:src/test/app/NFToken_test.cpp
unsigned-integer-overflow:src/test/app/Offer_test.cpp
unsigned-integer-overflow:src/test/app/Path_test.cpp

View File

@@ -11,18 +11,16 @@
#include <numeric>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#ifdef _MSC_VER
#pragma message("Using boost::multiprecision::uint128_t and int128_t")
#include <boost/multiprecision/cpp_int.hpp>
using uint128_t = boost::multiprecision::uint128_t;
using int128_t = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
using int128_t = __int128_t;
#endif // !defined(_MSC_VER)
#endif
using uint128_t = xrpl::detail::uint128_t;
using int128_t = xrpl::detail::int128_t;
namespace xrpl {
@@ -61,9 +59,6 @@ Number::setMantissaScale(MantissaRange::mantissa_scale scale)
// precision to an operation. This enables the final result
// to be correctly rounded to the internal precision of Number.
template <class T>
concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
class Number::Guard
{
std::uint64_t digits_; // 16 decimal guard digits
@@ -99,7 +94,7 @@ public:
round() noexcept;
// Modify the result to the correctly rounded value
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
doRoundUp(
bool& negative,
@@ -107,22 +102,22 @@ public:
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa,
std::string location);
std::string_view location);
// Modify the result to the correctly rounded value
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
// Modify the result to the correctly rounded value
void
doRound(rep& drops, std::string location);
doRound(rep& drops, std::string_view location);
private:
void
doPush(unsigned d) noexcept;
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
};
@@ -209,7 +204,7 @@ Number::Guard::round() noexcept
return 0;
}
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa)
{
@@ -224,13 +219,13 @@ Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent, intern
{
constexpr Number zero = Number{};
negative = zero.negative_;
negative = false;
mantissa = zero.mantissa_;
exponent = zero.exponent_;
}
}
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
Number::Guard::doRoundUp(
bool& negative,
@@ -238,7 +233,7 @@ Number::Guard::doRoundUp(
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa,
std::string location)
std::string_view location)
{
auto r = round();
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
@@ -246,7 +241,7 @@ Number::Guard::doRoundUp(
++mantissa;
// Ensure mantissa after incrementing fits within both the
// min/maxMantissa range and is a valid "rep".
if (mantissa > maxMantissa || mantissa > maxRep)
if (mantissa > maxMantissa)
{
mantissa /= 10;
++exponent;
@@ -254,10 +249,10 @@ Number::Guard::doRoundUp(
}
bringIntoRange(negative, mantissa, exponent, minMantissa);
if (exponent > maxExponent)
throw std::overflow_error(location);
Throw<std::overflow_error>(std::string{location});
}
template <UnsignedMantissa T>
template <detail::UnsignedMantissa T>
void
Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa)
{
@@ -276,12 +271,13 @@ Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent, internalr
// Modify the result to the correctly rounded value
void
Number::Guard::doRound(rep& drops, std::string location)
Number::Guard::doRound(rep& drops, std::string_view location)
{
auto r = round();
if (r == 1 || (r == 0 && (drops & 1) == 1))
{
if (drops >= maxRep)
auto const& range = range_.get();
if (drops >= range.max)
{
static_assert(sizeof(internalrep) == sizeof(rep));
// This should be impossible, because it's impossible to represent
@@ -290,7 +286,8 @@ Number::Guard::doRound(rep& drops, std::string location)
// or "(maxRep + 1) / 10", neither of which will round up when
// converting to rep, though the latter might overflow _before_
// rounding.
throw std::overflow_error(location); // LCOV_EXCL_LINE
Throw<std::overflow_error>(
std::string{location}); // LCOV_EXCL_LINE
}
++drops;
}
@@ -310,23 +307,144 @@ Number::externalToInternal(rep mantissa)
// If the mantissa is already positive, just return it
if (mantissa >= 0)
return mantissa;
// If the mantissa is negative, but fits within the positive range of rep,
// return it negated
if (mantissa >= -std::numeric_limits<rep>::max())
return -mantissa;
// If the mantissa doesn't fit within the positive range, convert to
// int128_t, negate that, and cast it back down to the internalrep
// In practice, this is only going to cover the case of
// std::numeric_limits<rep>::min().
int128_t temp = mantissa;
return static_cast<internalrep>(-temp);
// Cast to unsigned before negating to avoid undefined behavior
// when v == INT64_MIN (negating INT64_MIN in signed is UB)
return -static_cast<internalrep>(mantissa);
}
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.log digits.
*
*/
template <detail::UnsignedMantissa Rep>
std::tuple<bool, Rep, int>
Number::toInternal(MantissaRange const& range) const
{
auto exponent = exponent_;
bool const negative = mantissa_ < 0;
auto const sign = negative ? -1 : 1;
Rep mantissa = static_cast<Rep>(sign * mantissa_);
auto const referenceMin = range.referenceMin;
auto const minMantissa = range.min;
if (mantissa != 0 && mantissa >= minMantissa && mantissa < referenceMin)
{
// Ensure the mantissa has the correct number of digits
mantissa *= 10;
--exponent;
XRPL_ASSERT_PARTS(
mantissa >= referenceMin && mantissa < referenceMin * 10,
"xrpl::Number::toInternal()",
"Number is within reference range and has 'log' digits");
}
return {negative, mantissa, exponent};
}
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.log digits.
*
*/
template <detail::UnsignedMantissa Rep>
std::tuple<bool, Rep, int>
Number::toInternal() const
{
return toInternal(range_);
}
/** Rebuilds the number from components.
*
* If "normalized" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "normalized" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <bool expectNormal, detail::UnsignedMantissa Rep>
void
Number::fromInternal(
bool negative,
Rep mantissa,
int exponent,
MantissaRange const* pRange)
{
if constexpr (std::is_same_v<
std::bool_constant<expectNormal>,
std::false_type>)
{
if (!pRange)
throw std::runtime_error("Missing range to Number::fromInternal!");
auto const& range = *pRange;
auto const maxMantissa = range.max;
auto const minMantissa = range.min;
XRPL_ASSERT_PARTS(
mantissa >= minMantissa,
"xrpl::Number::fromInternal",
"mantissa large enough");
if (mantissa > maxMantissa || mantissa < minMantissa)
{
normalize(negative, mantissa, exponent, range.min, maxMantissa);
}
XRPL_ASSERT_PARTS(
mantissa >= minMantissa && mantissa <= maxMantissa,
"xrpl::Number::fromInternal",
"mantissa in range");
}
auto const sign = negative ? -1 : 1;
mantissa_ = sign * static_cast<rep>(mantissa);
exponent_ = exponent;
XRPL_ASSERT_PARTS(
(pRange && isnormal(*pRange)) || isnormal(),
"xrpl::Number::fromInternal",
"Number is normalized");
}
/** Rebuilds the number from components.
*
* If "normalized" is true, the values are expected to be normalized - all in
* their valid ranges.
*
* If "normalized" is false, the values are expected to be "near normalized",
* meaning that the mantissa has to be modified at most once to bring it back
* into range.
*
*/
template <bool expectNormal, detail::UnsignedMantissa Rep>
void
Number::fromInternal(bool negative, Rep mantissa, int exponent)
{
MantissaRange const* pRange = nullptr;
if constexpr (std::is_same_v<
std::bool_constant<expectNormal>,
std::false_type>)
{
pRange = &Number::range_.get();
}
fromInternal(negative, mantissa, exponent, pRange);
}
constexpr Number
Number::oneSmall()
{
return Number{false, Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}};
return Number{
false,
Number::smallRange.referenceMin,
-Number::smallRange.log,
Number::unchecked{}};
};
constexpr Number oneSml = Number::oneSmall();
@@ -334,101 +452,101 @@ constexpr Number oneSml = Number::oneSmall();
constexpr Number
Number::oneLarge()
{
return Number{false, Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}};
return Number{
false,
Number::largeRange.referenceMin,
-Number::largeRange.log,
Number::unchecked{}};
};
constexpr Number oneLrg = Number::oneLarge();
Number
Number::one()
Number::one(MantissaRange const& range)
{
if (&range_.get() == &smallRange)
if (&range == &smallRange)
return oneSml;
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_");
XRPL_ASSERT(&range == &largeRange, "Number::one() : valid range");
return oneLrg;
}
Number
Number::one()
{
return one(range_);
}
// Use the member names in this static function for now so the diff is cleaner
// TODO: Rename the function parameters to get rid of the "_" suffix
template <class T>
void
doNormalize(
bool& negative,
T& mantissa_,
int& exponent_,
T& mantissa,
int& exponent,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa)
{
auto constexpr minExponent = Number::minExponent;
auto constexpr maxExponent = Number::maxExponent;
auto constexpr maxRep = Number::maxRep;
using Guard = Number::Guard;
constexpr Number zero = Number{};
if (mantissa_ == 0)
if (mantissa == 0 || (mantissa < minMantissa && exponent <= minExponent))
{
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
negative = zero.negative_;
mantissa = zero.mantissa_;
exponent = zero.exponent_;
negative = false;
return;
}
auto m = mantissa_;
while ((m < minMantissa) && (exponent_ > minExponent))
auto m = mantissa;
while ((m < minMantissa) && (exponent > minExponent))
{
m *= 10;
--exponent_;
--exponent;
}
Guard g;
if (negative)
g.set_negative();
while (m > maxMantissa)
{
if (exponent_ >= maxExponent)
if (exponent >= maxExponent)
throw std::overflow_error("Number::normalize 1");
g.push(m % 10);
m /= 10;
++exponent_;
++exponent;
}
if ((exponent_ < minExponent) || (m < minMantissa))
if ((exponent < minExponent) || (m == 0))
{
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
negative = zero.negative_;
mantissa = zero.mantissa_;
exponent = zero.exponent_;
negative = false;
return;
}
// When using the largeRange, "m" needs fit within an int64, even if
// the final mantissa_ is going to end up larger to fit within the
// MantissaRange. Cut it down here so that the rounding will be done while
// it's smaller.
//
// Example: 9,900,000,000,000,123,456 > 9,223,372,036,854,775,807,
// so "m" will be modified to 990,000,000,000,012,345. Then that value
// will be rounded to 990,000,000,000,012,345 or
// 990,000,000,000,012,346, depending on the rounding mode. Finally,
// mantissa_ will be "m*10" so it fits within the range, and end up as
// 9,900,000,000,000,123,450 or 9,900,000,000,000,123,460.
// mantissa() will return mantissa_ / 10, and exponent() will return
// exponent_ + 1.
if (m > maxRep)
{
if (exponent_ >= maxExponent)
throw std::overflow_error("Number::normalize 1.5");
g.push(m % 10);
m /= 10;
++exponent_;
}
// Before modification, m should be within the min/max range. After
// modification, it must be less than maxRep. In other words, the original
// value should have been no more than maxRep * 10.
// (maxRep * 10 > maxMantissa)
XRPL_ASSERT_PARTS(m <= maxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64");
mantissa_ = m;
g.doRoundUp(negative, mantissa_, exponent_, minMantissa, maxMantissa, "Number::normalize 2");
XRPL_ASSERT_PARTS(
mantissa_ >= minMantissa && mantissa_ <= maxMantissa, "xrpl::doNormalize", "final mantissa fits in range");
m <= maxMantissa,
"xrpl::doNormalize",
"intermediate mantissa fits in int64");
mantissa = m;
g.doRoundUp(
negative,
mantissa,
exponent,
minMantissa,
maxMantissa,
"Number::normalize 2");
XRPL_ASSERT_PARTS(
mantissa >= minMantissa && mantissa <= maxMantissa,
"xrpl::doNormalize",
"final mantissa fits in range");
XRPL_ASSERT_PARTS(
exponent >= minExponent && exponent <= maxExponent,
"xrpl::doNormalize",
"final exponent fits in range");
}
template <>
@@ -467,11 +585,20 @@ Number::normalize<unsigned long>(
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa);
}
void
Number::normalize(MantissaRange const& range)
{
auto [negative, mantissa, exponent] = toInternal(range);
normalize(negative, mantissa, exponent, range.min, range.max);
fromInternal(negative, mantissa, exponent, &range);
}
void
Number::normalize()
{
auto const& range = range_.get();
normalize(negative_, mantissa_, exponent_, range.min, range.max);
normalize(range_);
}
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
@@ -481,21 +608,33 @@ Number
Number::shiftExponent(int exponentDelta) const
{
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized");
auto const newExponent = exponent_ + exponentDelta;
if (newExponent >= maxExponent)
Number result = *this;
result.exponent_ += exponentDelta;
if (result.exponent_ >= maxExponent)
throw std::overflow_error("Number::shiftExponent");
if (newExponent < minExponent)
if (result.exponent_ < minExponent)
{
return Number{};
}
Number const result{negative_, mantissa_, newExponent, unchecked{}};
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::Number::shiftExponent", "result is normalized");
return result;
}
Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
{
auto const& range = range_.get();
normalize(negative, mantissa, exponent, range.min, range.max);
fromInternal(negative, mantissa, exponent, &range);
}
Number&
Number::operator+=(Number const& y)
{
auto const& range = range_.get();
constexpr Number zero = Number{};
if (y == zero)
return *this;
@@ -510,7 +649,9 @@ Number::operator+=(Number const& y)
return *this;
}
XRPL_ASSERT(isnormal() && y.isnormal(), "xrpl::Number::operator+=(Number) : is normal");
XRPL_ASSERT(
isnormal(range) && y.isnormal(range),
"xrpl::Number::operator+=(Number) : is normal");
// *n = negative
// *s = sign
// *m = mantissa
@@ -518,13 +659,10 @@ Number::operator+=(Number const& y)
// Need to use uint128_t, because large mantissas can overflow when added
// together.
bool xn = negative_;
uint128_t xm = mantissa_;
auto xe = exponent_;
auto [xn, xm, xe] = toInternal<uint128_t>(range);
auto [yn, ym, ye] = y.toInternal<uint128_t>(range);
bool yn = y.negative_;
uint128_t ym = y.mantissa_;
auto ye = y.exponent_;
Guard g;
if (xe < ye)
{
@@ -549,14 +687,13 @@ Number::operator+=(Number const& y)
} while (xe > ye);
}
auto const& range = range_.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
if (xn == yn)
{
xm += ym;
if (xm > maxMantissa || xm > maxRep)
if (xm > maxMantissa)
{
g.push(xm % 10);
xm /= 10;
@@ -576,7 +713,7 @@ Number::operator+=(Number const& y)
xe = ye;
xn = yn;
}
while (xm < minMantissa && xm * 10 <= maxRep)
while (xm < minMantissa)
{
xm *= 10;
xm -= g.pop();
@@ -585,10 +722,8 @@ Number::operator+=(Number const& y)
g.doRoundDown(xn, xm, xe, minMantissa);
}
negative_ = xn;
mantissa_ = static_cast<internalrep>(xm);
exponent_ = xe;
normalize();
normalize(xn, xm, xe, minMantissa, maxMantissa);
fromInternal(xn, xm, xe, &range);
return *this;
}
@@ -623,6 +758,8 @@ divu10(uint128_t& u)
Number&
Number::operator*=(Number const& y)
{
auto const& range = range_.get();
constexpr Number zero = Number{};
if (*this == zero)
return *this;
@@ -636,15 +773,11 @@ Number::operator*=(Number const& y)
// *m = mantissa
// *e = exponent
bool xn = negative_;
auto [xn, xm, xe] = toInternal(range);
int xs = xn ? -1 : 1;
internalrep xm = mantissa_;
auto xe = exponent_;
bool yn = y.negative_;
auto [yn, ym, ye] = y.toInternal(range);
int ys = yn ? -1 : 1;
internalrep ym = y.mantissa_;
auto ye = y.exponent_;
auto zm = uint128_t(xm) * uint128_t(ym);
auto ze = xe + ye;
@@ -654,11 +787,10 @@ Number::operator*=(Number const& y)
if (zn)
g.set_negative();
auto const& range = range_.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
while (zm > maxMantissa || zm > maxRep)
while (zm > maxMantissa)
{
// The following is optimization for:
// g.push(static_cast<unsigned>(zm % 10));
@@ -669,18 +801,23 @@ Number::operator*=(Number const& y)
xm = static_cast<internalrep>(zm);
xe = ze;
g.doRoundUp(
zn, xm, xe, minMantissa, maxMantissa, "Number::multiplication overflow : exponent is " + std::to_string(xe));
negative_ = zn;
mantissa_ = xm;
exponent_ = xe;
zn,
xm,
xe,
minMantissa,
maxMantissa,
"Number::multiplication overflow : exponent is " + std::to_string(xe));
normalize();
normalize(zn, xm, xe, minMantissa, maxMantissa);
fromInternal(zn, xm, xe, &range);
return *this;
}
Number&
Number::operator/=(Number const& y)
{
auto const& range = range_.get();
constexpr Number zero = Number{};
if (y == zero)
throw std::overflow_error("Number: divide by 0");
@@ -693,17 +830,12 @@ Number::operator/=(Number const& y)
// *m = mantissa
// *e = exponent
bool np = negative_;
auto [np, nm, ne] = toInternal(range);
int ns = (np ? -1 : 1);
auto nm = mantissa_;
auto ne = exponent_;
bool dp = y.negative_;
auto [dp, dm, de] = y.toInternal(range);
int ds = (dp ? -1 : 1);
auto dm = y.mantissa_;
auto de = y.exponent_;
auto const& range = range_.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
@@ -715,9 +847,11 @@ Number::operator/=(Number const& y)
// f can be up to 10^(38-19) = 10^19 safely
static_assert(smallRange.log == 15);
static_assert(largeRange.log == 18);
bool small = Number::getMantissaScale() == MantissaRange::small;
uint128_t const f = small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
XRPL_ASSERT_PARTS(f >= minMantissa * 10, "Number::operator/=", "factor expected size");
bool small = range.scale == MantissaRange::small;
uint128_t const f =
small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
XRPL_ASSERT_PARTS(
f >= minMantissa * 10, "Number::operator/=", "factor expected size");
// unsigned denominator
auto const dmu = static_cast<uint128_t>(dm);
@@ -765,10 +899,9 @@ Number::operator/=(Number const& y)
}
}
normalize(zn, zm, ze, minMantissa, maxMantissa);
negative_ = zn;
mantissa_ = static_cast<internalrep>(zm);
exponent_ = ze;
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::operator/=", "result is normalized");
fromInternal(zn, zm, ze, &range);
XRPL_ASSERT_PARTS(
isnormal(range), "xrpl::Number::operator/=", "result is normalized");
return *this;
}
@@ -780,7 +913,7 @@ Number::operator rep() const
Guard g;
if (drops != 0)
{
if (negative_)
if (drops < 0)
{
g.set_negative();
drops = -drops;
@@ -792,7 +925,7 @@ Number::operator rep() const
}
for (; offset > 0; --offset)
{
if (drops > maxRep / 10)
if (drops >= largeRange.min)
throw std::overflow_error("Number::operator rep() overflow");
drops *= 10;
}
@@ -822,28 +955,35 @@ Number::truncate() const noexcept
std::string
to_string(Number const& amount)
{
auto const& range = Number::range_.get();
// keep full internal accuracy, but make more human friendly if possible
constexpr Number zero = Number{};
if (amount == zero)
return "0";
auto exponent = amount.exponent_;
auto mantissa = amount.mantissa_;
bool const negative = amount.negative_;
// The mantissa must have a set number of decimal places for this to work
auto [negative, mantissa, exponent] = amount.toInternal(range);
// Use scientific notation for exponents that are too small or too large
auto const rangeLog = Number::mantissaLog();
if (((exponent != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
auto const rangeLog = range.log;
if (((exponent != 0 && amount.exponent() != 0) &&
((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
{
while (mantissa != 0 && mantissa % 10 == 0 && exponent < Number::maxExponent)
// Remove trailing zeroes from the mantissa.
while (mantissa != 0 && mantissa % 10 == 0 &&
exponent < Number::maxExponent)
{
mantissa /= 10;
++exponent;
}
std::string ret = negative ? "-" : "";
ret.append(std::to_string(mantissa));
ret.append(1, 'e');
ret.append(std::to_string(exponent));
if (exponent != 0)
{
ret.append(1, 'e');
ret.append(std::to_string(exponent));
}
return ret;
}
@@ -925,20 +1065,11 @@ power(Number const& f, unsigned n)
return r;
}
// Returns f^(1/d)
// Uses NewtonRaphson iterations until the result stops changing
// to find the non-negative root of the polynomial g(x) = x^d - f
// This function, and power(Number f, unsigned n, unsigned d)
// treat corner cases such as 0 roots as advised by Annex F of
// the C standard, which itself is consistent with the IEEE
// floating point standards.
Number
root(Number f, unsigned d)
Number::root(MantissaRange const& range, Number f, unsigned d)
{
constexpr Number zero = Number{};
auto const one = Number::one();
auto const one = Number::one(range);
if (f == one || d == 1)
return f;
@@ -955,21 +1086,30 @@ root(Number f, unsigned d)
if (f == zero)
return f;
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
auto e = f.exponent_ + Number::mantissaLog() + 1;
auto const di = static_cast<int>(d);
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
{
int k = (e >= 0 ? e : e - (di - 1)) / di;
int k2 = e - k * di;
if (k2 == 0)
return 0;
return di - k2;
}();
e += ex;
f = f.shiftExponent(-e); // f /= 10^e;
auto const [e, di] = [&]() {
auto const [negative, mantissa, exponent] = f.toInternal(range);
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root(Number, unsigned)", "f is normalized");
// Scale f into the range (0, 1) such that the scale change (e) is a
// multiple of the root (d)
auto e = exponent + range.log + 1;
auto const di = static_cast<int>(d);
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
{
int k = (e >= 0 ? e : e - (di - 1)) / di;
int k2 = e - k * di;
if (k2 == 0)
return 0;
return di - k2;
}();
e += ex;
f = f.shiftExponent(-e); // f /= 10^e;
return std::make_tuple(e, di);
}();
XRPL_ASSERT_PARTS(
e % di == 0, "xrpl::root(Number, unsigned)", "e is divisible by d");
XRPL_ASSERT_PARTS(
f.isnormal(range), "xrpl::root(Number, unsigned)", "f is normalized");
bool neg = false;
if (f < zero)
{
@@ -1002,15 +1142,35 @@ root(Number f, unsigned d)
// return r * 10^(e/d) to reverse scaling
auto const result = r.shiftExponent(e / di);
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root(Number, unsigned)", "result is normalized");
XRPL_ASSERT_PARTS(
result.isnormal(range),
"xrpl::root(Number, unsigned)",
"result is normalized");
return result;
}
// Returns f^(1/d)
// Uses NewtonRaphson iterations until the result stops changing
// to find the non-negative root of the polynomial g(x) = x^d - f
// This function, and power(Number f, unsigned n, unsigned d)
// treat corner cases such as 0 roots as advised by Annex F of
// the C standard, which itself is consistent with the IEEE
// floating point standards.
Number
root(Number f, unsigned d)
{
auto const& range = Number::range_.get();
return Number::root(range, f, d);
}
Number
root2(Number f)
{
auto const& range = Number::range_.get();
constexpr Number zero = Number{};
auto const one = Number::one();
auto const one = Number::one(range);
if (f == one)
return f;
@@ -1019,12 +1179,19 @@ root2(Number f)
if (f == zero)
return f;
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
auto e = f.exponent_ + Number::mantissaLog() + 1;
if (e % 2 != 0)
++e;
f = f.shiftExponent(-e); // f /= 10^e;
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root2(Number)", "f is normalized");
auto const e = [&]() {
auto const [negative, mantissa, exponent] = f.toInternal(range);
// Scale f into the range (0, 1) such that f's exponent is a
// multiple of d
auto e = exponent + range.log + 1;
if (e % 2 != 0)
++e;
f = f.shiftExponent(-e); // f /= 10^e;
return e;
}();
XRPL_ASSERT_PARTS(
f.isnormal(range), "xrpl::root2(Number)", "f is normalized");
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
auto const D = 105;
@@ -1046,7 +1213,8 @@ root2(Number f)
// return r * 10^(e/2) to reverse scaling
auto const result = r.shiftExponent(e / 2);
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root2(Number)", "result is normalized");
XRPL_ASSERT_PARTS(
result.isnormal(range), "xrpl::root2(Number)", "result is normalized");
return result;
}
@@ -1056,8 +1224,10 @@ root2(Number f)
Number
power(Number const& f, unsigned n, unsigned d)
{
auto const& range = Number::range_.get();
constexpr Number zero = Number{};
auto const one = Number::one();
auto const one = Number::one(range);
if (f == one)
return f;
@@ -1079,7 +1249,7 @@ power(Number const& f, unsigned n, unsigned d)
d /= g;
if ((n % 2) == 1 && (d % 2) == 0 && f < zero)
throw std::overflow_error("Number::power nan");
return root(power(f, n), d);
return Number::root(range, power(f, n), d);
}
} // namespace xrpl

View File

@@ -191,17 +191,17 @@ Value::Value(ValueType type) : type_(type), allocated_(0)
}
}
Value::Value(Int value) : type_(intValue)
Value::Value(Int value) : type_(intValue), allocated_(0)
{
value_.int_ = value;
}
Value::Value(UInt value) : type_(uintValue)
Value::Value(UInt value) : type_(uintValue), allocated_(0)
{
value_.uint_ = value;
}
Value::Value(double value) : type_(realValue)
Value::Value(double value) : type_(realValue), allocated_(0)
{
value_.real_ = value;
}
@@ -227,7 +227,7 @@ Value::Value(StaticString const& value) : type_(stringValue), allocated_(false)
value_.string_ = const_cast<char*>(value.c_str());
}
Value::Value(bool value) : type_(booleanValue)
Value::Value(bool value) : type_(booleanValue), allocated_(0)
{
value_.bool_ = value;
}

View File

@@ -26,6 +26,12 @@ HTTPClient::initializeSSLContext(
httpClientSSLContext.emplace(sslVerifyDir, sslVerifyFile, sslVerify, j);
}
void
HTTPClient::cleanupSSLContext()
{
httpClientSSLContext.reset();
}
//------------------------------------------------------------------------------
//
// Fetch a web page via http or https.

View File

@@ -85,8 +85,7 @@ registerSSLCerts(boost::asio::ssl::context& ctx, boost::system::error_code& ec,
// There is a very unpleasant interaction between <wincrypt> and
// openssl x509 types (namely the former has macros that stomp
// on the latter), these undefs allow this TU to be safely used in
// unity builds without messing up subsequent TUs. Although we
// no longer use unity builds, leaving the undefs here does no harm.
// unity builds without messing up subsequent TUs.
#if BOOST_OS_WINDOWS
#undef X509_NAME
#undef X509_EXTENSIONS

View File

@@ -69,7 +69,7 @@ make_name(std::string const& object, std::string const& field)
if (field.empty())
return object;
return object + "." + field;
return {object + "." + field};
}
static inline Json::Value

View File

@@ -67,7 +67,8 @@ SHAMapNodeID::getChildNodeID(unsigned int m) const
if (depth_ >= SHAMap::leafDepth)
Throw<std::logic_error>("Request for child node ID of " + to_string(*this));
if (id_ != (id_ & depthMask(depth_)))
auto const idAtDepth = id_ & depthMask(depth_);
if (id_ != idAtDepth)
Throw<std::logic_error>("Incorrect mask for " + to_string(*this));
SHAMapNodeID node{depth_ + 1, id_};

View File

@@ -148,7 +148,7 @@ private:
std::vector<std::string> emptyCfgKeys;
struct publisher
{
publisher(FetchListConfig const& c) : cfg{c}
publisher(FetchListConfig const& c) : cfg{c}, isRetry{false}
{
}
std::shared_ptr<TrustedPublisherServer> server;

View File

@@ -32,9 +32,10 @@ public:
test_limits()
{
auto const scale = Number::getMantissaScale();
testcase << "test_limits " << to_string(scale);
bool caught = false;
auto const minMantissa = Number::minMantissa();
testcase << "test_limits " << to_string(scale) << ", " << minMantissa;
bool caught = false;
try
{
Number x = Number{false, minMantissa * 10, 32768, Number::normalized{}};
@@ -58,8 +59,9 @@ public:
__LINE__);
test(Number{false, minMantissa, -32769, Number::normalized{}}, Number{}, __LINE__);
test(
// Use 1501 to force rounding up
Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 +
Number{false, 1'500, 32000, Number::normalized{}},
Number{false, 1'501, 32000, Number::normalized{}},
Number{false, minMantissa + 2, 32003, Number::normalized{}},
__LINE__);
// 9,223,372,036,854,775,808
@@ -159,8 +161,8 @@ public:
{Number{true, 9'999'999'999'999'999'999ULL, -37, Number::normalized{}},
Number{1'000'000'000'000'000'000, -18},
Number{false, 9'999'999'999'999'999'990ULL, -19, Number::normalized{}}},
{Number{Number::maxRep}, Number{6, -1}, Number{Number::maxRep / 10, 1}},
{Number{Number::maxRep - 1}, Number{1, 0}, Number{Number::maxRep}},
{Number{Number::largestMantissa}, Number{6, -1}, Number{Number::largestMantissa / 10, 1}},
{Number{Number::largestMantissa - 1}, Number{1, 0}, Number{Number::largestMantissa}},
// Test extremes
{
// Each Number operand rounds up, so the actual mantissa is
@@ -170,11 +172,18 @@ public:
Number{2, 19},
},
{
// Does not round. Mantissas are going to be > maxRep, so if
// added together as uint64_t's, the result will overflow.
// With addition using uint128_t, there's no problem. After
// normalizing, the resulting mantissa ends up less than
// maxRep.
// Does not round. Mantissas are going to be >
// largestMantissa, so if added together as uint64_t's, the
// result will overflow. With addition using uint128_t,
// there's no problem. After normalizing, the resulting
// mantissa ends up less than largestMantissa.
Number{false, Number::largestMantissa, 0, Number::normalized{}},
Number{false, Number::largestMantissa, 0, Number::normalized{}},
Number{false, Number::largestMantissa * 2, 0, Number::normalized{}},
},
{
// These mantissas round down, so adding them together won't
// have any consequences.
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::normalized{}},
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::normalized{}},
Number{false, 1'999'999'999'999'999'998ULL, 1, Number::normalized{}},
@@ -261,12 +270,14 @@ public:
{Number{1'000'000'000'000'000'001, -18},
Number{1'000'000'000'000'000'000, -18},
Number{1'000'000'000'000'000'000, -36}},
{Number{Number::maxRep}, Number{6, -1}, Number{Number::maxRep - 1}},
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
{Number{Number::largestMantissa}, Number{6, -1}, Number{Number::largestMantissa - 1}},
{Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
Number{1, 0},
Number{Number::maxRep / 10 + 1, 1}},
{Number{false, Number::maxRep + 1, 0, Number::normalized{}}, Number{3, 0}, Number{Number::maxRep}},
{power(2, 63), Number{3, 0}, Number{Number::maxRep}},
Number{Number::largestMantissa / 10 + 1, 1}},
{Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
Number{3, 0},
Number{Number::largestMantissa}},
{power(2, 63), Number{3, 0}, Number{Number::largestMantissa}},
});
auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c)
@@ -289,14 +300,16 @@ public:
auto const scale = Number::getMantissaScale();
testcase << "test_mul " << to_string(scale);
using Case = std::tuple<Number, Number, Number>;
// Case: Factor 1, Factor 2, Expected product, Line number
using Case = std::tuple<Number, Number, Number, int>;
auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c)
for (auto const& [x, y, z, line] : c)
{
auto const result = x * y;
std::stringstream ss;
ss << x << " * " << y << " = " << result << ". Expected: " << z;
BEAST_EXPECTS(result == z, ss.str());
BEAST_EXPECTS(
result == z, ss.str() + " line: " + std::to_string(line));
}
};
auto tests = [&](auto const& cSmall, auto const& cLarge) {
@@ -306,48 +319,105 @@ public:
test(cLarge);
};
auto const maxMantissa = Number::maxMantissa();
auto const maxInternalMantissa =
static_cast<std::uint64_t>(
static_cast<std::int64_t>(power(10, Number::mantissaLog()))) *
10 -
1;
saveNumberRoundMode save{Number::setround(Number::to_nearest)};
{
auto const cSmall = std::to_array<Case>({
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{2000000000000000, -15}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-2000000000000000, -15}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{2000000000000000, -15}},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{1000000000000000, -14}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}},
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{1000000000000000, -14},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__},
// Maximum mantissa range
{Number{9'999'999'999'999'999, 0}, Number{9'999'999'999'999'999, 0}, Number{9'999'999'999'999'998, 16}},
{Number{9'999'999'999'999'999, 0},
Number{9'999'999'999'999'999, 0},
Number{9'999'999'999'999'998, 16},
__LINE__},
});
auto const cLarge = std::to_array<Case>({
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999862, -18}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999862, -18}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999862, -18}},
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}},
Number{
false,
9'999'999'999'999'999'579ULL,
-18,
Number::normalized{}},
__LINE__},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0},
__LINE__},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2000000000000000001, -18}},
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999998, -18}},
Number{-1999999999999999998, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
// Maximum mantissa range - rounds up to 1e19
Number{1999999999999999999, -18},
__LINE__},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0},
__LINE__},
// Maximum internal mantissa range - rounds up to 1e19
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{1, 38},
__LINE__},
// Maximum actual mantissa range - same as int64 range
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{1, 38}},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
// Maximum int64 range
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'85, 19}},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
});
tests(cSmall, cLarge);
}
@@ -355,44 +425,100 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " towards_zero";
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999, -15}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999, -15}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999, -15}},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{9999999999999999, -15}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
{{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999861, -18}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999861, -18}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999861, -18}},
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9999999999999999579ULL, -18, Number::normalized{}}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}},
Number{
false,
9999999999999999579ULL,
-18,
Number::normalized{}},
__LINE__},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0},
__LINE__},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2, 0}},
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2, 0},
__LINE__},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999997, -18}},
Number{-1999999999999999997, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
// Maximum mantissa range - rounds down to maxMantissa/10e1
Number{1999999999999999999, -18},
__LINE__},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0},
__LINE__},
// Maximum internal mantissa range - rounds down to
// maxMantissa/10e1
// 99'999'999'999'999'999'800'000'000'000'000'000'100
{Number{
false, maxInternalMantissa, 0, Number::normalized{}},
Number{
false, maxInternalMantissa, 0, Number::normalized{}},
Number{
false,
maxInternalMantissa / 10 - 1,
20,
Number::normalized{}},
__LINE__},
// Maximum actual mantissa range - same as int64
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'84, 19}},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
});
tests(cSmall, cLarge);
}
@@ -400,44 +526,100 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " downward";
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999, -15}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-2000000000000000, -15}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999, -15}},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{9999999999999999, -15}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
{{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999861, -18}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999862, -18}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999861, -18}},
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}},
Number{
false,
9'999'999'999'999'999'579ULL,
-18,
Number::normalized{}},
__LINE__},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0},
__LINE__},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2, 0}},
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2, 0},
__LINE__},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999998, -18}},
Number{-1999999999999999998, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
// Maximum mantissa range - rounds down to maxMantissa/10e1
Number{1999999999999999999, -18},
__LINE__},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0},
__LINE__},
// Maximum internal mantissa range - rounds down to
// maxMantissa/10-1
// 99'999'999'999'999'999'800'000'000'000'000'000'100
{Number{
false, maxInternalMantissa, 0, Number::normalized{}},
Number{
false, maxInternalMantissa, 0, Number::normalized{}},
Number{
false,
maxInternalMantissa / 10 - 1,
20,
Number::normalized{}},
__LINE__},
// Maximum mantissa range - same as int64
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'84, 19}},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
});
tests(cSmall, cLarge);
}
@@ -445,44 +627,91 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " upward";
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{2000000000000000, -15}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999, -15}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{2000000000000000, -15}},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{1000000000000000, -14}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
{{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{1000000000000000, -14},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999862, -18}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999861, -18}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999862, -18}},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{999999999999999958, -17}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}},
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{999999999999999958, -17},
__LINE__},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0},
__LINE__},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18}},
Number{2000000000000000001, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999997, -18}},
{Number{-1414213562373095048, -18}, Number{-1414213562373095049, -18}, Number{2, 0}},
Number{-1999999999999999997, -18},
__LINE__},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{2, 0},
__LINE__},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{1000000000000000001, -17}},
// Maximum mantissa range - rounds up to minMantissa*10
// 1e19*1e19=1e38
Number{1000000000000000001, -17},
__LINE__},
// Maximum internal mantissa range - rounds up to
// minMantissa*10 1e19*1e19=1e38
{Number{
false, maxInternalMantissa, 0, Number::normalized{}},
Number{
false, maxInternalMantissa, 0, Number::normalized{}},
Number{1, 38},
__LINE__},
// Maximum mantissa range - same as int64
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{1, 38}},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'85, 19}},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
});
tests(cSmall, cLarge);
}
@@ -697,6 +926,12 @@ public:
};
*/
auto const maxInternalMantissa =
static_cast<std::uint64_t>(
static_cast<std::int64_t>(power(10, Number::mantissaLog()))) *
10 -
1;
auto const cSmall = std::to_array<Case>(
{{Number{2}, 2, Number{1414213562373095049, -18}},
{Number{2'000'000}, 2, Number{1414213562373095049, -15}},
@@ -708,14 +943,19 @@ public:
{Number{0}, 5, Number{0}},
{Number{5625, -4}, 2, Number{75, -2}}});
auto const cLarge = std::to_array<Case>({
{Number{false, Number::maxMantissa() - 9, -1, Number::normalized{}},
{Number{false, maxInternalMantissa - 9, -1, Number::normalized{}},
2,
Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}},
{Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}},
{Number{false, maxInternalMantissa - 9, 0, Number::normalized{}},
2,
Number{false, 3'162'277'660'168'379'330, -9, Number::normalized{}}},
{Number{Number::maxRep}, 2, Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
{Number{Number::maxRep}, 4, Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
Number{
false, 3'162'277'660'168'379'330, -9, Number::normalized{}}},
{Number{Number::largestMantissa},
2,
Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
{Number{Number::largestMantissa},
4,
Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
});
test(cSmall);
if (Number::getMantissaScale() != MantissaRange::small)
@@ -762,6 +1002,9 @@ public:
}
};
auto const maxInternalMantissa =
power(10, Number::mantissaLog()) * 10 - 1;
auto const cSmall = std::to_array<Number>({
Number{2},
Number{2'000'000},
@@ -771,7 +1014,10 @@ public:
Number{5, -1},
Number{0},
Number{5625, -4},
Number{Number::maxRep},
Number{Number::largestMantissa},
maxInternalMantissa,
Number{Number::minMantissa(), 0, Number::unchecked{}},
Number{Number::maxMantissa(), 0, Number::unchecked{}},
});
test(cSmall);
bool caught = false;
@@ -1113,16 +1359,20 @@ public:
case MantissaRange::large:
// Test the edges
// ((exponent < -(28)) || (exponent > -(8)))))
test(Number::min(), "1e-32750");
test(Number::min(), "922337203685477581e-32768");
test(Number::max(), "9223372036854775807e32768");
test(Number::lowest(), "-9223372036854775807e32768");
{
NumberRoundModeGuard mg(Number::towards_zero);
auto const maxMantissa = Number::maxMantissa();
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL);
test(Number{false, maxMantissa, 0, Number::normalized{}}, "9999999999999999990");
test(Number{true, maxMantissa, 0, Number::normalized{}}, "-9999999999999999990");
BEAST_EXPECT(maxMantissa == 9'223'372'036'854'775'807ULL);
test(
Number{false, maxMantissa, 0, Number::normalized{}},
"9223372036854775807");
test(
Number{true, maxMantissa, 0, Number::normalized{}},
"-9223372036854775807");
test(Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807");
test(-(Number{std::numeric_limits<std::int64_t>::max(), 0}), "-9223372036854775807");
@@ -1300,7 +1550,7 @@ public:
Number const initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() > 0);
Number const maxInt64{Number::maxRep};
Number const maxInt64{Number::largestMantissa};
BEAST_EXPECT(maxInt64.exponent() > 0);
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'62, 22}));
@@ -1317,20 +1567,254 @@ public:
Number const initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() <= 0);
Number const maxInt64{Number::maxRep};
Number const maxInt64{Number::largestMantissa};
BEAST_EXPECT(maxInt64.exponent() <= 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'615'85, 19}));
NumberRoundModeGuard mg(Number::towards_zero);
auto const maxMantissa = Number::maxMantissa();
Number const max = Number{false, maxMantissa, 0, Number::normalized{}};
BEAST_EXPECT(max.mantissa() == maxMantissa / 10);
BEAST_EXPECT(max.exponent() == 1);
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
BEAST_EXPECT((power(max, 2) == Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}));
{
auto const maxInternalMantissa =
static_cast<std::uint64_t>(static_cast<std::int64_t>(
power(10, Number::mantissaLog()))) *
10 -
1;
// Rounds down to fit under 2^63
Number const max =
Number{false, maxInternalMantissa, 0, Number::normalized{}};
// No alterations by the accessors
BEAST_EXPECT(max.mantissa() == maxInternalMantissa / 10);
BEAST_EXPECT(max.exponent() == 1);
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
BEAST_EXPECT(
(power(max, 2) ==
Number{
false,
maxInternalMantissa / 10 - 1,
20,
Number::normalized{}}));
}
{
auto const maxMantissa = Number::maxMantissa();
Number const max =
Number{false, maxMantissa, 0, Number::normalized{}};
// No alterations by the accessors
BEAST_EXPECT(max.mantissa() == maxMantissa);
BEAST_EXPECT(max.exponent() == 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - also 38
// digits
BEAST_EXPECT(
(power(max, 2) ==
Number{
false,
85'070'591'730'234'615'84,
19,
Number::normalized{}}));
}
}
}
void
testNormalizeToRange()
{
// Test edge-cases of normalizeToRange
auto const scale = Number::getMantissaScale();
testcase << "normalizeToRange " << to_string(scale);
auto test = [this](
Number const& n,
auto const rangeMin,
auto const rangeMax,
auto const expectedMantissa,
auto const expectedExponent,
auto const line) {
auto const normalized = n.normalizeToRange(rangeMin, rangeMax);
BEAST_EXPECTS(
normalized.first == expectedMantissa,
"Number " + to_string(n) + " scaled to " +
std::to_string(rangeMax) +
". Expected mantissa:" + std::to_string(expectedMantissa) +
", got: " + std::to_string(normalized.first) + " @ " +
std::to_string(line));
BEAST_EXPECTS(
normalized.second == expectedExponent,
"Number " + to_string(n) + " scaled to " +
std::to_string(rangeMax) +
". Expected exponent:" + std::to_string(expectedExponent) +
", got: " + std::to_string(normalized.second) + " @ " +
std::to_string(line));
};
std::int64_t constexpr iRangeMin = 100;
std::int64_t constexpr iRangeMax = 999;
std::uint64_t constexpr uRangeMin = 100;
std::uint64_t constexpr uRangeMax = 999;
constexpr static MantissaRange largeRange{MantissaRange::large};
std::int64_t constexpr iBigMin = largeRange.min;
std::int64_t constexpr iBigMax = largeRange.max;
auto const testSuite = [&](Number const& n,
auto const expectedSmallMantissa,
auto const expectedSmallExponent,
auto const expectedLargeMantissa,
auto const expectedLargeExponent,
auto const line) {
test(
n,
iRangeMin,
iRangeMax,
expectedSmallMantissa,
expectedSmallExponent,
line);
test(
n,
iBigMin,
iBigMax,
expectedLargeMantissa,
expectedLargeExponent,
line);
// Only test non-negative. testing a negative number with an
// unsigned range will assert, and asserts can't be tested.
if (n.signum() >= 0)
{
test(
n,
uRangeMin,
uRangeMax,
expectedSmallMantissa,
expectedSmallExponent,
line);
test(
n,
largeRange.min,
largeRange.max,
expectedLargeMantissa,
expectedLargeExponent,
line);
}
};
{
// zero
Number const n{0};
testSuite(
n,
0,
std::numeric_limits<int>::lowest(),
0,
std::numeric_limits<int>::lowest(),
__LINE__);
}
{
// Small positive number
Number const n{2};
testSuite(n, 200, -2, 2'000'000'000'000'000'000, -18, __LINE__);
}
{
// Negative number
Number const n{-2};
testSuite(n, -200, -2, -2'000'000'000'000'000'000, -18, __LINE__);
}
{
// Biggest valid mantissa
Number const n{Number::largestMantissa, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, Number::largestMantissa, 0, __LINE__);
}
{
// Biggest valid mantissa + 1
Number const n{
Number::largestMantissa + 1, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
}
{
// Biggest valid mantissa + 2
Number const n{
Number::largestMantissa + 2, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
}
{
// Biggest valid mantissa + 3
Number const n{
Number::largestMantissa + 3, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
}
{
// int64 min
Number const n{std::numeric_limits<std::int64_t>::min(), 0};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
}
{
// int64 min + 1
Number const n{std::numeric_limits<std::int64_t>::min() + 1, 0};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -9'223'372'036'854'775'807, 0, __LINE__);
}
{
// int64 min - 1
// Need to cast to uint, even though we're dealing with a negative
// number to avoid overflow and UB
Number const n{
true,
static_cast<std::uint64_t>(
std::numeric_limits<std::int64_t>::min()) +
1,
0,
Number::normalized{}};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
}
}
@@ -1361,6 +1845,7 @@ public:
test_truncate();
testRounding();
testInt64();
testNormalizeToRange();
}
}
};

View File

@@ -183,6 +183,9 @@ private:
};
// Helper function to run HTTP client test
// Note: Caller must ensure HTTPClient::initializeSSLContext has been called
// before this function, and HTTPClient::cleanupSSLContext is called after
// all tests are completed.
bool
runHTTPTest(
TestHTTPServer& server,
@@ -190,14 +193,9 @@ runHTTPTest(
bool& completed,
int& resultStatus,
std::string& resultData,
boost::system::error_code& resultError)
boost::system::error_code& resultError,
beast::Journal& j)
{
// Create a null journal for testing
beast::Journal j{TestSink::instance()};
// Initialize HTTPClient SSL context
HTTPClient::initializeSSLContext("", "", false, j);
HTTPClient::get(
false, // no SSL
server.ioc(),
@@ -230,6 +228,9 @@ runHTTPTest(
}
}
// Drain any remaining handlers to ensure proper cleanup of HTTPClientImp
server.ioc().poll();
return completed;
}
@@ -258,18 +259,27 @@ TEST(HTTPClient, case_insensitive_content_length)
std::string resultData;
boost::system::error_code resultError;
bool testCompleted = runHTTPTest(server, "/test", completed, resultStatus, resultData, resultError);
beast::Journal j{TestSink::instance()};
HTTPClient::initializeSSLContext("", "", false, j);
bool testCompleted = runHTTPTest(server, "/test", completed, resultStatus, resultData, resultError, j);
// Verify results
EXPECT_TRUE(testCompleted);
EXPECT_FALSE(resultError);
EXPECT_EQ(resultStatus, 200);
EXPECT_EQ(resultData, testBody);
}
// Clean up SSL context to prevent memory leaks
HTTPClient::cleanupSSLContext();
}
TEST(HTTPClient, basic_http_request)
{
// Initialize SSL context once for the entire test
beast::Journal j{TestSink::instance()};
HTTPClient::initializeSSLContext("", "", false, j);
TestHTTPServer server;
std::string testBody = "Test response body";
server.setResponseBody(testBody);
@@ -280,16 +290,23 @@ TEST(HTTPClient, basic_http_request)
std::string resultData;
boost::system::error_code resultError;
bool testCompleted = runHTTPTest(server, "/basic", completed, resultStatus, resultData, resultError);
bool testCompleted = runHTTPTest(server, "/basic", completed, resultStatus, resultData, resultError, j);
EXPECT_TRUE(testCompleted);
EXPECT_FALSE(resultError);
EXPECT_EQ(resultStatus, 200);
EXPECT_EQ(resultData, testBody);
// Clean up SSL context to prevent memory leaks
HTTPClient::cleanupSSLContext();
}
TEST(HTTPClient, empty_response)
{
// Initialize SSL context once for the entire test
beast::Journal j{TestSink::instance()};
HTTPClient::initializeSSLContext("", "", false, j);
TestHTTPServer server;
server.setResponseBody(""); // Empty body
server.setHeader("Content-Length", "0");
@@ -299,16 +316,23 @@ TEST(HTTPClient, empty_response)
std::string resultData;
boost::system::error_code resultError;
bool testCompleted = runHTTPTest(server, "/empty", completed, resultStatus, resultData, resultError);
bool testCompleted = runHTTPTest(server, "/empty", completed, resultStatus, resultData, resultError, j);
EXPECT_TRUE(testCompleted);
EXPECT_FALSE(resultError);
EXPECT_EQ(resultStatus, 200);
EXPECT_TRUE(resultData.empty());
// Clean up SSL context to prevent memory leaks
HTTPClient::cleanupSSLContext();
}
TEST(HTTPClient, different_status_codes)
{
// Initialize SSL context once for the entire test
beast::Journal j{TestSink::instance()};
HTTPClient::initializeSSLContext("", "", false, j);
std::vector<unsigned int> statusCodes = {200, 404, 500};
for (auto status : statusCodes)
@@ -322,10 +346,13 @@ TEST(HTTPClient, different_status_codes)
std::string resultData;
boost::system::error_code resultError;
bool testCompleted = runHTTPTest(server, "/status", completed, resultStatus, resultData, resultError);
bool testCompleted = runHTTPTest(server, "/status", completed, resultStatus, resultData, resultError, j);
EXPECT_TRUE(testCompleted);
EXPECT_FALSE(resultError);
EXPECT_EQ(resultStatus, static_cast<int>(status));
}
// Clean up SSL context to prevent memory leaks
HTTPClient::cleanupSSLContext();
}

View File

@@ -6,7 +6,6 @@
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/shamap/TreeNodeCache.h>
@@ -92,7 +91,7 @@ class Validations;
class RCLValidationsAdaptor;
using RCLValidations = Validations<RCLValidationsAdaptor>;
class Application : public ServiceRegistry, public beast::PropertyStream::Source
class Application : public beast::PropertyStream::Source
{
public:
/* VFALCO NOTE
@@ -147,12 +146,92 @@ public:
virtual boost::asio::io_context&
getIOContext() = 0;
virtual CollectorManager&
getCollectorManager() = 0;
virtual Family&
getNodeFamily() = 0;
virtual TimeKeeper&
timeKeeper() = 0;
virtual JobQueue&
getJobQueue() = 0;
virtual NodeCache&
getTempNodeCache() = 0;
virtual CachedSLEs&
cachedSLEs() = 0;
virtual AmendmentTable&
getAmendmentTable() = 0;
virtual HashRouter&
getHashRouter() = 0;
virtual LoadFeeTrack&
getFeeTrack() = 0;
virtual LoadManager&
getLoadManager() = 0;
virtual Overlay&
overlay() = 0;
virtual TxQ&
getTxQ() = 0;
virtual ValidatorList&
validators() = 0;
virtual ValidatorSite&
validatorSites() = 0;
virtual ManifestCache&
validatorManifests() = 0;
virtual ManifestCache&
publisherManifests() = 0;
virtual Cluster&
cluster() = 0;
virtual PeerReservationTable&
peerReservations() = 0;
virtual RCLValidations&
getValidations() = 0;
virtual NodeStore::Database&
getNodeStore() = 0;
virtual InboundLedgers&
getInboundLedgers() = 0;
virtual InboundTransactions&
getInboundTransactions() = 0;
virtual TaggedCache<uint256, AcceptedLedger>&
getAcceptedLedgerCache() = 0;
virtual LedgerMaster&
getLedgerMaster() = 0;
virtual LedgerCleaner&
getLedgerCleaner() = 0;
virtual LedgerReplayer&
getLedgerReplayer() = 0;
virtual NetworkOPs&
getOPs() = 0;
virtual OrderBookDB&
getOrderBookDB() = 0;
virtual ServerHandler&
getServerHandler() = 0;
virtual TransactionMaster&
getMasterTransaction() = 0;
virtual perf::PerfLog&
getPerfLog() = 0;
virtual std::pair<PublicKey, SecretKey> const&
nodeIdentity() = 0;
virtual std::optional<PublicKey const>
getValidationPublicKey() const = 0;
virtual Resource::Manager&
getResourceManager() = 0;
virtual PathRequests&
getPathRequests() = 0;
virtual SHAMapStore&
getSHAMapStore() = 0;
virtual PendingSaves&
pendingSaves() = 0;
virtual OpenLedger&
openLedger() = 0;
virtual OpenLedger const&
openLedger() const = 0;
virtual RelationalDatabase&
getRelationalDatabase() = 0;
virtual std::chrono::milliseconds
getIOLatency() = 0;