Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit a476d6e

Browse files
committed
hex: Use std::optional to report invalid input
This replaces std::error_code and exceptions with std::optional<bytes> to inform about invalid hex input.
1 parent 6d6852a commit a476d6e

File tree

6 files changed

+47
-143
lines changed

6 files changed

+47
-143
lines changed

include/evmc/hex.hpp

Lines changed: 15 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
#include <cstdint>
77
#include <iterator>
8+
#include <optional>
89
#include <string>
910
#include <string_view>
10-
#include <system_error>
1111

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

20-
/// Hex decoding error codes.
21-
enum class hex_errc
22-
{
23-
/// Invalid hex digit encountered during decoding.
24-
invalid_hex_digit = 1,
25-
26-
/// Input contains incomplete hex byte (length is odd).
27-
incomplete_hex_byte_pair = 2,
28-
};
29-
} // namespace evmc
30-
31-
namespace std
32-
{
33-
/// Template specialization of std::is_error_code_enum for evmc::hex_errc.
34-
/// This enabled implicit conversions from evmc::hex_errc to std::error_code.
35-
template <>
36-
struct is_error_code_enum<evmc::hex_errc> : true_type
37-
{};
38-
} // namespace std
39-
40-
namespace evmc
41-
{
42-
43-
/// Obtains a reference to the static error category object for hex errors.
44-
inline const std::error_category& hex_category() noexcept
45-
{
46-
struct hex_category_impl : std::error_category
47-
{
48-
const char* name() const noexcept final { return "hex"; }
49-
50-
std::string message(int ev) const final
51-
{
52-
switch (static_cast<hex_errc>(ev))
53-
{
54-
case hex_errc::invalid_hex_digit:
55-
return "invalid hex digit";
56-
case hex_errc::incomplete_hex_byte_pair:
57-
return "incomplete hex byte pair";
58-
default:
59-
return "unknown error";
60-
}
61-
}
62-
};
63-
64-
// Create static category object. This involves mutex-protected dynamic initialization.
65-
// Because of the C++ CWG defect 253, the {} syntax is used.
66-
static const hex_category_impl category_instance{};
67-
68-
return category_instance;
69-
}
70-
71-
/// Creates error_code object out of a hex error code value.
72-
inline std::error_code make_error_code(hex_errc errc) noexcept
73-
{
74-
return {static_cast<int>(errc), hex_category()};
75-
}
76-
77-
/// Hex decoding exception.
78-
struct hex_error : std::system_error
79-
{
80-
using system_error::system_error;
81-
};
8220

8321
/// Encode a byte to a hex string.
8422
inline std::string hex(uint8_t b) noexcept
8523
{
86-
static constexpr auto hex_chars = "0123456789abcdef";
87-
return {hex_chars[b >> 4], hex_chars[b & 0xf]};
24+
static constexpr auto hex_digits = "0123456789abcdef";
25+
return {hex_digits[b >> 4], hex_digits[b & 0xf]};
8826
}
8927

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

12361
template <typename OutputIt>
124-
inline constexpr hex_errc from_hex(std::string_view hex, OutputIt result) noexcept
62+
inline constexpr bool from_hex(std::string_view hex, OutputIt result) noexcept
12563
{
12664
// Omit the optional 0x prefix.
12765
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
13674

13775
const int v = from_hex_digit(h);
13876
if (v < 0)
139-
return hex_errc::invalid_hex_digit;
77+
return false;
14078

14179
if (hi_nibble == empty_mark)
14280
{
@@ -149,14 +87,14 @@ inline constexpr hex_errc from_hex(std::string_view hex, OutputIt result) noexce
14987
}
15088
}
15189

152-
if (hi_nibble != empty_mark)
153-
return hex_errc::incomplete_hex_byte_pair;
154-
return {};
90+
return hi_nibble == empty_mark;
15591
}
15692
} // namespace internal_hex
15793

