From c50d166c239d71e83772988bce40d69392b1e3b3 Mon Sep 17 00:00:00 2001 From: Scott Schurr Date: Mon, 16 Aug 2021 11:27:05 -0700 Subject: [PATCH] Add ripple::Expected which simulates std::expected: Also integrates use of ripple::Expected into the code base. --- Builds/CMake/RippledCore.cmake | 1 + src/ripple/app/tx/impl/SetSignerList.cpp | 8 +- src/ripple/app/tx/impl/SignerEntries.cpp | 14 +- src/ripple/app/tx/impl/SignerEntries.h | 3 +- src/ripple/app/tx/impl/Transactor.cpp | 8 +- src/ripple/app/tx/impl/apply.cpp | 4 +- src/ripple/basics/Expected.h | 243 +++++++++++++++++++++++ src/ripple/basics/base_uint.h | 36 ++-- src/ripple/protocol/STTx.h | 7 +- src/ripple/protocol/TER.h | 5 +- src/ripple/protocol/impl/STTx.cpp | 42 ++-- src/test/basics/Expected_test.cpp | 212 ++++++++++++++++++++ src/test/basics/base_uint_test.cpp | 41 ++++ src/test/protocol/STTx_test.cpp | 2 +- 14 files changed, 557 insertions(+), 69 deletions(-) create mode 100644 src/ripple/basics/Expected.h create mode 100644 src/test/basics/Expected_test.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 26f83fb79..2392eea6f 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -708,6 +708,7 @@ target_sources (rippled PRIVATE #]===============================] src/test/basics/Buffer_test.cpp src/test/basics/DetectCrash_test.cpp + src/test/basics/Expected_test.cpp src/test/basics/FileUtilities_test.cpp src/test/basics/IOUAmount_test.cpp src/test/basics/KeyCache_test.cpp diff --git a/src/ripple/app/tx/impl/SetSignerList.cpp b/src/ripple/app/tx/impl/SetSignerList.cpp index e72f37676..8e321c4c1 100644 --- a/src/ripple/app/tx/impl/SetSignerList.cpp +++ b/src/ripple/app/tx/impl/SetSignerList.cpp @@ -58,13 +58,13 @@ SetSignerList::determineOperation( { auto signers = SignerEntries::deserialize(tx, j, "transaction"); - if (signers.second != tesSUCCESS) - return std::make_tuple(signers.second, quorum, sign, op); + if (!signers) + return std::make_tuple(signers.error(), quorum, sign, op); - std::sort(signers.first.begin(), signers.first.end()); + std::sort(signers->begin(), signers->end()); // Save deserialized list for later. - sign = std::move(signers.first); + sign = std::move(*signers); op = set; } else if ((quorum == 0) && !hasSignerEntries) diff --git a/src/ripple/app/tx/impl/SignerEntries.cpp b/src/ripple/app/tx/impl/SignerEntries.cpp index b914f6ad0..5081dc3e2 100644 --- a/src/ripple/app/tx/impl/SignerEntries.cpp +++ b/src/ripple/app/tx/impl/SignerEntries.cpp @@ -25,7 +25,7 @@ namespace ripple { -std::pair, NotTEC> +Expected, NotTEC> SignerEntries::deserialize( STObject const& obj, beast::Journal journal, @@ -37,11 +37,10 @@ SignerEntries::deserialize( { JLOG(journal.trace()) << "Malformed " << annotation << ": Need signer entry array."; - s.second = temMALFORMED; - return s; + return Unexpected(temMALFORMED); } - auto& accountVec = s.first; + std::vector accountVec; accountVec.reserve(STTx::maxMultiSigners); STArray const& sEntries(obj.getFieldArray(sfSignerEntries)); @@ -52,8 +51,7 @@ SignerEntries::deserialize( { JLOG(journal.trace()) << "Malformed " << annotation << ": Expected SignerEntry."; - s.second = temMALFORMED; - return s; + return Unexpected(temMALFORMED); } // Extract SignerEntry fields. @@ -61,9 +59,7 @@ SignerEntries::deserialize( std::uint16_t const weight = sEntry.getFieldU16(sfSignerWeight); accountVec.emplace_back(account, weight); } - - s.second = tesSUCCESS; - return s; + return accountVec; } } // namespace ripple diff --git a/src/ripple/app/tx/impl/SignerEntries.h b/src/ripple/app/tx/impl/SignerEntries.h index 835b826bd..96b5e29d9 100644 --- a/src/ripple/app/tx/impl/SignerEntries.h +++ b/src/ripple/app/tx/impl/SignerEntries.h @@ -21,6 +21,7 @@ #define RIPPLE_TX_IMPL_SIGNER_ENTRIES_H_INCLUDED #include // NotTEC +#include // #include // beast::Journal #include // STTx::maxMultiSigners #include // temMALFORMED @@ -62,7 +63,7 @@ public: }; // Deserialize a SignerEntries array from the network or from the ledger. - static std::pair, NotTEC> + static Expected, NotTEC> deserialize( STObject const& obj, beast::Journal journal, diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index 5cfaabca8..8b74463de 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -554,8 +554,8 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) auto accountSigners = SignerEntries::deserialize(*sleAccountSigners, ctx.j, "ledger"); - if (accountSigners.second != tesSUCCESS) - return accountSigners.second; + if (!accountSigners) + return accountSigners.error(); // Get the array of transaction signers. STArray const& txSigners(ctx.tx.getFieldArray(sfSigners)); @@ -567,7 +567,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) // matching multi-signers to account signers should be a simple // linear walk. *All* signers must be valid or the transaction fails. std::uint32_t weightSum = 0; - auto iter = accountSigners.first.begin(); + auto iter = accountSigners->begin(); for (auto const& txSigner : txSigners) { AccountID const txSignerAcctID = txSigner.getAccountID(sfAccount); @@ -575,7 +575,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) // Attempt to match the SignerEntry with a Signer; while (iter->account < txSignerAcctID) { - if (++iter == accountSigners.first.end()) + if (++iter == accountSigners->end()) { JLOG(ctx.j.trace()) << "applyTransaction: Invalid SigningAccount.Account."; diff --git a/src/ripple/app/tx/impl/apply.cpp b/src/ripple/app/tx/impl/apply.cpp index 7ccba7826..332364863 100644 --- a/src/ripple/app/tx/impl/apply.cpp +++ b/src/ripple/app/tx/impl/apply.cpp @@ -55,10 +55,10 @@ checkValidity( : STTx::RequireFullyCanonicalSig::no; auto const sigVerify = tx.checkSign(requireCanonicalSig); - if (!sigVerify.first) + if (!sigVerify) { router.setFlags(id, SF_SIGBAD); - return {Validity::SigBad, sigVerify.second}; + return {Validity::SigBad, sigVerify.error()}; } router.setFlags(id, SF_SIGGOOD); } diff --git a/src/ripple/basics/Expected.h b/src/ripple/basics/Expected.h new file mode 100644 index 000000000..09d2bdc50 --- /dev/null +++ b/src/ripple/basics/Expected.h @@ -0,0 +1,243 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2021 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_BASICS_EXPECTED_H_INCLUDED +#define RIPPLE_BASICS_EXPECTED_H_INCLUDED + +#include +#include +#include +#include + +namespace ripple { + +/** Expected is an approximation of std::expected (hoped for in C++23) + + See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.html + + The implementation is entirely based on boost::outcome_v2::result. +*/ + +// Exception thrown by an invalid access to Expected. +struct bad_expected_access : public std::runtime_error +{ + bad_expected_access() : runtime_error("bad expected access") + { + } +}; + +namespace detail { + +// Custom policy for Expected. Always throw on an invalid access. +struct throw_policy : public boost::outcome_v2::policy::base +{ + template + static constexpr void + wide_value_check(Impl&& self) + { + if (!base::_has_value(std::forward(self))) + Throw(); + } + + template + static constexpr void + wide_error_check(Impl&& self) + { + if (!base::_has_error(std::forward(self))) + Throw(); + } + + template + static constexpr void + wide_exception_check(Impl&& self) + { + if (!base::_has_exception(std::forward(self))) + Throw(); + } +}; + +} // namespace detail + +// Definition of Unexpected, which is used to construct the unexpected +// return type of an Expected. +template +class Unexpected +{ +public: + static_assert(!std::is_same::value, "E must not be void"); + + Unexpected() = delete; + + constexpr explicit Unexpected(E const& e) : val_(e) + { + } + + constexpr explicit Unexpected(E&& e) : val_(std::move(e)) + { + } + + constexpr const E& + value() const& + { + return val_; + } + + constexpr E& + value() & + { + return val_; + } + + constexpr E&& + value() && + { + return std::move(val_); + } + + constexpr const E&& + value() const&& + { + return std::move(val_); + } + +private: + E val_; +}; + +// Unexpected deduction guide that converts array to const*. +template +Unexpected(E (&)[N]) -> Unexpected; + +// Definition of Expected. All of the machinery comes from boost::result. +template +class [[nodiscard]] Expected + : private boost::outcome_v2::result +{ + using Base = boost::outcome_v2::result; + +public: + template < + typename U, + typename = std::enable_if_t>> + constexpr Expected(U r) : Base(T{std::forward(r)}) + { + } + + template < + typename U, + typename = std::enable_if_t>> + constexpr Expected(Unexpected e) : Base(E{std::forward(e.value())}) + { + } + + constexpr bool has_value() const + { + return Base::has_value(); + } + + constexpr T const& value() const + { + return Base::value(); + } + + constexpr T& value() + { + return Base::value(); + } + + constexpr E const& error() const + { + return Base::error(); + } + + constexpr E& error() + { + return Base::error(); + } + + constexpr explicit operator bool() const + { + return has_value(); + } + + // Add operator* and operator-> so the Expected API looks a bit more like + // what std::expected is likely to look like. See: + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.html + [[nodiscard]] constexpr T& operator*() + { + return this->value(); + } + + [[nodiscard]] constexpr T const& operator*() const + { + return this->value(); + } + + [[nodiscard]] constexpr T* operator->() + { + return &this->value(); + } + + [[nodiscard]] constexpr T const* operator->() const + { + return &this->value(); + } +}; + +// Specialization of Expected. Allows returning either success +// (without a value) or the reason for the failure. +template +class [[nodiscard]] Expected + : private boost::outcome_v2::result +{ + using Base = boost::outcome_v2::result; + +public: + // The default constructor makes a successful Expected. + // This aligns with std::expected behavior proposed in P0323R10. + constexpr Expected() : Base(boost::outcome_v2::success()) + { + } + + template < + typename U, + typename = std::enable_if_t>> + constexpr Expected(Unexpected e) : Base(E{std::forward(e.value())}) + { + } + + constexpr E const& error() const + { + return Base::error(); + } + + constexpr E& error() + { + return Base::error(); + } + + constexpr explicit operator bool() const + { + return Base::has_value(); + } +}; + +} // namespace ripple + +#endif // RIPPLE_BASICS_EXPECTED_H_INCLUDED diff --git a/src/ripple/basics/base_uint.h b/src/ripple/basics/base_uint.h index 23ac188ab..00b38eec7 100644 --- a/src/ripple/basics/base_uint.h +++ b/src/ripple/basics/base_uint.h @@ -25,6 +25,7 @@ #ifndef RIPPLE_BASICS_BASE_UINT_H_INCLUDED #define RIPPLE_BASICS_BASE_UINT_H_INCLUDED +#include #include #include #include @@ -185,7 +186,7 @@ private: badChar, }; - constexpr std::pair + constexpr Expected parseFromStringView(std::string_view sv) noexcept { // Local lambda that converts a single hex char to four bits and @@ -212,7 +213,7 @@ private: return ParseResult::okay; }; - std::pair ret{ParseResult::okay, {}}; + decltype(data_) ret{}; if (sv == "0") { @@ -220,15 +221,10 @@ private: } if (sv.size() != size() * 2) - { - ret.first = ParseResult::badLength; - return ret; - } - - auto out = ret.second.data(); + return Unexpected(ParseResult::badLength); + std::size_t i = 0u; auto in = sv.begin(); - while (in != sv.end()) { std::uint32_t accum = {}; @@ -236,12 +232,9 @@ private: { if (auto const result = hexCharToUInt(*in++, shift, accum); result != ParseResult::okay) - { - ret.first = result; - return ret; - } + return Unexpected(result); } - *out++ = accum; + ret[i++] = accum; } return ret; } @@ -250,13 +243,14 @@ private: parseFromStringViewThrows(std::string_view sv) noexcept(false) { auto const result = parseFromStringView(sv); - if (result.first == ParseResult::badLength) - Throw("invalid length for hex string"); + if (!result) + { + if (result.error() == ParseResult::badLength) + Throw("invalid length for hex string"); - if (result.first == ParseResult::badChar) Throw("invalid hex character"); - - return result.second; + } + return *result; } public: @@ -481,10 +475,10 @@ public: parseHex(std::string_view sv) { auto const result = parseFromStringView(sv); - if (result.first != ParseResult::okay) + if (!result) return false; - data_ = result.second; + data_ = *result; return true; } diff --git a/src/ripple/protocol/STTx.h b/src/ripple/protocol/STTx.h index 0d90b2324..706a99359 100644 --- a/src/ripple/protocol/STTx.h +++ b/src/ripple/protocol/STTx.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_PROTOCOL_STTX_H_INCLUDED #define RIPPLE_PROTOCOL_STTX_H_INCLUDED +#include #include #include #include @@ -131,7 +132,7 @@ public: @return `true` if valid signature. If invalid, the error message string. */ enum class RequireFullyCanonicalSig : bool { no, yes }; - std::pair + Expected checkSign(RequireFullyCanonicalSig requireCanonicalSig) const; // SQL Functions with metadata. @@ -150,10 +151,10 @@ public: std::string const& escapedMetaData) const; private: - std::pair + Expected checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const; - std::pair + Expected checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const; uint256 tid_; diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index eff280b4f..3a1351058 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -349,7 +349,10 @@ public: } // Trait tells enable_if which types are allowed for construction. - template ::value>> + template < + typename T, + typename = std::enable_if_t< + Trait>>::value>> constexpr TERSubset(T rhs) : code_(TERtoInt(rhs)) { } diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index 898738d7b..a7a45912e 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -186,24 +186,22 @@ STTx::sign(PublicKey const& publicKey, SecretKey const& secretKey) tid_ = getHash(HashPrefix::transactionID); } -std::pair +Expected STTx::checkSign(RequireFullyCanonicalSig requireCanonicalSig) const { - std::pair ret{false, ""}; try { // Determine whether we're single- or multi-signing by looking // at the SigningPubKey. If it's empty we must be // multi-signing. Otherwise we're single-signing. Blob const& signingPubKey = getFieldVL(sfSigningPubKey); - ret = signingPubKey.empty() ? checkMultiSign(requireCanonicalSig) - : checkSingleSign(requireCanonicalSig); + return signingPubKey.empty() ? checkMultiSign(requireCanonicalSig) + : checkSingleSign(requireCanonicalSig); } catch (std::exception const&) { - ret = {false, "Internal signature check failure."}; } - return ret; + return Unexpected("Internal signature check failure."); } Json::Value STTx::getJson(JsonOptions) const @@ -269,14 +267,14 @@ STTx::getMetaSQL( getFieldU32(sfSequence) % inLedger % status % rTxn % escapedMetaData); } -std::pair +Expected STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const { // We don't allow both a non-empty sfSigningPubKey and an sfSigners. // That would allow the transaction to be signed two ways. So if both // fields are present the signature is invalid. if (isFieldPresent(sfSigners)) - return {false, "Cannot both single- and multi-sign."}; + return Unexpected("Cannot both single- and multi-sign."); bool validSig = false; try @@ -304,29 +302,29 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const validSig = false; } if (validSig == false) - return {false, "Invalid signature."}; - - return {true, ""}; + return Unexpected("Invalid signature."); + // Signature was verified. + return {}; } -std::pair +Expected STTx::checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const { // Make sure the MultiSigners are present. Otherwise they are not // attempting multi-signing and we just have a bad SigningPubKey. if (!isFieldPresent(sfSigners)) - return {false, "Empty SigningPubKey."}; + return Unexpected("Empty SigningPubKey."); // We don't allow both an sfSigners and an sfTxnSignature. Both fields // being present would indicate that the transaction is signed both ways. if (isFieldPresent(sfTxnSignature)) - return {false, "Cannot both single- and multi-sign."}; + return Unexpected("Cannot both single- and multi-sign."); STArray const& signers{getFieldArray(sfSigners)}; // There are well known bounds that the number of signers must be within. if (signers.size() < minMultiSigners || signers.size() > maxMultiSigners) - return {false, "Invalid Signers array size."}; + return Unexpected("Invalid Signers array size."); // We can ease the computational load inside the loop a bit by // pre-constructing part of the data that we hash. Fill a Serializer @@ -349,15 +347,15 @@ STTx::checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const // The account owner may not multisign for themselves. if (accountID == txnAccountID) - return {false, "Invalid multisigner."}; + return Unexpected("Invalid multisigner."); // No duplicate signers allowed. if (lastAccountID == accountID) - return {false, "Duplicate Signers not allowed."}; + return Unexpected("Duplicate Signers not allowed."); // Accounts must be in order by account ID. No duplicates allowed. if (lastAccountID > accountID) - return {false, "Unsorted Signers array."}; + return Unexpected("Unsorted Signers array."); // The next signature must be greater than this one. lastAccountID = accountID; @@ -388,14 +386,12 @@ STTx::checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const validSig = false; } if (!validSig) - return { - false, + return Unexpected( std::string("Invalid signature on account ") + - toBase58(accountID) + "."}; + toBase58(accountID) + "."); } - // All signatures verified. - return {true, ""}; + return {}; } //------------------------------------------------------------------------------ diff --git a/src/test/basics/Expected_test.cpp b/src/test/basics/Expected_test.cpp new file mode 100644 index 000000000..1f16e724d --- /dev/null +++ b/src/test/basics/Expected_test.cpp @@ -0,0 +1,212 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github0.com/ripple/rippled + Copyright (c) 2012-2016 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +struct Expected_test : beast::unit_test::suite +{ + void + run() override + { + // Test non-error const construction. + { + auto const expected = []() -> Expected { + return "Valid value"; + }(); + BEAST_EXPECT(expected); + BEAST_EXPECT(expected.has_value()); + BEAST_EXPECT(expected.value() == "Valid value"); + BEAST_EXPECT(*expected == "Valid value"); + BEAST_EXPECT(expected->at(0) == 'V'); + + bool throwOccurred = false; + try + { + // There's no error, so should throw. + [[maybe_unused]] TER const t = expected.error(); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT(e.what() == std::string("bad expected access")); + throwOccurred = true; + } + BEAST_EXPECT(throwOccurred); + } + // Test non-error non-const construction. + { + auto expected = []() -> Expected { + return "Valid value"; + }(); + BEAST_EXPECT(expected); + BEAST_EXPECT(expected.has_value()); + BEAST_EXPECT(expected.value() == "Valid value"); + BEAST_EXPECT(*expected == "Valid value"); + BEAST_EXPECT(expected->at(0) == 'V'); + std::string mv = std::move(*expected); + BEAST_EXPECT(mv == "Valid value"); + + bool throwOccurred = false; + try + { + // There's no error, so should throw. + [[maybe_unused]] TER const t = expected.error(); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT(e.what() == std::string("bad expected access")); + throwOccurred = true; + } + BEAST_EXPECT(throwOccurred); + } + // Test error construction from rvalue. + { + auto const expected = []() -> Expected { + return Unexpected(telLOCAL_ERROR); + }(); + BEAST_EXPECT(!expected); + BEAST_EXPECT(!expected.has_value()); + BEAST_EXPECT(expected.error() == telLOCAL_ERROR); + + bool throwOccurred = false; + try + { + // There's no result, so should throw. + [[maybe_unused]] std::string const s = *expected; + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT(e.what() == std::string("bad expected access")); + throwOccurred = true; + } + BEAST_EXPECT(throwOccurred); + } + // Test error construction from lvalue. + { + auto const err(telLOCAL_ERROR); + auto expected = [&err]() -> Expected { + return Unexpected(err); + }(); + BEAST_EXPECT(!expected); + BEAST_EXPECT(!expected.has_value()); + BEAST_EXPECT(expected.error() == telLOCAL_ERROR); + + bool throwOccurred = false; + try + { + // There's no result, so should throw. + [[maybe_unused]] std::size_t const s = expected->size(); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT(e.what() == std::string("bad expected access")); + throwOccurred = true; + } + BEAST_EXPECT(throwOccurred); + } + // Test error construction from const char*. + { + auto const expected = []() -> Expected { + return Unexpected("Not what is expected!"); + }(); + BEAST_EXPECT(!expected); + BEAST_EXPECT(!expected.has_value()); + BEAST_EXPECT( + expected.error() == std::string("Not what is expected!")); + } + // Test error construction of string from const char*. + { + auto expected = []() -> Expected { + return Unexpected("Not what is expected!"); + }(); + BEAST_EXPECT(!expected); + BEAST_EXPECT(!expected.has_value()); + BEAST_EXPECT(expected.error() == "Not what is expected!"); + std::string const s(std::move(expected.error())); + BEAST_EXPECT(s == "Not what is expected!"); + } + // Test non-error const construction of Expected. + { + auto const expected = []() -> Expected { + return {}; + }(); + BEAST_EXPECT(expected); + bool throwOccurred = false; + try + { + // There's no error, so should throw. + [[maybe_unused]] std::size_t const s = expected.error().size(); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT(e.what() == std::string("bad expected access")); + throwOccurred = true; + } + BEAST_EXPECT(throwOccurred); + } + // Test non-error non-const construction of Expected. + { + auto expected = []() -> Expected { + return {}; + }(); + BEAST_EXPECT(expected); + bool throwOccurred = false; + try + { + // There's no error, so should throw. + [[maybe_unused]] std::size_t const s = expected.error().size(); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT(e.what() == std::string("bad expected access")); + throwOccurred = true; + } + BEAST_EXPECT(throwOccurred); + } + // Test error const construction of Expected. + { + auto const expected = []() -> Expected { + return Unexpected("Not what is expected!"); + }(); + BEAST_EXPECT(!expected); + BEAST_EXPECT(expected.error() == "Not what is expected!"); + } + // Test error non-const construction of Expected. + { + auto expected = []() -> Expected { + return Unexpected("Not what is expected!"); + }(); + BEAST_EXPECT(!expected); + BEAST_EXPECT(expected.error() == "Not what is expected!"); + std::string const s(std::move(expected.error())); + BEAST_EXPECT(s == "Not what is expected!"); + } + } +}; + +BEAST_DEFINE_TESTSUITE(Expected, ripple_basics, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/basics/base_uint_test.cpp b/src/test/basics/base_uint_test.cpp index 5ed5ec498..c1ba7302a 100644 --- a/src/test/basics/base_uint_test.cpp +++ b/src/test/basics/base_uint_test.cpp @@ -223,6 +223,47 @@ struct base_uint_test : beast::unit_test::suite static_assert(test96("00000000000000000000000~").signum() == 1); #endif // 0 + // Using the constexpr constructor in a non-constexpr context + // with an error in the parsing throws an exception. + { + // Invalid length for string. + bool caught = false; + try + { + // Try to prevent constant evaluation. + std::vector str(23, '7'); + std::string_view sView(str.data(), str.size()); + [[maybe_unused]] test96 t96(sView); + } + catch (std::invalid_argument const& e) + { + BEAST_EXPECT( + e.what() == + std::string("invalid length for hex string")); + caught = true; + } + BEAST_EXPECT(caught); + } + { + // Invalid character in string. + bool caught = false; + try + { + // Try to prevent constant evaluation. + std::vector str(23, '7'); + str.push_back('G'); + std::string_view sView(str.data(), str.size()); + [[maybe_unused]] test96 t96(sView); + } + catch (std::range_error const& e) + { + BEAST_EXPECT( + e.what() == std::string("invalid hex character")); + caught = true; + } + BEAST_EXPECT(caught); + } + // Verify that constexpr base_uints interpret a string the same // way parseHex() does. struct StrBaseUint diff --git a/src/test/protocol/STTx_test.cpp b/src/test/protocol/STTx_test.cpp index 21851af51..5d24efcfc 100644 --- a/src/test/protocol/STTx_test.cpp +++ b/src/test/protocol/STTx_test.cpp @@ -1592,7 +1592,7 @@ public: j.sign(keypair.first, keypair.second); unexpected( - !j.checkSign(STTx::RequireFullyCanonicalSig::yes).first, + !j.checkSign(STTx::RequireFullyCanonicalSig::yes), "Transaction fails signature test"); Serializer rawTxn;