Skip to content

Commit

Permalink
hex: Use std::optional to report invalid input
Browse files Browse the repository at this point in the history
This replaces std::error_code and exceptions with std::optional<bytes>
to inform about invalid hex input.
  • Loading branch information
chfast committed May 20, 2022
1 parent b4cf713 commit 3016bf6
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 143 deletions.
91 changes: 15 additions & 76 deletions include/evmc/hex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

#include <cstdint>
#include <iterator>
#include <optional>
#include <string>
#include <string_view>
#include <system_error>

namespace evmc
{
Expand All @@ -17,74 +17,12 @@ using bytes = std::basic_string<uint8_t>;
/// String view of uint8_t chars.
using bytes_view = std::basic_string_view<uint8_t>;

/// Hex decoding error codes.
enum class hex_errc
{
/// Invalid hex digit encountered during decoding.
invalid_hex_digit = 1,

/// Input contains incomplete hex byte (length is odd).
incomplete_hex_byte_pair = 2,
};
} // namespace evmc

namespace std
{
/// Template specialization of std::is_error_code_enum for evmc::hex_errc.
/// This enables implicit conversions from evmc::hex_errc to std::error_code.
template <>
struct is_error_code_enum<evmc::hex_errc> : true_type
{};
} // namespace std

namespace evmc
{

/// Obtains a reference to the static error category object for hex errors.
inline const std::error_category& hex_category() noexcept
{
struct hex_category_impl : std::error_category
{
const char* name() const noexcept final { return "hex"; }

std::string message(int ev) const final
{
switch (static_cast<hex_errc>(ev))
{
case hex_errc::invalid_hex_digit:
return "invalid hex digit";
case hex_errc::incomplete_hex_byte_pair:
return "incomplete hex byte pair";
default:
return "unknown error";
}
}
};

// Create static category object. This involves mutex-protected dynamic initialization.
// Because of the C++ CWG defect 253, the {} syntax is used.
static const hex_category_impl category_instance{};

return category_instance;
}

/// Creates error_code object out of a hex error code value.
inline std::error_code make_error_code(hex_errc errc) noexcept
{
return {static_cast<int>(errc), hex_category()};
}

/// Hex decoding exception.
struct hex_error : std::system_error
{
using system_error::system_error;
};

/// Encode a byte to a hex string.
inline std::string hex(uint8_t b) noexcept
{
static constexpr auto hex_chars = "0123456789abcdef";
return {hex_chars[b >> 4], hex_chars[b & 0xf]};
static constexpr auto hex_digits = "0123456789abcdef";
return {hex_digits[b >> 4], hex_digits[b & 0xf]};
}

/// Encodes bytes as hex string.
Expand Down Expand Up @@ -121,7 +59,7 @@ inline constexpr bool isspace(char ch) noexcept
}

template <typename OutputIt>
inline constexpr hex_errc from_hex(std::string_view hex, OutputIt result) noexcept
inline constexpr bool from_hex(std::string_view hex, OutputIt result) noexcept
{
// Omit the optional 0x prefix.
if (hex.size() >= 2 && hex[0] == '0' && hex[1] == 'x')
Expand All @@ -136,7 +74,7 @@ inline constexpr hex_errc from_hex(std::string_view hex, OutputIt result) noexce

const int v = from_hex_digit(h);
if (v < 0)
return hex_errc::invalid_hex_digit;
return false;

if (hi_nibble == empty_mark)
{
Expand All @@ -149,14 +87,14 @@ inline constexpr hex_errc from_hex(std::string_view hex, OutputIt result) noexce
}
}

if (hi_nibble != empty_mark)
return hex_errc::incomplete_hex_byte_pair;
return {};
return hi_nibble == empty_mark;
}
} // namespace internal_hex