15894
/// Validates hex encoded string.
159-
inline std::error_code validate_hex(std::string_view hex) noexcept
95+
///
96+
/// @return True if the input is valid hex.
97+
inline bool validate_hex(std::string_view hex) noexcept
16098
{
16199
struct noop_output_iterator
162100
{
@@ -170,14 +108,15 @@ inline std::error_code validate_hex(std::string_view hex) noexcept
170108

171109
/// Decodes hex encoded string to bytes.
172110
///
173-
/// Throws hex_error with the appropriate error code.
174-
inline bytes from_hex(std::string_view hex)
111+
/// In case the input is invalid the returned value is std::nullopt.
112+
/// This can happen if a non-hex digit or odd number of digits is encountered.
113+
/// Whitespace in the input is ignored.
114+
inline std::optional<bytes> from_hex(std::string_view hex)
175115
{
176116
bytes bs;
177117
bs.reserve(hex.size() / 2);
178-
const auto ec = internal_hex::from_hex(hex, std::back_inserter(bs));
179-
if (static_cast<int>(ec) != 0)
180-
throw hex_error{ec};
118+
if (!internal_hex::from_hex(hex, std::back_inserter(bs)))
119+
return {};
181120
return bs;
182121
}
183122
} // namespace evmc

lib/tooling/run.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,13 @@ int run(evmc::VM& vm,
7171
out << (create ? "Creating and executing on " : "Executing on ") << rev << " with " << gas
7272
<< " gas limit\n";
7373

74-
const auto code = from_hex(code_hex);
75-
const auto input = from_hex(input_hex);
74+
auto opt_code = from_hex(code_hex);
75+
auto opt_input = from_hex(input_hex);
76+
if (!opt_code || !opt_input)
77+
throw std::invalid_argument{"invalid hex"};
78+
79+
const auto& code = *opt_code;
80+
const auto& input = *opt_input;
7681

7782
MockedHost host;
7883

test/tools/CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ set_tests_properties(${PROJECT_NAME}/evmc-tool/explicit_empty_input PROPERTIES P
5454
add_evmc_tool_test(
5555
invalid_hex_code
5656
"--vm $<TARGET_FILE:evmc::example-vm> run 0x600"
57-
"code: \\(incomplete hex byte pair\\) OR \\(File does not exist: 0x600\\)"
57+
"code: \\(invalid hex\\) OR \\(File does not exist: 0x600\\)"
5858
)
5959

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

6666
add_evmc_tool_test(
@@ -78,7 +78,7 @@ add_evmc_tool_test(
7878
add_evmc_tool_test(
7979
invalid_code_file
8080
"--vm $<TARGET_FILE:evmc::example-vm> run ${CMAKE_CURRENT_SOURCE_DIR}/invalid_code.evm"
81-
"Error: invalid hex digit"
81+
"Error: invalid hex"
8282
)
8383

8484
add_evmc_tool_test(

test/unittests/example_vm_test.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ struct Output
1717
{
1818
evmc::bytes bytes;
1919

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

2222
friend bool operator==(const evmc::result& result, const Output& expected) noexcept
2323
{
@@ -45,8 +45,8 @@ class example_vm : public testing::Test
4545
const char* code_hex,
4646
const char* input_hex = "")
4747
{
48-
const auto code = evmc::from_hex(code_hex);
49-
const auto input = evmc::from_hex(input_hex);
48+
const auto code = evmc::from_hex(code_hex).value();
49+
const auto input = evmc::from_hex(input_hex).value();
5050

5151
msg.gas = gas;
5252
msg.input_data = input.data();
@@ -150,7 +150,7 @@ TEST_F(example_vm, revert_undefined)
150150
TEST_F(example_vm, call)
151151
{
152152
// pseudo-Yul: call(3, 3, 3, 3, 3, 3, 3) return(0, msize())
153-
const auto expected_output = evmc::from_hex("aabbcc");
153+
const auto expected_output = evmc::from_hex("aabbcc").value();
154154
host.call_result.output_data = expected_output.data();
155155
host.call_result.output_size = expected_output.size();
156156
const auto r = execute_in_example_vm(100, "6003808080808080f1596000f3");

test/unittests/hex_test.cpp

Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -40,42 +40,29 @@ TEST(hex, from_hex)
4040
EXPECT_EQ(from_hex("00bc01000C"), (bytes{0x00, 0xbc, 0x01, 0x00, 0x0c}));
4141
}
4242

43-
static std::error_code catch_hex_error(const char* input)
44-
{
45-
try
46-
{
47-
from_hex(input);
48-
}
49-
catch (const hex_error& e)
50-
{
51-
return e.code();
52-
}
53-
return {};
54-
}
55-
5643
TEST(hex, from_hex_odd_length)
5744
{
58-
EXPECT_EQ(catch_hex_error("0"), hex_errc::incomplete_hex_byte_pair);
59-
EXPECT_EQ(catch_hex_error("1"), hex_errc::incomplete_hex_byte_pair);
60-
EXPECT_EQ(catch_hex_error("123"), hex_errc::incomplete_hex_byte_pair);
45+
EXPECT_EQ(from_hex("0"), std::nullopt);
46+
EXPECT_EQ(from_hex("1"), std::nullopt);
47+
EXPECT_EQ(from_hex("123"), std::nullopt);
6148
}
6249

6350
TEST(hex, from_hex_not_hex_digit)
6451
{
65-
EXPECT_EQ(catch_hex_error("0g"), hex_errc::invalid_hex_digit);
66-
EXPECT_EQ(catch_hex_error("000h"), hex_errc::invalid_hex_digit);
67-
EXPECT_EQ(catch_hex_error("ffffffzz"), hex_errc::invalid_hex_digit);
52+
EXPECT_EQ(from_hex("0g"), std::nullopt);
53+
EXPECT_EQ(from_hex("000h"), std::nullopt);
54+
EXPECT_EQ(from_hex("ffffffzz"), std::nullopt);
6855
}
6956

7057
TEST(hex, from_hex_0x_prefix)
7158
{
7259
EXPECT_EQ(from_hex("0x"), bytes{});
7360
EXPECT_EQ(from_hex("0x00"), bytes{0x00});
7461
EXPECT_EQ(from_hex("0x01020304"), (bytes{0x01, 0x02, 0x03, 0x04}));
75-
EXPECT_EQ(catch_hex_error("0x123"), hex_errc::incomplete_hex_byte_pair);
76-
EXPECT_EQ(catch_hex_error("00x"), hex_errc::invalid_hex_digit);
77-
EXPECT_EQ(catch_hex_error("00x0"), hex_errc::invalid_hex_digit);
78-
EXPECT_EQ(catch_hex_error("0x001y"), hex_errc::invalid_hex_digit);
62+
EXPECT_EQ(from_hex("0x123"), std::nullopt);
63+
EXPECT_EQ(from_hex("00x"), std::nullopt);
64+
EXPECT_EQ(from_hex("00x0"), std::nullopt);
65+
EXPECT_EQ(from_hex("0x001y"), std::nullopt);
7966
}
8067

8168
TEST(hex, from_hex_skip_whitespace)
@@ -87,36 +74,11 @@ TEST(hex, from_hex_skip_whitespace)
8774

8875
TEST(hex, validate_hex)
8976
{
90-
EXPECT_FALSE(validate_hex(""));
91-
EXPECT_FALSE(validate_hex("0x"));
92-
EXPECT_FALSE(validate_hex("01"));
93-
EXPECT_EQ(validate_hex("0"), hex_errc::incomplete_hex_byte_pair);
94-
EXPECT_EQ(validate_hex("WXYZ"), hex_errc::invalid_hex_digit);
95-
}
96-
97-
TEST(hex, hex_error_code)
98-
{
99-
std::error_code ec1 = hex_errc::invalid_hex_digit;
100-
EXPECT_EQ(ec1.value(), 1);
101-
EXPECT_EQ(ec1.message(), "invalid hex digit");
102-
103-
std::error_code ec2 = hex_errc::incomplete_hex_byte_pair;
104-
EXPECT_EQ(ec2.value(), 2);
105-
EXPECT_EQ(ec2.message(), "incomplete hex byte pair");
106-
}
107-
108-
TEST(hex, hex_category_inspection)
109-
{
110-
EXPECT_STREQ(hex_category().name(), "hex");
111-
}
112-
113-
TEST(hex, hex_category_comparison)
114-
{
115-
std::error_code ec1 = hex_errc::invalid_hex_digit;
116-
EXPECT_EQ(ec1.category(), hex_category());
117-
118-
std::error_code ec2 = hex_errc::incomplete_hex_byte_pair;
119-
EXPECT_EQ(ec2.category(), hex_category());
77+
EXPECT_TRUE(validate_hex(""));
78+
EXPECT_TRUE(validate_hex("0x"));
79+
EXPECT_TRUE(validate_hex("01"));
80+
EXPECT_FALSE(validate_hex("0"));
81+
EXPECT_FALSE(validate_hex("WXYZ"));
12082
}
12183

12284
TEST(hex, isspace)

tools/evmc/main.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ namespace
1515
/// @todo The file content is expected to be a hex string but not validated.
1616
std::string load_hex(const std::string& str)
1717
{
18-
const auto error_code = evmc::validate_hex(str);
19-
if (!error_code)
18+
if (evmc::validate_hex(str))
2019
return str;
2120

2221
// Must be a file path.
@@ -30,9 +29,8 @@ struct HexValidator : public CLI::Validator
3029
{
3130
name_ = "HEX";
3231
func_ = [](const std::string& str) -> std::string {
33-
const auto error_code = evmc::validate_hex(str);
34-
if (error_code)
35-
return error_code.message();
32+
if (!evmc::validate_hex(str))
33+
return "invalid hex";
3634
return {};
3735
};
3836
}

0 commit comments

Comments
 (0)