Skip to content

Commit

Permalink
Merge pull request #643 from ethereum/hex_inline
Browse files Browse the repository at this point in the history
Convert evmc::hex library into single hex.hpp header
  • Loading branch information
chfast committed May 20, 2022
2 parents a1d2845 + 3016bf6 commit 5e3d163
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 244 deletions.
125 changes: 88 additions & 37 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,55 +17,106 @@ 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,
};
/// Encode a byte to a hex string.
inline std::string hex(uint8_t b) noexcept
{
static constexpr auto hex_digits = "0123456789abcdef";
return {hex_digits[b >> 4], hex_digits[b & 0xf]};
}

/// Obtains a reference to the static error category object for hex errors.
const std::error_category& hex_category() noexcept;
/// 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;
}

/// Creates error_code object out of a hex error code value.
inline std::error_code make_error_code(hex_errc errc) noexcept
namespace internal_hex
{
/// 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
{
return {static_cast<int>(errc), hex_category()};
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
return -1;
}

/// Hex decoding exception.
struct hex_error : std::system_error
/// The constexpr variant of std::isspace().
inline constexpr bool isspace(char ch) noexcept
{
using system_error::system_error;
};
// Implementation taken from LLVM's libc.
return ch == ' ' || (static_cast<unsigned>(ch) - '\t') < 5;
}

/// Encode a byte to a hex string.
inline std::string hex(uint8_t b) noexcept
template <typename OutputIt>
inline constexpr bool from_hex(std::string_view hex, OutputIt result) noexcept
{
static constexpr auto hex_chars = "0123456789abcdef";
return {hex_chars[b >> 4], hex_chars[b & 0xf]};
// Omit the optional 0x prefix.
if (hex.size() >= 2 && hex[0] == '0' && hex[1] == 'x')
hex.remove_prefix(2);

constexpr int empty_mark = -1;
int hi_nibble = empty_mark;
for (const auto h : hex)
{
if (isspace(h))
continue;

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

if (hi_nibble == empty_mark)
{
hi_nibble = v << 4;
}
else
{
*result++ = static_cast<uint8_t>(hi_nibble | v);
hi_nibble = empty_mark;
}
}

return hi_nibble == empty_mark;
}
} // namespace internal_hex

/// Validates hex encoded string.
std::error_code validate_hex(std::string_view hex) noexcept;

/// Decodes hex encoded string to bytes.
///
/// Throws hex_error with the appropriate error code.
bytes from_hex(std::string_view hex);
/// @return True if the input is valid hex.
inline bool 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)
};

/// Encodes bytes as hex string.
std::string hex(bytes_view bs);
} // namespace evmc
return internal_hex::from_hex(hex, noop_output_iterator{});
}

namespace std
/// Decodes hex encoded string to bytes.
///
/// 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)
{
/// 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<evmc::hex_errc> : true_type
{};
} // namespace std
bytes bs;
bs.reserve(hex.size() / 2);
if (!internal_hex::from_hex(hex, std::back_inserter(bs)))
return {};
return bs;
}
} // namespace evmc
1 change: 0 additions & 1 deletion lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ target_compile_features(evmc_cpp INTERFACE cxx_std_17)
target_include_directories(evmc_cpp INTERFACE $<BUILD_INTERFACE:${EVMC_INCLUDE_DIR}>$<INSTALL_INTERFACE:include>)
target_link_libraries(evmc_cpp INTERFACE evmc::evmc)

add_subdirectory(hex)
add_subdirectory(instructions)
add_subdirectory(loader)
add_subdirectory(mocked_host)
Expand Down
18 changes: 0 additions & 18 deletions lib/hex/CMakeLists.txt

This file was deleted.

122 changes: 0 additions & 122 deletions lib/hex/hex.cpp

This file was deleted.

2 changes: 1 addition & 1 deletion lib/tooling/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
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
1 change: 0 additions & 1 deletion test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
10 changes: 5 additions & 5 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 All @@ -43,10 +43,10 @@ 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);
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

0 comments on commit 5e3d163

Please sign in to comment.