diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 14f68a8d2..2f6bf39a6 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable( loader_mock.h loader_test.cpp mocked_host_test.cpp + skip_space_iterator_test.cpp tooling_test.cpp hex_test.cpp ) diff --git a/test/unittests/skip_space_iterator_test.cpp b/test/unittests/skip_space_iterator_test.cpp new file mode 100644 index 000000000..dbee4b871 --- /dev/null +++ b/test/unittests/skip_space_iterator_test.cpp @@ -0,0 +1,83 @@ +// EVMC: Ethereum Client-VM Connector API. +// Copyright 2022 The EVMC Authors. +// Licensed under the Apache License, Version 2.0. + +#include +#include + +using evmc::skip_space_iterator; + +namespace +{ +std::string remove_space(std::string_view in) +{ + // Copy input to additional buffer. This helps with out-of-buffer reads detection by sanitizers. + const auto in_buffer = std::make_unique(in.size()); + const auto begin = in_buffer.get(); + const auto end = begin + in.size(); + std::copy(in.begin(), in.end(), begin); + + // Filter the input. + std::string out; + std::copy(skip_space_iterator{begin, end}, skip_space_iterator{end, end}, + std::back_inserter(out)); + return out; +} +} // namespace + +TEST(skip_space_iterator, empty) +{ + EXPECT_EQ(remove_space(""), ""); + EXPECT_EQ(remove_space(" "), ""); + EXPECT_EQ(remove_space(" "), ""); +} + +TEST(skip_space_iterator, filter_middle) +{ + EXPECT_EQ(remove_space("x y"), "xy"); + EXPECT_EQ(remove_space("x y"), "xy"); +} + +TEST(skip_space_iterator, filter_front) +{ + EXPECT_EQ(remove_space(" x"), "x"); + EXPECT_EQ(remove_space(" x"), "x"); +} + +TEST(skip_space_iterator, filter_back) +{ + EXPECT_EQ(remove_space("x "), "x"); + EXPECT_EQ(remove_space("x "), "x"); +} + +TEST(skip_space_iterator, filter_mixed) +{ + EXPECT_EQ(remove_space(" x y z "), "xyz"); + EXPECT_EQ(remove_space(" x y z "), "xyz"); +} + +TEST(skip_space_iterator, 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::isspace(c), (std::isspace(c) != 0)); + switch (c) + { + case ' ': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + EXPECT_TRUE(evmc::isspace(c)); + break; + default: + EXPECT_FALSE(evmc::isspace(c)); + break; + } + } +} diff --git a/tools/evmc/CMakeLists.txt b/tools/evmc/CMakeLists.txt index 9e5c6123d..227ba32db 100644 --- a/tools/evmc/CMakeLists.txt +++ b/tools/evmc/CMakeLists.txt @@ -5,7 +5,7 @@ hunter_add_package(CLI11) find_package(CLI11 REQUIRED) -add_executable(evmc-tool main.cpp) +add_executable(evmc-tool main.cpp skip_space_iterator.hpp) add_executable(evmc::tool ALIAS evmc-tool) set_target_properties(evmc-tool PROPERTIES OUTPUT_NAME evmc) set_source_files_properties(main.cpp PROPERTIES diff --git a/tools/evmc/main.cpp b/tools/evmc/main.cpp index 6c8a4b720..7811973df 100644 --- a/tools/evmc/main.cpp +++ b/tools/evmc/main.cpp @@ -2,6 +2,7 @@ // Copyright 2019-2020 The EVMC Authors. // Licensed under the Apache License, Version 2.0. +#include "skip_space_iterator.hpp" #include #include #include @@ -18,11 +19,13 @@ evmc::bytes load_from_hex(const std::string& str) if (str[0] == '@') // The argument is file path. { std::ifstream file{str.c_str() + 1}; - std::string content{std::istreambuf_iterator{file}, std::istreambuf_iterator{}}; - auto o = evmc::from_hex(content); - if (!o) + const std::istreambuf_iterator file_begin{file}; + const std::istreambuf_iterator file_end; + evmc::bytes out; + if (!evmc::from_hex(evmc::skip_space_iterator{file_begin, file_end}, + evmc::skip_space_iterator{file_end, file_end}, std::back_inserter(out))) throw std::invalid_argument{"invalid hex"}; - return std::move(*o); + return out; } return evmc::from_hex(str).value(); // Should be validated already. diff --git a/tools/evmc/skip_space_iterator.hpp b/tools/evmc/skip_space_iterator.hpp new file mode 100644 index 000000000..d1aa97f3f --- /dev/null +++ b/tools/evmc/skip_space_iterator.hpp @@ -0,0 +1,60 @@ +// EVMC: Ethereum Client-VM Connector API. +// Copyright 2022 The EVMC Authors. +// Licensed under the Apache License, Version 2.0. +#pragma once + +#include + +namespace evmc +{ +/// The constexpr variant of std::isspace(). +inline constexpr bool isspace(char ch) noexcept +{ + // Implementation taken from LLVM's libc. + return ch == ' ' || (static_cast(ch) - '\t') < 5; +} + +/// The input filter iterator which skips whitespace characters from the base input iterator. +template +struct skip_space_iterator +{ + using difference_type = typename std::iterator_traits::difference_type; + using value_type = typename std::iterator_traits::value_type; + using pointer = typename std::iterator_traits::pointer; + using reference = typename std::iterator_traits::reference; + using iterator_category = std::input_iterator_tag; + +private: + BaseIterator base; + BaseIterator base_end; + value_type value; + + constexpr void forward_to_next_value() + { + for (; base != base_end; ++base) + { + value = *base; + if (isspace(value) == 0) + break; + } + } + +public: + constexpr skip_space_iterator(BaseIterator it, BaseIterator end) noexcept + : base{it}, base_end{end} + { + forward_to_next_value(); + } + + constexpr auto operator*() { return value; } + + constexpr void operator++() + { + ++base; + forward_to_next_value(); + } + + constexpr bool operator!=(const skip_space_iterator& o) { return base != o.base; } + constexpr bool operator==(const skip_space_iterator& o) { return base == o.base; } +}; +} // namespace evmc