From 1fb93b4e56d2e99cd0d39b85a3ec7768e3afef11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 12 May 2022 12:08:39 +0200 Subject: [PATCH 1/5] Convert evmc::hex library into single hex.hpp header --- include/evmc/hex.hpp | 139 ++++++++++++++++++++++++++++++---- lib/CMakeLists.txt | 1 - lib/hex/CMakeLists.txt | 18 ----- lib/hex/hex.cpp | 122 ----------------------------- lib/tooling/CMakeLists.txt | 2 +- test/unittests/CMakeLists.txt | 1 - 6 files changed, 125 insertions(+), 158 deletions(-) delete mode 100644 lib/hex/CMakeLists.txt delete mode 100644 lib/hex/hex.cpp diff --git a/include/evmc/hex.hpp b/include/evmc/hex.hpp index 476a622b2..bfe52c172 100644 --- a/include/evmc/hex.hpp +++ b/include/evmc/hex.hpp @@ -3,6 +3,7 @@ // Licensed under the Apache License, Version 2.0. #pragma once +#include #include #include #include @@ -26,9 +27,47 @@ enum class hex_errc /// 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. -const std::error_category& hex_category() noexcept; +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 @@ -49,23 +88,93 @@ inline std::string hex(uint8_t b) noexcept return {hex_chars[b >> 4], hex_chars[b & 0xf]}; } +/// Encodes bytes as hex string. +inline std::string hex(bytes_view bs) +{ + std::string str; + str.reserve(bs.size() * 2); + for (const auto b : bs) + str += hex(b); + return str; +} + +namespace internal_hex +{ +inline constexpr int from_hex_digit(char h) +{ + if (h >= '0' && h <= '9') + return h - '0'; + else if (h >= 'a' && h <= 'f') + return h - 'a' + 10; + else if (h >= 'A' && h <= 'F') + return h - 'A' + 10; + else + throw hex_error{hex_errc::invalid_hex_digit}; +} + +template +inline constexpr void from_hex(std::string_view hex, OutputIt result) +{ + // TODO: This can be implemented with hex_decode_iterator and std::copy. + + // Omit the optional 0x prefix. + const auto hex_begin = + (hex.size() >= 2 && hex[0] == '0' && hex[1] == 'x') ? hex.begin() + 2 : hex.begin(); + + constexpr int empty_byte_mark = -1; + int b = empty_byte_mark; + for (auto it = hex_begin; it != hex.end(); ++it) + { + const auto h = *it; + if (std::isspace(h)) + continue; + + const int v = from_hex_digit(h); + if (b == empty_byte_mark) + { + b = v << 4; + } + else + { + *result++ = static_cast(b | v); + b = empty_byte_mark; + } + } + + if (b != empty_byte_mark) + throw hex_error{hex_errc::incomplete_hex_byte_pair}; +} +} // namespace internal_hex + /// Validates hex encoded string. -std::error_code validate_hex(std::string_view hex) noexcept; +inline std::error_code validate_hex(std::string_view hex) noexcept +{ + struct noop_output_iterator + { + uint8_t sink = {}; + uint8_t& operator*() noexcept { return sink; } + noop_output_iterator operator++(int) noexcept { return *this; } // NOLINT(cert-dcl21-cpp) + }; + + try + { + internal_hex::from_hex(hex, noop_output_iterator{}); + return {}; + } + catch (const hex_error& e) + { + return e.code(); + } +} /// Decodes hex encoded string to bytes. /// /// Throws hex_error with the appropriate error code. -bytes from_hex(std::string_view hex); - -/// Encodes bytes as hex string. -std::string hex(bytes_view bs); -} // namespace evmc - -namespace std +inline bytes from_hex(std::string_view hex) { -/// 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 + bytes bs; + bs.reserve(hex.size() / 2); + internal_hex::from_hex(hex, std::back_inserter(bs)); + return bs; +} +} // namespace evmc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 32b5757ee..dfaf29cd8 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -13,7 +13,6 @@ target_compile_features(evmc_cpp INTERFACE cxx_std_17) target_include_directories(evmc_cpp INTERFACE $$) target_link_libraries(evmc_cpp INTERFACE evmc::evmc) -add_subdirectory(hex) add_subdirectory(instructions) add_subdirectory(loader) add_subdirectory(mocked_host) diff --git a/lib/hex/CMakeLists.txt b/lib/hex/CMakeLists.txt deleted file mode 100644 index f753649f8..000000000 --- a/lib/hex/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -# EVMC: Ethereum Client-VM Connector API. -# Copyright 2021 The EVMC Authors. -# Licensed under the Apache License, Version 2.0. - -add_library( - hex STATIC - ${EVMC_INCLUDE_DIR}/evmc/hex.hpp - hex.cpp -) - -add_library(evmc::hex ALIAS hex) -target_compile_features(hex PUBLIC cxx_std_17) -target_include_directories(hex PUBLIC $$) -set_target_properties(hex PROPERTIES POSITION_INDEPENDENT_CODE TRUE) - -if(EVMC_INSTALL) - install(TARGETS hex EXPORT evmcTargets DESTINATION ${CMAKE_INSTALL_LIBDIR}) -endif() diff --git a/lib/hex/hex.cpp b/lib/hex/hex.cpp deleted file mode 100644 index 458bd2565..000000000 --- a/lib/hex/hex.cpp +++ /dev/null @@ -1,122 +0,0 @@ -// EVMC: Ethereum Client-VM Connector API. -// Copyright 2021 The EVMC Authors. -// Licensed under the Apache License, Version 2.0. - -#include -#include - -namespace evmc -{ -namespace -{ -inline int from_hex_digit(char h) -{ - if (h >= '0' && h <= '9') - return h - '0'; - else if (h >= 'a' && h <= 'f') - return h - 'a' + 10; - else if (h >= 'A' && h <= 'F') - return h - 'A' + 10; - else - throw hex_error{hex_errc::invalid_hex_digit}; -} - -template -inline void from_hex(std::string_view hex, OutputIt result) -{ - // TODO: This can be implemented with hex_decode_iterator and std::copy. - - // Omit the optional 0x prefix. - const auto hex_begin = - (hex.size() >= 2 && hex[0] == '0' && hex[1] == 'x') ? hex.begin() + 2 : hex.begin(); - - constexpr int empty_byte_mark = -1; - int b = empty_byte_mark; - for (auto it = hex_begin; it != hex.end(); ++it) - { - const auto h = *it; - if (std::isspace(h)) - continue; - - const int v = from_hex_digit(h); - if (b == empty_byte_mark) - { - b = v << 4; - } - else - { - *result++ = static_cast(b | v); - b = empty_byte_mark; - } - } - - if (b != empty_byte_mark) - throw hex_error{hex_errc::incomplete_hex_byte_pair}; -} - -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"; - } - } -}; -} // namespace - -const std::error_category& hex_category() noexcept -{ - // 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; -} - -std::error_code validate_hex(std::string_view hex) noexcept -{ - struct noop_output_iterator - { - uint8_t sink = {}; - uint8_t& operator*() noexcept { return sink; } - noop_output_iterator operator++(int) noexcept { return *this; } // NOLINT(cert-dcl21-cpp) - }; - - try - { - from_hex(hex, noop_output_iterator{}); - return {}; - } - catch (const hex_error& e) - { - return e.code(); - } -} - -bytes from_hex(std::string_view hex) -{ - bytes bs; - bs.reserve(hex.size() / 2); - from_hex(hex, std::back_inserter(bs)); - return bs; -} - -std::string hex(bytes_view bs) -{ - std::string str; - str.reserve(bs.size() * 2); - for (const auto b : bs) - str += hex(b); - return str; -} - -} // namespace evmc diff --git a/lib/tooling/CMakeLists.txt b/lib/tooling/CMakeLists.txt index b6a3f7f58..d19cd0c2e 100644 --- a/lib/tooling/CMakeLists.txt +++ b/lib/tooling/CMakeLists.txt @@ -5,7 +5,7 @@ add_library(tooling STATIC) add_library(evmc::tooling ALIAS tooling) target_compile_features(tooling PUBLIC cxx_std_17) -target_link_libraries(tooling PUBLIC evmc::evmc_cpp evmc::mocked_host evmc::hex) +target_link_libraries(tooling PUBLIC evmc::evmc_cpp evmc::mocked_host) target_sources( tooling PRIVATE diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 16dd818dc..14f68a8d2 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -36,7 +36,6 @@ target_link_libraries( evmc::instructions evmc::evmc_cpp evmc::tooling - evmc::hex GTest::gtest_main ) target_include_directories(evmc-unittests PRIVATE ${PROJECT_SOURCE_DIR}) From 6d6852a958c49dd69f7a422c6015396ae0ebc9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 12 May 2022 15:17:25 +0200 Subject: [PATCH 2/5] hex: Tune implementation --- include/evmc/hex.hpp | 61 ++++++++++++++++-------------- test/unittests/example_vm_test.cpp | 2 +- test/unittests/hex_test.cpp | 27 +++++++++++++ 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/include/evmc/hex.hpp b/include/evmc/hex.hpp index bfe52c172..927f39fe8 100644 --- a/include/evmc/hex.hpp +++ b/include/evmc/hex.hpp @@ -3,7 +3,6 @@ // Licensed under the Apache License, Version 2.0. #pragma once -#include #include #include #include @@ -100,7 +99,9 @@ inline std::string hex(bytes_view bs) namespace internal_hex { -inline constexpr int from_hex_digit(char h) +/// Extracts the nibble value out of a hex digit. +/// Returns -1 in case of invalid hex digit. +inline constexpr int from_hex_digit(char h) noexcept { if (h >= '0' && h <= '9') return h - '0'; @@ -109,40 +110,48 @@ inline constexpr int from_hex_digit(char h) else if (h >= 'A' && h <= 'F') return h - 'A' + 10; else - throw hex_error{hex_errc::invalid_hex_digit}; + return -1; } -template -inline constexpr void from_hex(std::string_view hex, OutputIt result) +/// The constexpr variant of std::isspace(). +inline constexpr bool isspace(char ch) noexcept { - // TODO: This can be implemented with hex_decode_iterator and std::copy. + // Implementation taken from LLVM's libc. + return ch == ' ' || (static_cast(ch) - '\t') < 5; +} +template +inline constexpr hex_errc from_hex(std::string_view hex, OutputIt result) noexcept +{ // Omit the optional 0x prefix. - const auto hex_begin = - (hex.size() >= 2 && hex[0] == '0' && hex[1] == 'x') ? hex.begin() + 2 : hex.begin(); + if (hex.size() >= 2 && hex[0] == '0' && hex[1] == 'x') + hex.remove_prefix(2); - constexpr int empty_byte_mark = -1; - int b = empty_byte_mark; - for (auto it = hex_begin; it != hex.end(); ++it) + constexpr int empty_mark = -1; + int hi_nibble = empty_mark; + for (const auto h : hex) { - const auto h = *it; - if (std::isspace(h)) + if (isspace(h)) continue; const int v = from_hex_digit(h); - if (b == empty_byte_mark) + if (v < 0) + return hex_errc::invalid_hex_digit; + + if (hi_nibble == empty_mark) { - b = v << 4; + hi_nibble = v << 4; } else { - *result++ = static_cast(b | v); - b = empty_byte_mark; + *result++ = static_cast(hi_nibble | v); + hi_nibble = empty_mark; } } - if (b != empty_byte_mark) - throw hex_error{hex_errc::incomplete_hex_byte_pair}; + if (hi_nibble != empty_mark) + return hex_errc::incomplete_hex_byte_pair; + return {}; } } // namespace internal_hex @@ -156,15 +165,7 @@ inline std::error_code validate_hex(std::string_view hex) noexcept noop_output_iterator operator++(int) noexcept { return *this; } // NOLINT(cert-dcl21-cpp) }; - try - { - internal_hex::from_hex(hex, noop_output_iterator{}); - return {}; - } - catch (const hex_error& e) - { - return e.code(); - } + return internal_hex::from_hex(hex, noop_output_iterator{}); } /// Decodes hex encoded string to bytes. @@ -174,7 +175,9 @@ inline bytes from_hex(std::string_view hex) { bytes bs; bs.reserve(hex.size() / 2); - internal_hex::from_hex(hex, std::back_inserter(bs)); + const auto ec = internal_hex::from_hex(hex, std::back_inserter(bs)); + if (static_cast(ec) != 0) + throw hex_error{ec}; return bs; } } // namespace evmc diff --git a/test/unittests/example_vm_test.cpp b/test/unittests/example_vm_test.cpp index 97fe2e036..c8d79449d 100644 --- a/test/unittests/example_vm_test.cpp +++ b/test/unittests/example_vm_test.cpp @@ -43,7 +43,7 @@ class example_vm : public testing::Test evmc::result execute_in_example_vm(int64_t gas, const char* code_hex, - const char* input_hex = "") noexcept + const char* input_hex = "") { const auto code = evmc::from_hex(code_hex); const auto input = evmc::from_hex(input_hex); diff --git a/test/unittests/hex_test.cpp b/test/unittests/hex_test.cpp index 4720e480c..f05406e84 100644 --- a/test/unittests/hex_test.cpp +++ b/test/unittests/hex_test.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace evmc; @@ -117,3 +118,29 @@ TEST(hex, hex_category_comparison) std::error_code ec2 = hex_errc::incomplete_hex_byte_pair; EXPECT_EQ(ec2.category(), hex_category()); } + +TEST(hex, isspace) +{ + // Test internal isspace() compliance with std::isspace(). + // The https://en.cppreference.com/w/cpp/string/byte/isspace has the list of "space" characters. + + for (int i = int{std::numeric_limits::min()}; i <= std::numeric_limits::max(); ++i) + { + const auto c = static_cast(i); + EXPECT_EQ(evmc::internal_hex::isspace(c), (std::isspace(c) != 0)); + switch (c) + { + case ' ': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + EXPECT_TRUE(evmc::internal_hex::isspace(c)); + break; + default: + EXPECT_FALSE(evmc::internal_hex::isspace(c)); + break; + } + } +} From a476d6e7831d0a068fe5907003d5f8cc9685cd65 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 3/5] 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 f05406e84..fb14cadd1 100644 --- a/test/unittests/hex_test.cpp +++ b/test/unittests/hex_test.cpp @@ -40,31 +40,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) @@ -72,10 +59,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) @@ -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) 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 {}; }; } From fe0d43fb23d564331222c5f44d8f26d6628e1dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 12 May 2022 22:54:08 +0200 Subject: [PATCH 4/5] C++: Rework hex literals parsing --- include/evmc/evmc.hpp | 69 ++++++++++--------------------------- include/evmc/hex.hpp | 8 ++--- test/unittests/hex_test.cpp | 6 ++-- 3 files changed, 25 insertions(+), 58 deletions(-) diff --git a/include/evmc/evmc.hpp b/include/evmc/evmc.hpp index 57f0faa9d..7afffacd4 100644 --- a/include/evmc/evmc.hpp +++ b/include/evmc/evmc.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -280,71 +281,37 @@ inline constexpr bytes32::operator bool() const noexcept namespace literals { -namespace internal -{ -constexpr int from_hex(char c) noexcept -{ - return (c >= 'a' && c <= 'f') ? c - ('a' - 10) : - (c >= 'A' && c <= 'F') ? c - ('A' - 10) : - c - '0'; -} - -constexpr uint8_t byte(const char* s, size_t i) noexcept -{ - return static_cast((from_hex(s[2 * i]) << 4) | from_hex(s[2 * i + 1])); -} +/// Breaks compilation and reports error string in constexpr context. +inline void error([[maybe_unused]] const char* message) noexcept {} +/// Converts a raw literal into value of type T. template -T from_hex(const char*) noexcept; - -template <> -constexpr bytes32 from_hex(const char* s) noexcept -{ - return { - {{byte(s, 0), byte(s, 1), byte(s, 2), byte(s, 3), byte(s, 4), byte(s, 5), byte(s, 6), - byte(s, 7), byte(s, 8), byte(s, 9), byte(s, 10), byte(s, 11), byte(s, 12), byte(s, 13), - byte(s, 14), byte(s, 15), byte(s, 16), byte(s, 17), byte(s, 18), byte(s, 19), byte(s, 20), - byte(s, 21), byte(s, 22), byte(s, 23), byte(s, 24), byte(s, 25), byte(s, 26), byte(s, 27), - byte(s, 28), byte(s, 29), byte(s, 30), byte(s, 31)}}}; -} - -template <> -constexpr address from_hex
(const char* s) noexcept +constexpr T to(std::string_view s) noexcept { - return { - {{byte(s, 0), byte(s, 1), byte(s, 2), byte(s, 3), byte(s, 4), byte(s, 5), byte(s, 6), - byte(s, 7), byte(s, 8), byte(s, 9), byte(s, 10), byte(s, 11), byte(s, 12), byte(s, 13), - byte(s, 14), byte(s, 15), byte(s, 16), byte(s, 17), byte(s, 18), byte(s, 19)}}}; -} + if (s == "0") + return T{}; -template -constexpr T from_literal() noexcept -{ - constexpr auto size = sizeof...(c); - constexpr char literal[] = {c...}; - constexpr bool is_simple_zero = size == 1 && literal[0] == '0'; + if (s[0] != '0' || s[1] != 'x') + error("literal must be in hexadecimal notation"); - static_assert(is_simple_zero || (literal[0] == '0' && literal[1] == 'x'), - "literal must be in hexadecimal notation"); - static_assert(is_simple_zero || size == 2 * sizeof(T) + 2, - "literal must match the result type size"); + if (s.length() != 2 * sizeof(T) + 2) + error("literal must match the result type size"); - return is_simple_zero ? T{} : from_hex(&literal[2]); + T r{}; + internal::from_hex(s, r.bytes); + return r; } -} // namespace internal /// Literal for evmc::address. -template -constexpr address operator""_address() noexcept +constexpr address operator""_address(const char* s) noexcept { - return internal::from_literal(); + return to
(s); } /// Literal for evmc::bytes32. -template -constexpr bytes32 operator""_bytes32() noexcept +constexpr bytes32 operator""_bytes32(const char* s) noexcept { - return internal::from_literal(); + return to(s); } } // namespace literals diff --git a/include/evmc/hex.hpp b/include/evmc/hex.hpp index 9a07ca213..67fc6305f 100644 --- a/include/evmc/hex.hpp +++ b/include/evmc/hex.hpp @@ -35,7 +35,7 @@ inline std::string hex(bytes_view bs) return str; } -namespace internal_hex +namespace internal { /// Extracts the nibble value out of a hex digit. /// Returns -1 in case of invalid hex digit. @@ -89,7 +89,7 @@ inline constexpr bool from_hex(std::string_view hex, OutputIt result) noexcept return hi_nibble == empty_mark; } -} // namespace internal_hex +} // namespace internal /// Validates hex encoded string. /// @@ -103,7 +103,7 @@ inline bool validate_hex(std::string_view hex) noexcept noop_output_iterator operator++(int) noexcept { return *this; } // NOLINT(cert-dcl21-cpp) }; - return internal_hex::from_hex(hex, noop_output_iterator{}); + return internal::from_hex(hex, noop_output_iterator{}); } /// Decodes hex encoded string to bytes. @@ -115,7 +115,7 @@ inline std::optional from_hex(std::string_view hex) { bytes bs; bs.reserve(hex.size() / 2); - if (!internal_hex::from_hex(hex, std::back_inserter(bs))) + if (!internal::from_hex(hex, std::back_inserter(bs))) return {}; return bs; } diff --git a/test/unittests/hex_test.cpp b/test/unittests/hex_test.cpp index fb14cadd1..74d441aee 100644 --- a/test/unittests/hex_test.cpp +++ b/test/unittests/hex_test.cpp @@ -89,7 +89,7 @@ TEST(hex, isspace) for (int i = int{std::numeric_limits::min()}; i <= std::numeric_limits::max(); ++i) { const auto c = static_cast(i); - EXPECT_EQ(evmc::internal_hex::isspace(c), (std::isspace(c) != 0)); + EXPECT_EQ(evmc::internal::isspace(c), (std::isspace(c) != 0)); switch (c) { case ' ': @@ -98,10 +98,10 @@ TEST(hex, isspace) case '\r': case '\t': case '\v': - EXPECT_TRUE(evmc::internal_hex::isspace(c)); + EXPECT_TRUE(evmc::internal::isspace(c)); break; default: - EXPECT_FALSE(evmc::internal_hex::isspace(c)); + EXPECT_FALSE(evmc::internal::isspace(c)); break; } } From 02a2af1fd78e6eda93240d86d8e849a0eac66ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 13 May 2022 17:11:28 +0200 Subject: [PATCH 5/5] C++: Refactor {address,bytes32} constructors --- include/evmc/evmc.hpp | 70 ++++++++----------------------------------- 1 file changed, 12 insertions(+), 58 deletions(-) diff --git a/include/evmc/evmc.hpp b/include/evmc/evmc.hpp index 7afffacd4..9cff74e75 100644 --- a/include/evmc/evmc.hpp +++ b/include/evmc/evmc.hpp @@ -31,34 +31,17 @@ struct address : evmc_address /// Default and converting constructor. /// /// Initializes bytes to zeros if not other @p init value provided. - constexpr address(evmc_address init = {}) noexcept : evmc_address{init} {} + inline constexpr address(evmc_address init = {}) noexcept : evmc_address{init} {} /// Converting constructor from unsigned integer value. /// /// This constructor assigns the @p v value to the last 8 bytes [12:19] /// in big-endian order. - constexpr explicit address(uint64_t v) noexcept - : evmc_address{{0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - static_cast(v >> 56), - static_cast(v >> 48), - static_cast(v >> 40), - static_cast(v >> 32), - static_cast(v >> 24), - static_cast(v >> 16), - static_cast(v >> 8), - static_cast(v >> 0)}} - {} + inline constexpr explicit address(uint64_t v) noexcept : evmc_address{} + { + for (size_t i = sizeof(bytes) - sizeof(v); i < sizeof(bytes); ++i) + bytes[i] = static_cast(v >> (8 * (sizeof(bytes) - 1 - i))); + } /// Explicit operator converting to bool. inline constexpr explicit operator bool() const noexcept; @@ -75,46 +58,17 @@ struct bytes32 : evmc_bytes32 /// Default and converting constructor. /// /// Initializes bytes to zeros if not other @p init value provided. - constexpr bytes32(evmc_bytes32 init = {}) noexcept : evmc_bytes32{init} {} + inline constexpr bytes32(evmc_bytes32 init = {}) noexcept : evmc_bytes32{init} {} /// Converting constructor from unsigned integer value. /// /// This constructor assigns the @p v value to the last 8 bytes [24:31] /// in big-endian order. - constexpr explicit bytes32(uint64_t v) noexcept - : evmc_bytes32{{0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - static_cast(v >> 56), - static_cast(v >> 48), - static_cast(v >> 40), - static_cast(v >> 32), - static_cast(v >> 24), - static_cast(v >> 16), - static_cast(v >> 8), - static_cast(v >> 0)}} - {} + inline constexpr explicit bytes32(uint64_t v) noexcept : evmc_bytes32{} + { + for (size_t i = sizeof(bytes) - sizeof(v); i < sizeof(bytes); ++i) + bytes[i] = static_cast(v >> (8 * (sizeof(bytes) - 1 - i))); + } /// Explicit operator converting to bool. inline constexpr explicit operator bool() const noexcept;