mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Compile time check preflight returns no tec (RIPD-1624):
The six different ranges of TER codes are broken up into six different enumerations. A template class allows subsets of these enumerations to be aggregated. This technique allows verification at compile time that no TEC codes are returned before the signature is checked. Conversion between TER instance and integer is provided by named functions. This makes accidental conversion almost impossible and makes type abuse easier to spot in the code base.
This commit is contained in:
@@ -20,6 +20,9 @@
|
||||
#include <ripple/protocol/TER.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
struct TER_test : public beast::unit_test::suite
|
||||
@@ -29,7 +32,7 @@ struct TER_test : public beast::unit_test::suite
|
||||
{
|
||||
for (auto i = -400; i < 400; ++i)
|
||||
{
|
||||
TER t = static_cast<TER>(i);
|
||||
TER t = TER::fromInt (i);
|
||||
auto inRange = isTelLocal(t) ||
|
||||
isTemMalformed(t) ||
|
||||
isTefFailure(t) ||
|
||||
@@ -49,13 +52,214 @@ struct TER_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
// Helper template that makes sure two types are not convertible or
|
||||
// assignable if not the same.
|
||||
// o I1 one tuple index.
|
||||
// o I2 other tuple index.
|
||||
// o Tup is expected to be a tuple.
|
||||
// It's a functor, rather than a function template, since a class template
|
||||
// can be a template argument without being full specified.
|
||||
template<std::size_t I1, std::size_t I2>
|
||||
class NotConvertible
|
||||
{
|
||||
public:
|
||||
template<typename Tup>
|
||||
void operator()(Tup const& tup, beast::unit_test::suite&) const
|
||||
{
|
||||
// Entries in the tuple should not be convertible or assignable
|
||||
// unless they are the same types.
|
||||
using To_t = std::decay_t<decltype (std::get<I1>(tup))>;
|
||||
using From_t = std::decay_t<decltype (std::get<I2>(tup))>;
|
||||
static_assert (std::is_same<From_t, To_t>::value ==
|
||||
std::is_convertible<From_t, To_t>::value, "Convert err");
|
||||
static_assert (std::is_same<To_t, From_t>::value ==
|
||||
std::is_constructible<To_t, From_t>::value, "Construct err");
|
||||
static_assert (std::is_same <To_t, From_t>::value ==
|
||||
std::is_assignable<To_t&, From_t const&>::value, "Assign err");
|
||||
|
||||
// Assignment or conversion from integer to type should never work.
|
||||
static_assert (
|
||||
! std::is_convertible<int, To_t>::value, "Convert err");
|
||||
static_assert (
|
||||
! std::is_constructible<To_t, int>::value, "Construct err");
|
||||
static_assert (
|
||||
! std::is_assignable<To_t&, int const&>::value, "Assign err");
|
||||
}
|
||||
};
|
||||
|
||||
// Fast iteration over the tuple.
|
||||
template<std::size_t I1, std::size_t I2,
|
||||
template<std::size_t, std::size_t> class Func, typename Tup>
|
||||
std::enable_if_t<I1 != 0>
|
||||
testIterate (Tup const& tup, beast::unit_test::suite& suite)
|
||||
{
|
||||
Func<I1, I2> func;
|
||||
func (tup, suite);
|
||||
testIterate<I1 - 1, I2, Func>(tup, suite);
|
||||
}
|
||||
|
||||
// Slow iteration over the tuple.
|
||||
template<std::size_t I1, std::size_t I2,
|
||||
template<std::size_t, std::size_t> class Func, typename Tup>
|
||||
std::enable_if_t<I1 == 0 && I2 != 0>
|
||||
testIterate (Tup const& tup, beast::unit_test::suite& suite)
|
||||
{
|
||||
Func<I1, I2> func;
|
||||
func (tup, suite);
|
||||
testIterate<std::tuple_size<Tup>::value - 1, I2 - 1, Func>(tup, suite);
|
||||
}
|
||||
|
||||
// Finish iteration over the tuple.
|
||||
template<std::size_t I1, std::size_t I2,
|
||||
template<std::size_t, std::size_t> class Func, typename Tup>
|
||||
std::enable_if_t<I1 == 0 && I2 == 0>
|
||||
testIterate (Tup const& tup, beast::unit_test::suite& suite)
|
||||
{
|
||||
Func<I1, I2> func;
|
||||
func (tup, suite);
|
||||
}
|
||||
|
||||
void testConversion()
|
||||
{
|
||||
// Verify that valid conversions are valid and invalid conversions
|
||||
// are not valid.
|
||||
|
||||
// Examples of each kind of enum.
|
||||
static auto const terEnums = std::make_tuple (telLOCAL_ERROR,
|
||||
temMALFORMED, tefFAILURE, terRETRY, tesSUCCESS, tecCLAIM);
|
||||
static const int hiIndex {
|
||||
std::tuple_size<decltype (terEnums)>::value - 1};
|
||||
|
||||
// Verify that enums cannot be converted to other enum types.
|
||||
testIterate<hiIndex, hiIndex, NotConvertible> (terEnums, *this);
|
||||
|
||||
// Lambda that verifies assignability and convertibility.
|
||||
auto isConvertable = [] (auto from, auto to)
|
||||
{
|
||||
using From_t = std::decay_t<decltype (from)>;
|
||||
using To_t = std::decay_t<decltype (to)>;
|
||||
static_assert (
|
||||
std::is_convertible<From_t, To_t>::value, "Convert err");
|
||||
static_assert (
|
||||
std::is_constructible<To_t, From_t>::value, "Construct err");
|
||||
static_assert (
|
||||
std::is_assignable<To_t&, From_t const&>::value, "Assign err");
|
||||
};
|
||||
|
||||
// Verify the right types convert to NotTEC.
|
||||
NotTEC const notTec;
|
||||
isConvertable (telLOCAL_ERROR, notTec);
|
||||
isConvertable (temMALFORMED, notTec);
|
||||
isConvertable (tefFAILURE, notTec);
|
||||
isConvertable (terRETRY, notTec);
|
||||
isConvertable (tesSUCCESS, notTec);
|
||||
isConvertable (notTec, notTec);
|
||||
|
||||
// Lambda that verifies types and not assignable or convertible.
|
||||
auto notConvertible = [] (auto from, auto to)
|
||||
{
|
||||
using To_t = std::decay_t<decltype (to)>;
|
||||
using From_t = std::decay_t<decltype (from)>;
|
||||
static_assert (
|
||||
!std::is_convertible<From_t, To_t>::value, "Convert err");
|
||||
static_assert (
|
||||
!std::is_constructible<To_t, From_t>::value, "Construct err");
|
||||
static_assert (
|
||||
!std::is_assignable<To_t&, From_t const&>::value, "Assign err");
|
||||
};
|
||||
|
||||
// Verify types that shouldn't convert to NotTEC.
|
||||
TER const ter;
|
||||
notConvertible (tecCLAIM, notTec);
|
||||
notConvertible (ter, notTec);
|
||||
notConvertible (4, notTec);
|
||||
|
||||
// Verify the right types convert to TER.
|
||||
isConvertable (telLOCAL_ERROR, ter);
|
||||
isConvertable (temMALFORMED, ter);
|
||||
isConvertable (tefFAILURE, ter);
|
||||
isConvertable (terRETRY, ter);
|
||||
isConvertable (tesSUCCESS, ter);
|
||||
isConvertable (tecCLAIM, ter);
|
||||
isConvertable (notTec, ter);
|
||||
isConvertable (ter, ter);
|
||||
|
||||
// Verify that you can't convert from int to ter.
|
||||
notConvertible (4, ter);
|
||||
}
|
||||
|
||||
// Helper template that makes sure two types are comparable. Also
|
||||
// verifies that one of the types does not compare to int.
|
||||
// o I1 one tuple index.
|
||||
// o I2 other tuple index.
|
||||
// o Tup is expected to be a tuple.
|
||||
// It's a functor, rather than a function template, since a class template
|
||||
// can be a template argument without being full specified.
|
||||
template<std::size_t I1, std::size_t I2>
|
||||
class CheckComparable
|
||||
{
|
||||
public:
|
||||
template<typename Tup>
|
||||
void operator()(Tup const& tup, beast::unit_test::suite& suite) const
|
||||
{
|
||||
// All entries in the tuple should be comparable one to the other.
|
||||
auto const lhs = std::get<I1>(tup);
|
||||
auto const rhs = std::get<I2>(tup);
|
||||
|
||||
static_assert (std::is_same<
|
||||
decltype (operator== (lhs, rhs)), bool>::value, "== err");
|
||||
|
||||
static_assert (std::is_same<
|
||||
decltype (operator!= (lhs, rhs)), bool>::value, "!= err");
|
||||
|
||||
static_assert (std::is_same<
|
||||
decltype (operator< (lhs, rhs)), bool>::value, "< err");
|
||||
|
||||
static_assert (std::is_same<
|
||||
decltype (operator<= (lhs, rhs)), bool>::value, "<= err");
|
||||
|
||||
static_assert (std::is_same<
|
||||
decltype (operator> (lhs, rhs)), bool>::value, "> err");
|
||||
|
||||
static_assert (std::is_same<
|
||||
decltype (operator>= (lhs, rhs)), bool>::value, ">= err");
|
||||
|
||||
// Make sure a sampling of TER types exhibit the expected behavior
|
||||
// for all comparison operators.
|
||||
suite.expect ((lhs == rhs) == (TERtoInt (lhs) == TERtoInt (rhs)));
|
||||
suite.expect ((lhs != rhs) == (TERtoInt (lhs) != TERtoInt (rhs)));
|
||||
suite.expect ((lhs < rhs) == (TERtoInt (lhs) < TERtoInt (rhs)));
|
||||
suite.expect ((lhs <= rhs) == (TERtoInt (lhs) <= TERtoInt (rhs)));
|
||||
suite.expect ((lhs > rhs) == (TERtoInt (lhs) > TERtoInt (rhs)));
|
||||
suite.expect ((lhs >= rhs) == (TERtoInt (lhs) >= TERtoInt (rhs)));
|
||||
}
|
||||
};
|
||||
|
||||
void testComparison()
|
||||
{
|
||||
// All of the TER-related types should be comparable.
|
||||
|
||||
// Examples of all the types we expect to successfully compare.
|
||||
static auto const ters = std::make_tuple (telLOCAL_ERROR, temMALFORMED,
|
||||
tefFAILURE, terRETRY, tesSUCCESS, tecCLAIM,
|
||||
NotTEC {telLOCAL_ERROR}, TER {tecCLAIM});
|
||||
static const int hiIndex {
|
||||
std::tuple_size<decltype (ters)>::value - 1};
|
||||
|
||||
// Verify that all types in the ters tuple can be compared with all
|
||||
// the other types in ters.
|
||||
testIterate<hiIndex, hiIndex, CheckComparable> (ters, *this);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testTransResultInfo();
|
||||
testConversion();
|
||||
testComparison();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(TER,protocol,ripple);
|
||||
|
||||
}
|
||||
} //namespace ripple
|
||||
|
||||
Reference in New Issue
Block a user