From 956e07cf51799145fe93f8347237fb031babe074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 12 May 2022 22:03:32 +0200 Subject: [PATCH] hex: Use std::optional to report invalid input This replaces std::error_code and exceptions with std::optional to inform about invalid hex input. --- include/evmc/hex.hpp | 91 +++++------------------------- lib/tooling/run.cpp | 9 ++- test/tools/CMakeLists.txt | 6 +- test/unittests/example_vm_test.cpp | 8 +-- test/unittests/hex_test.cpp | 68 +++++----------------- tools/evmc/main.cpp | 8 +-- 6 files changed, 47 insertions(+), 143 deletions(-) diff --git a/include/evmc/hex.hpp b/include/evmc/hex.hpp index 927f39fe8..9a07ca213 100644 --- a/include/evmc/hex.hpp +++ b/include/evmc/hex.hpp @@ -5,9 +5,9 @@ #include #include +#include #include #include -#include namespace evmc { @@ -17,74 +17,12 @@ using bytes = std::basic_string; /// String view of uint8_t chars. using bytes_view = std::basic_string_view; -/// 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 enabled implicit conversions from evmc::hex_errc to std::error_code. -template <> -struct is_error_code_enum : 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(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(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. @@ -121,7 +59,7 @@ inline constexpr bool isspace(char ch) noexcept } template -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') @@ -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) { @@ -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 { @@ -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 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(ec) != 0) - throw hex_error{ec}; + if (!internal_hex::from_hex(hex, std::back_inserter(bs))) + return {}; return bs; } } // namespace evmc diff --git a/lib/tooling/run.cpp b/lib/tooling/run.cpp index 8eceb6abf..7dac8edc7 100644 --- a/lib/tooling/run.cpp +++ b/lib/tooling/run.cpp @@ -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; diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 6f26b1b10..6f4fa46ae 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -54,13 +54,13 @@ set_tests_properties(${PROJECT_NAME}/evmc-tool/explicit_empty_input PROPERTIES P add_evmc_tool_test( invalid_hex_code "--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 $ 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( @@ -78,7 +78,7 @@ add_evmc_tool_test( add_evmc_tool_test( invalid_code_file "--vm $ run ${CMAKE_CURRENT_SOURCE_DIR}/invalid_code.evm" - "Error: invalid hex digit" + "Error: invalid hex" ) add_evmc_tool_test( diff --git a/test/unittests/example_vm_test.cpp b/test/unittests/example_vm_test.cpp index c8d79449d..b62272c33 100644 --- a/test/unittests/example_vm_test.cpp +++ b/test/unittests/example_vm_test.cpp @@ -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 { @@ -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(); @@ -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"); diff --git a/test/unittests/hex_test.cpp b/test/unittests/hex_test.cpp index 003d3f764..5f9e272fd 100644 --- a/test/unittests/hex_test.cpp +++ b/test/unittests/hex_test.cpp @@ -39,31 +39,18 @@ 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) @@ -71,10 +58,10 @@ 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) @@ -86,36 +73,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) diff --git a/tools/evmc/main.cpp b/tools/evmc/main.cpp index 703d81539..72f02ab96 100644 --- a/tools/evmc/main.cpp +++ b/tools/evmc/main.cpp @@ -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. @@ -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 {}; }; }