/// Validates hex encoded string.
inline std::error_code validate_hex(std::string_view hex) noexcept
///
/// @return True if the input is valid hex.
inline bool validate_hex(std::string_view hex) noexcept
{
struct noop_output_iterator
{
Expand All @@ -170,14 +108,15 @@ inline std::error_code validate_hex(std::string_view hex) noexcept

/// Decodes hex encoded string to bytes.
///
/// Throws hex_error with the appropriate error code.
inline bytes from_hex(std::string_view hex)
/// In case the input is invalid the returned value is std::nullopt.
/// This can happen if a non-hex digit or odd number of digits is encountered.
/// Whitespace in the input is ignored.
inline std::optional<bytes> from_hex(std::string_view hex)
{
bytes bs;
bs.reserve(hex.size() / 2);
const auto ec = internal_hex::from_hex(hex, std::back_inserter(bs));
if (static_cast<int>(ec) != 0)
throw hex_error{ec};
if (!internal_hex::from_hex(hex, std::back_inserter(bs)))
return {};
return bs;
}
} // namespace evmc
9 changes: 7 additions & 2 deletions lib/tooling/run.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,13 @@ int run(evmc::VM& vm,
out << (create ? "Creating and executing on " : "Executing on ") << rev << " with " << gas
<< " gas limit\n";

const auto code = from_hex(code_hex);
const auto input = from_hex(input_hex);
auto opt_code = from_hex(code_hex);
auto opt_input = from_hex(input_hex);
if (!opt_code || !opt_input)
throw std::invalid_argument{"invalid hex"};

const auto& code = *opt_code;
const auto& input = *opt_input;

MockedHost host;

Expand Down
6 changes: 3 additions & 3 deletions test/tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ set_tests_properties(${PROJECT_NAME}/evmc-tool/explicit_empty_input PROPERTIES P
add_evmc_tool_test(
invalid_hex_code
"--vm $<TARGET_FILE:evmc::example-vm> run 0x600"
"code: \\(incomplete hex byte pair\\) OR \\(File does not exist: 0x600\\)"
"code: \\(invalid hex\\) OR \\(File does not exist: 0x600\\)"
)

add_evmc_tool_test(
invalid_hex_input
"--vm $<TARGET_FILE:evmc::example-vm> run 0x --input aa0y"
"--input: \\(invalid hex digit\\) OR \\(File does not exist: aa0y\\)"
"--input: \\(invalid hex\\) OR \\(File does not exist: aa0y\\)"
)

add_evmc_tool_test(
Expand All @@ -78,7 +78,7 @@ add_evmc_tool_test(
add_evmc_tool_test(
invalid_code_file
"--vm $<TARGET_FILE:evmc::example-vm> run ${CMAKE_CURRENT_SOURCE_DIR}/invalid_code.evm"
"Error: invalid hex digit"
"Error: invalid hex"
)

add_evmc_tool_test(
Expand Down
8 changes: 4 additions & 4 deletions test/unittests/example_vm_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct Output
{
evmc::bytes bytes;

explicit Output(const char* output_hex) noexcept : bytes{evmc::from_hex(output_hex)} {}
explicit Output(const char* output_hex) noexcept : bytes{evmc::from_hex(output_hex).value()} {}

friend bool operator==(const evmc::result& result, const Output& expected) noexcept
{
Expand Down Expand Up @@ -45,8 +45,8 @@ class example_vm : public testing::Test
const char* code_hex,
const char* input_hex = "")
{
const auto code = evmc::from_hex(code_hex);
const auto input = evmc::from_hex(input_hex);
const auto code = evmc::from_hex(code_hex).value();
const auto input = evmc::from_hex(input_hex).value();

msg.gas = gas;
msg.input_data = input.data();
Expand Down Expand Up @@ -150,7 +150,7 @@ TEST_F(example_vm, revert_undefined)
TEST_F(example_vm, call)
{
// pseudo-Yul: call(3, 3, 3, 3, 3, 3, 3) return(0, msize())
const auto expected_output = evmc::from_hex("aabbcc");
const auto expected_output = evmc::from_hex("aabbcc").value();
host.call_result.output_data = expected_output.data();
host.call_result.output_size = expected_output.size();
const auto r = execute_in_example_vm(100, "6003808080808080f1596000f3");
Expand Down
68 changes: 15 additions & 53 deletions test/unittests/hex_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,42 +40,29 @@ TEST(hex, from_hex)
EXPECT_EQ(from_hex("00bc01000C"), (bytes{0x00, 0xbc, 0x01, 0x00, 0x0c}));
}

static std::error_code catch_hex_error(const char* input)
{
try
{
from_hex(input);
}
catch (const hex_error& e)
{
return e.code();
}
return {};
}

TEST(hex, from_hex_odd_length)
{
EXPECT_EQ(catch_hex_error("0"), hex_errc::incomplete_hex_byte_pair);
EXPECT_EQ(catch_hex_error("1"), hex_errc::incomplete_hex_byte_pair);
EXPECT_EQ(catch_hex_error("123"), hex_errc::incomplete_hex_byte_pair);
EXPECT_EQ(from_hex("0"), std::nullopt);
EXPECT_EQ(from_hex("1"), std::nullopt);
EXPECT_EQ(from_hex("123"), std::nullopt);
}

TEST(hex, from_hex_not_hex_digit)
{
EXPECT_EQ(catch_hex_error("0g"), hex_errc::invalid_hex_digit);
EXPECT_EQ(catch_hex_error("000h"), hex_errc::invalid_hex_digit);
EXPECT_EQ(catch_hex_error("ffffffzz"), hex_errc::invalid_hex_digit);
EXPECT_EQ(from_hex("0g"), std::nullopt);
EXPECT_EQ(from_hex("000h"), std::nullopt);
EXPECT_EQ(from_hex("ffffffzz"), std::nullopt);
}

TEST(hex, from_hex_0x_prefix)
{
EXPECT_EQ(from_hex("0x"), bytes{});
EXPECT_EQ(from_hex("0x00"), bytes{0x00});
EXPECT_EQ(from_hex("0x01020304"), (bytes{0x01, 0x02, 0x03, 0x04}));
EXPECT_EQ(catch_hex_error("0x123"), hex_errc::incomplete_hex_byte_pair);
EXPECT_EQ(catch_hex_error("00x"), hex_errc::invalid_hex_digit);
EXPECT_EQ(catch_hex_error("00x0"), hex_errc::invalid_hex_digit);
EXPECT_EQ(catch_hex_error("0x001y"), hex_errc::invalid_hex_digit);
EXPECT_EQ(from_hex("0x123"), std::nullopt);
EXPECT_EQ(from_hex("00x"), std::nullopt);
EXPECT_EQ(from_hex("00x0"), std::nullopt);
EXPECT_EQ(from_hex("0x001y"), std::nullopt);
}

TEST(hex, from_hex_skip_whitespace)
Expand All @@ -87,36 +74,11 @@ TEST(hex, from_hex_skip_whitespace)

TEST(hex, validate_hex)
{
EXPECT_FALSE(validate_hex(""));
EXPECT_FALSE(validate_hex("0x"));
EXPECT_FALSE(validate_hex("01"));
EXPECT_EQ(validate_hex("0"), hex_errc::incomplete_hex_byte_pair);
EXPECT_EQ(validate_hex("WXYZ"), hex_errc::invalid_hex_digit);
}

TEST(hex, hex_error_code)
{
std::error_code ec1 = hex_errc::invalid_hex_digit;
EXPECT_EQ(ec1.value(), 1);
EXPECT_EQ(ec1.message(), "invalid hex digit");

std::error_code ec2 = hex_errc::incomplete_hex_byte_pair;
EXPECT_EQ(ec2.value(), 2);
EXPECT_EQ(ec2.message(), "incomplete hex byte pair");
}

TEST(hex, hex_category_inspection)
{
EXPECT_STREQ(hex_category().name(), "hex");
}

TEST(hex, hex_category_comparison)
{
std::error_code ec1 = hex_errc::invalid_hex_digit;
EXPECT_EQ(ec1.category(), hex_category());

std::error_code ec2 = hex_errc::incomplete_hex_byte_pair;
EXPECT_EQ(ec2.category(), hex_category());
EXPECT_TRUE(validate_hex(""));
EXPECT_TRUE(validate_hex("0x"));
EXPECT_TRUE(validate_hex("01"));
EXPECT_FALSE(validate_hex("0"));
EXPECT_FALSE(validate_hex("WXYZ"));
}

TEST(hex, isspace)
Expand Down
8 changes: 3 additions & 5 deletions tools/evmc/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ namespace
/// @todo The file content is expected to be a hex string but not validated.
std::string load_hex(const std::string& str)
{
const auto error_code = evmc::validate_hex(str);
if (!error_code)
if (evmc::validate_hex(str))
return str;

// Must be a file path.
Expand All @@ -30,9 +29,8 @@ struct HexValidator : public CLI::Validator
{
name_ = "HEX";
func_ = [](const std::string& str) -> std::string {
const auto error_code = evmc::validate_hex(str);
if (error_code)
return error_code.message();
if (!evmc::validate_hex(str))
return "invalid hex";
return {};
};
}
Expand Down

0 comments on commit 3016bf6

Please sign in to comment.