diff --git a/include/evmc/evmc.hpp b/include/evmc/evmc.hpp index 6df50197a..a55ee35c5 100644 --- a/include/evmc/evmc.hpp +++ b/include/evmc/evmc.hpp @@ -9,6 +9,7 @@ #include #include +#include #include /// EVMC C++ API - wrappers and bindings for C++ @@ -337,6 +338,20 @@ constexpr bytes32 operator""_bytes32() noexcept using namespace literals; + +/// @copydoc evmc_status_code_to_string +inline const char* to_string(evmc_status_code status_code) noexcept +{ + return evmc_status_code_to_string(status_code); +} + +/// @copydoc evmc_revision_to_string +inline const char* to_string(evmc_revision rev) noexcept +{ + return evmc_revision_to_string(rev); +} + + /// Alias for evmc_make_result(). constexpr auto make_result = evmc_make_result; @@ -852,6 +867,24 @@ inline const evmc_host_interface& Host::get_interface() noexcept } // namespace evmc +/// "Stream out" operator implementation for ::evmc_status_code. +/// +/// @note This is defined in global namespace to match ::evmc_status_code definition and allow +/// convenient operator overloading usage. +inline std::ostream& operator<<(std::ostream& os, evmc_status_code status_code) +{ + return os << evmc::to_string(status_code); +} + +/// "Stream out" operator implementation for ::evmc_revision. +/// +/// @note This is defined in global namespace to match ::evmc_revision definition and allow +/// convenient operator overloading usage. +inline std::ostream& operator<<(std::ostream& os, evmc_revision rev) +{ + return os << evmc::to_string(rev); +} + namespace std { /// Hash operator template specialization for evmc::address. Needed for unordered containers. diff --git a/include/evmc/helpers.h b/include/evmc/helpers.h index 36febfd72..5ada94a77 100644 --- a/include/evmc/helpers.h +++ b/include/evmc/helpers.h @@ -209,4 +209,84 @@ static inline const union evmc_result_optional_storage* evmc_get_const_optional_ /** @} */ +/** Returns text representation of the ::evmc_status_code. */ +static inline const char* evmc_status_code_to_string(enum evmc_status_code status_code) +{ + switch (status_code) + { + case EVMC_SUCCESS: + return "success"; + case EVMC_FAILURE: + return "failure"; + case EVMC_REVERT: + return "revert"; + case EVMC_OUT_OF_GAS: + return "out of gas"; + case EVMC_INVALID_INSTRUCTION: + return "invalid instruction"; + case EVMC_UNDEFINED_INSTRUCTION: + return "undefined instruction"; + case EVMC_STACK_OVERFLOW: + return "stack overflow"; + case EVMC_STACK_UNDERFLOW: + return "stack underflow"; + case EVMC_BAD_JUMP_DESTINATION: + return "bad jump destination"; + case EVMC_INVALID_MEMORY_ACCESS: + return "invalid memory access"; + case EVMC_CALL_DEPTH_EXCEEDED: + return "call depth exceeded"; + case EVMC_STATIC_MODE_VIOLATION: + return "static mode violation"; + case EVMC_PRECOMPILE_FAILURE: + return "precompile failure"; + case EVMC_CONTRACT_VALIDATION_FAILURE: + return "contract validation failure"; + case EVMC_ARGUMENT_OUT_OF_RANGE: + return "argument out of range"; + case EVMC_WASM_UNREACHABLE_INSTRUCTION: + return "wasm unreachable instruction"; + case EVMC_WASM_TRAP: + return "wasm trap"; + case EVMC_INSUFFICIENT_BALANCE: + return "insufficient balance"; + case EVMC_INTERNAL_ERROR: + return "internal error"; + case EVMC_REJECTED: + return "rejected"; + case EVMC_OUT_OF_MEMORY: + return "out of memory"; + } + return ""; +} + +/** Returns the name of the ::evmc_revision. */ +static inline const char* evmc_revision_to_string(enum evmc_revision rev) +{ + switch (rev) + { + case EVMC_FRONTIER: + return "Frontier"; + case EVMC_HOMESTEAD: + return "Homestead"; + case EVMC_TANGERINE_WHISTLE: + return "Tangerine Whistle"; + case EVMC_SPURIOUS_DRAGON: + return "Spurious Dragon"; + case EVMC_BYZANTIUM: + return "Byzantium"; + case EVMC_CONSTANTINOPLE: + return "Constantinople"; + case EVMC_PETERSBURG: + return "Petersburg"; + case EVMC_ISTANBUL: + return "Istanbul"; + case EVMC_BERLIN: + return "Berlin"; + case EVMC_LONDON: + return "London"; + } + return ""; +} + /** @} */ diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index a97a29326..f62013835 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -36,7 +36,6 @@ target_link_libraries( evmc::instructions evmc::evmc_cpp evmc::tool-commands - evmc::tool-utils evmc::hex GTest::gtest_main ) diff --git a/test/unittests/cpp_test.cpp b/test/unittests/cpp_test.cpp index 94b1d398c..0de09218e 100644 --- a/test/unittests/cpp_test.cpp +++ b/test/unittests/cpp_test.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -775,3 +776,146 @@ TEST(cpp, result_create) EXPECT_TRUE(std::memcmp(c.output_data, r.output_data, c.output_size) == 0); c.release(&c); } + +TEST(cpp, status_code_to_string) +{ + struct TestCase + { + evmc_status_code status_code; + std::string_view str; + }; + +#define TEST_CASE(STATUS_CODE) \ + TestCase { STATUS_CODE, #STATUS_CODE } + constexpr TestCase test_cases[]{ + TEST_CASE(EVMC_SUCCESS), + TEST_CASE(EVMC_FAILURE), + TEST_CASE(EVMC_REVERT), + TEST_CASE(EVMC_OUT_OF_GAS), + TEST_CASE(EVMC_INVALID_INSTRUCTION), + TEST_CASE(EVMC_UNDEFINED_INSTRUCTION), + TEST_CASE(EVMC_STACK_OVERFLOW), + TEST_CASE(EVMC_STACK_UNDERFLOW), + TEST_CASE(EVMC_BAD_JUMP_DESTINATION), + TEST_CASE(EVMC_INVALID_MEMORY_ACCESS), + TEST_CASE(EVMC_CALL_DEPTH_EXCEEDED), + TEST_CASE(EVMC_STATIC_MODE_VIOLATION), + TEST_CASE(EVMC_PRECOMPILE_FAILURE), + TEST_CASE(EVMC_CONTRACT_VALIDATION_FAILURE), + TEST_CASE(EVMC_ARGUMENT_OUT_OF_RANGE), + TEST_CASE(EVMC_WASM_UNREACHABLE_INSTRUCTION), + TEST_CASE(EVMC_WASM_TRAP), + TEST_CASE(EVMC_INSUFFICIENT_BALANCE), + TEST_CASE(EVMC_INTERNAL_ERROR), + TEST_CASE(EVMC_REJECTED), + TEST_CASE(EVMC_OUT_OF_MEMORY), + }; +#undef TEST_CASE + + std::ostringstream os; + for (const auto& t : test_cases) + { + std::string expected; + std::transform(std::cbegin(t.str) + std::strlen("EVMC_"), std::cend(t.str), + std::back_inserter(expected), [](char c) -> char { + return (c == '_') ? ' ' : static_cast(std::tolower(c)); + }); + EXPECT_EQ(evmc::to_string(t.status_code), expected); + os << t.status_code; + EXPECT_EQ(os.str(), expected); + os.str({}); + } +} + +TEST(cpp, revision_to_string) +{ + struct TestCase + { + evmc_revision rev; + std::string_view str; + }; + +#define TEST_CASE(STATUS_CODE) \ + TestCase { STATUS_CODE, #STATUS_CODE } + constexpr TestCase test_cases[]{ + TEST_CASE(EVMC_FRONTIER), + TEST_CASE(EVMC_HOMESTEAD), + TEST_CASE(EVMC_TANGERINE_WHISTLE), + TEST_CASE(EVMC_SPURIOUS_DRAGON), + TEST_CASE(EVMC_BYZANTIUM), + TEST_CASE(EVMC_CONSTANTINOPLE), + TEST_CASE(EVMC_PETERSBURG), + TEST_CASE(EVMC_ISTANBUL), + TEST_CASE(EVMC_BERLIN), + TEST_CASE(EVMC_LONDON), + }; +#undef TEST_CASE + + std::ostringstream os; + ASSERT_EQ(std::size(test_cases), size_t{EVMC_MAX_REVISION + 1}); + for (size_t i = 0; i < std::size(test_cases); ++i) + { + const auto& t = test_cases[i]; + EXPECT_EQ(t.rev, static_cast(i)); + std::string expected; + std::transform(std::cbegin(t.str) + std::strlen("EVMC_"), std::cend(t.str), + std::back_inserter(expected), [skip = true](char c) mutable -> char { + if (skip) + { + skip = false; + return c; + } + else if (c == '_') + { + skip = true; + return ' '; + } + else + return static_cast(std::tolower(c)); + }); + EXPECT_EQ(evmc::to_string(t.rev), expected); + os << t.rev; + EXPECT_EQ(os.str(), expected); + os.str({}); + } +} + + +#ifdef __GNUC__ +extern "C" [[gnu::weak]] void __ubsan_handle_builtin_unreachable(void*); +#endif + +static bool has_ubsan() noexcept +{ +#ifdef __GNUC__ + return (__ubsan_handle_builtin_unreachable != nullptr); +#else + return false; +#endif +} + +TEST(cpp, status_code_to_string_invalid) +{ + if (!has_ubsan()) + { + std::ostringstream os; + int value = 99; + const auto invalid = static_cast(value); + EXPECT_STREQ(evmc::to_string(invalid), ""); + os << invalid; + EXPECT_EQ(os.str(), ""); + } +} + +TEST(cpp, revision_to_string_invalid) +{ + if (!has_ubsan()) + { + std::ostringstream os; + int value = 99; + const auto invalid = static_cast(value); + EXPECT_STREQ(evmc::to_string(invalid), ""); + os << invalid; + EXPECT_EQ(os.str(), ""); + } +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 74110e909..735e54306 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,5 +4,4 @@ add_subdirectory(commands) add_subdirectory(evmc) -add_subdirectory(utils) add_subdirectory(vmtester) diff --git a/tools/commands/CMakeLists.txt b/tools/commands/CMakeLists.txt index 45723ffce..15fdce49c 100644 --- a/tools/commands/CMakeLists.txt +++ b/tools/commands/CMakeLists.txt @@ -6,4 +6,4 @@ add_library(tool-commands STATIC commands.hpp run.cpp) add_library(evmc::tool-commands ALIAS tool-commands) target_compile_features(tool-commands PUBLIC cxx_std_17) target_include_directories(tool-commands PUBLIC ${PROJECT_SOURCE_DIR}) -target_link_libraries(tool-commands PRIVATE evmc::tool-utils evmc::mocked_host evmc::hex) +target_link_libraries(tool-commands PRIVATE evmc::evmc_cpp evmc::mocked_host evmc::hex) diff --git a/tools/commands/run.cpp b/tools/commands/run.cpp index 61a56b89e..c638bc3e6 100644 --- a/tools/commands/run.cpp +++ b/tools/commands/run.cpp @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. #include "tools/commands/commands.hpp" -#include "tools/utils/utils.hpp" +#include #include #include #include diff --git a/tools/utils/CMakeLists.txt b/tools/utils/CMakeLists.txt deleted file mode 100644 index ef53e8fda..000000000 --- a/tools/utils/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# EVMC: Ethereum Client-VM Connector API. -# Copyright 2020 The EVMC Authors. -# Licensed under the Apache License, Version 2.0. - -add_library(tool-utils STATIC utils.cpp utils.hpp) -add_library(evmc::tool-utils ALIAS tool-utils) -target_compile_features(tool-utils PUBLIC cxx_std_17) -target_include_directories(tool-utils PUBLIC ${PROJECT_SOURCE_DIR}) -target_link_libraries(tool-utils PUBLIC evmc::evmc) diff --git a/tools/utils/utils.cpp b/tools/utils/utils.cpp deleted file mode 100644 index 392339b59..000000000 --- a/tools/utils/utils.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// EVMC: Ethereum Client-VM Connector API. -// Copyright 2018-2020 The EVMC Authors. -// Licensed under the Apache License, Version 2.0. - -#include -#include -#include - -namespace evmc -{ -std::ostream& operator<<(std::ostream& os, evmc_status_code status_code) -{ - const char* s = nullptr; - switch (status_code) - { - case EVMC_SUCCESS: - s = "success"; - break; - case EVMC_FAILURE: - s = "failure"; - break; - case EVMC_REVERT: - s = "revert"; - break; - case EVMC_OUT_OF_GAS: - s = "out of gas"; - break; - case EVMC_INVALID_INSTRUCTION: - s = "invalid instruction"; - break; - case EVMC_UNDEFINED_INSTRUCTION: - s = "undefined instruction"; - break; - case EVMC_STACK_OVERFLOW: - s = "stack overflow"; - break; - case EVMC_STACK_UNDERFLOW: - s = "stack underflow"; - break; - case EVMC_BAD_JUMP_DESTINATION: - s = "bad jump destination"; - break; - case EVMC_INVALID_MEMORY_ACCESS: - s = "invalid memory access"; - break; - case EVMC_CALL_DEPTH_EXCEEDED: - s = "call depth exceeded"; - break; - case EVMC_STATIC_MODE_VIOLATION: - s = "static mode violation"; - break; - case EVMC_PRECOMPILE_FAILURE: - s = "precompile failure"; - break; - case EVMC_CONTRACT_VALIDATION_FAILURE: - s = "contract validation failure"; - break; - case EVMC_ARGUMENT_OUT_OF_RANGE: - s = "argument out of range"; - break; - case EVMC_WASM_UNREACHABLE_INSTRUCTION: - s = "wasm unreachable instruction"; - break; - case EVMC_WASM_TRAP: - s = "wasm trap"; - break; - case EVMC_INSUFFICIENT_BALANCE: - s = "insufficient balance"; - break; - case EVMC_INTERNAL_ERROR: - s = "internal error"; - break; - case EVMC_REJECTED: - s = "rejected"; - break; - case EVMC_OUT_OF_MEMORY: - s = "out of memory"; - break; - default: - throw std::invalid_argument{"invalid EVMC status code: " + std::to_string(status_code)}; - } - return os << s; -} - -std::ostream& operator<<(std::ostream& os, evmc_revision revision) -{ - const char* s = nullptr; - switch (revision) - { - case EVMC_FRONTIER: - s = "Frontier"; - break; - case EVMC_HOMESTEAD: - s = "Homestead"; - break; - case EVMC_TANGERINE_WHISTLE: - s = "Tangerine Whistle"; - break; - case EVMC_SPURIOUS_DRAGON: - s = "Spurious Dragon"; - break; - case EVMC_BYZANTIUM: - s = "Byzantium"; - break; - case EVMC_CONSTANTINOPLE: - s = "Constantinople"; - break; - case EVMC_PETERSBURG: - s = "Petersburg"; - break; - case EVMC_ISTANBUL: - s = "Istanbul"; - break; - case EVMC_BERLIN: - s = "Berlin"; - break; - case EVMC_LONDON: - s = "London"; - break; - default: - throw std::invalid_argument{"invalid EVM revision: " + std::to_string(revision)}; - } - return os << s; -} - -} // namespace evmc diff --git a/tools/utils/utils.hpp b/tools/utils/utils.hpp deleted file mode 100644 index 774dc792d..000000000 --- a/tools/utils/utils.hpp +++ /dev/null @@ -1,20 +0,0 @@ -// EVMC: Ethereum Client-VM Connector API. -// Copyright 2018-2020 The EVMC Authors. -// Licensed under the Apache License, Version 2.0. -#pragma once - -#include -#include -#include -#include -#include - -namespace evmc -{ -/// Output stream operator for evmc_status_code. -std::ostream& operator<<(std::ostream& os, evmc_status_code status_code); - -/// Output stream operator for EVM revision. -std::ostream& operator<<(std::ostream& os, evmc_revision revision); - -} // namespace evmc