Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C++ API tweaks (part 2) #449

Merged
merged 2 commits into from
Nov 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 112 additions & 118 deletions include/evmc/evmc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,118 +334,6 @@ class result : private evmc_result
}
};

class Host;

/// @copybrief evmc_vm
///
/// This is a RAII wrapper for evmc_vm and objects of this type
/// automatically destroys the VM instance.
class VM
{
public:
VM() noexcept = default;

/// Converting constructor from evmc_vm.
explicit VM(evmc_vm* vm) noexcept : m_instance{vm} {}

/// Destructor responsible for automatically destroying the VM instance.
~VM() noexcept
{
if (m_instance)
m_instance->destroy(m_instance);
}

VM(const VM&) = delete;
VM& operator=(const VM&) = delete;

/// Move constructor.
VM(VM&& other) noexcept : m_instance{other.m_instance} { other.m_instance = nullptr; }

/// Move assignment operator.
VM& operator=(VM&& other) noexcept
{
this->~VM();
m_instance = other.m_instance;
other.m_instance = nullptr;
return *this;
}

/// The constructor that captures a VM instance and configures the instance
/// with the provided list of options.
inline VM(evmc_vm* vm,
std::initializer_list<std::pair<const char*, const char*>> options) noexcept;

/// Checks if contains a valid pointer to the VM instance.
explicit operator bool() const noexcept { return m_instance != nullptr; }

/// Checks whenever the VM instance is ABI compatible with the current EVMC API.
bool is_abi_compatible() const noexcept { return m_instance->abi_version == EVMC_ABI_VERSION; }

/// @copydoc evmc_vm::name
char const* name() const noexcept { return m_instance->name; }

/// @copydoc evmc_vm::version
char const* version() const noexcept { return m_instance->version; }

/// @copydoc evmc::vm::get_capabilities
evmc_capabilities_flagset get_capabilities() const noexcept
{
return m_instance->get_capabilities(m_instance);
}

/// @copydoc evmc_set_option()
evmc_set_option_result set_option(const char name[], const char value[]) noexcept
{
return evmc_set_option(m_instance, name, value);
}

/// @copydoc evmc_execute()
result execute(const evmc_host_interface& host,
evmc_host_context* ctx,
evmc_revision rev,
const evmc_message& msg,
const uint8_t* code,
size_t code_size) noexcept
{
return result{m_instance->execute(m_instance, &host, ctx, rev, &msg, code, code_size)};
}

/// Convenient variant of the VM::execute() that takes reference to evmc::Host class.
inline result execute(Host& host,
evmc_revision rev,
const evmc_message& msg,
const uint8_t* code,
size_t code_size) noexcept;

/// Executes code without the Host context.
///
/// The same as
/// execute(const evmc_host_interface&, evmc_host_context*, evmc_revision,
/// const evmc_message&, const uint8_t*, size_t),
/// but without providing the Host context and interface.
/// This method is for experimental precompiles support where execution is
/// guaranteed not to require any Host access.
result execute(evmc_revision rev,
const evmc_message& msg,
const uint8_t* code,
size_t code_size) noexcept
{
return result{
m_instance->execute(m_instance, nullptr, nullptr, rev, &msg, code, code_size)};
}

private:
evmc_vm* m_instance = nullptr;
};

inline VM::VM(evmc_vm* vm,
std::initializer_list<std::pair<const char*, const char*>> options) noexcept
: m_instance{vm}
{
for (const auto& option : options)
set_option(option.first, option.second);
}


/// The EVMC Host interface
class HostInterface
Expand Down Expand Up @@ -596,6 +484,7 @@ class HostContext : public HostInterface
}
};


/// Abstract class to be used by Host implementations.
///
/// When implementing EVMC Host, you can directly inherit from the evmc::Host class.
Expand Down Expand Up @@ -628,13 +517,118 @@ class Host : public HostInterface
};


inline result VM::execute(Host& host,
evmc_revision rev,
const evmc_message& msg,
const uint8_t* code,
size_t code_size) noexcept
/// @copybrief evmc_vm
///
/// This is a RAII wrapper for evmc_vm, and object of this type
/// automatically destroys the VM instance.
class VM
{
public:
VM() noexcept = default;

/// Converting constructor from evmc_vm.
explicit VM(evmc_vm* vm) noexcept : m_instance{vm} {}

/// Destructor responsible for automatically destroying the VM instance.
~VM() noexcept
{
if (m_instance)
m_instance->destroy(m_instance);
}

VM(const VM&) = delete;
VM& operator=(const VM&) = delete;

/// Move constructor.
VM(VM&& other) noexcept : m_instance{other.m_instance} { other.m_instance = nullptr; }

/// Move assignment operator.
VM& operator=(VM&& other) noexcept
{
this->~VM();
m_instance = other.m_instance;
other.m_instance = nullptr;
return *this;
}

/// The constructor that captures a VM instance and configures the instance
/// with the provided list of options.
inline VM(evmc_vm* vm,
std::initializer_list<std::pair<const char*, const char*>> options) noexcept;

/// Checks if contains a valid pointer to the VM instance.
explicit operator bool() const noexcept { return m_instance != nullptr; }

/// Checks whenever the VM instance is ABI compatible with the current EVMC API.
bool is_abi_compatible() const noexcept { return m_instance->abi_version == EVMC_ABI_VERSION; }

/// @copydoc evmc_vm::name
char const* name() const noexcept { return m_instance->name; }

/// @copydoc evmc_vm::version
char const* version() const noexcept { return m_instance->version; }

/// @copydoc evmc::vm::get_capabilities
evmc_capabilities_flagset get_capabilities() const noexcept
{
return m_instance->get_capabilities(m_instance);
}

/// @copydoc evmc_set_option()
evmc_set_option_result set_option(const char name[], const char value[]) noexcept
{
return evmc_set_option(m_instance, name, value);
}

/// @copydoc evmc_execute()
result execute(const evmc_host_interface& host,
evmc_host_context* ctx,
evmc_revision rev,
const evmc_message& msg,
const uint8_t* code,
size_t code_size) noexcept
{
return result{m_instance->execute(m_instance, &host, ctx, rev, &msg, code, code_size)};
}

/// Convenient variant of the VM::execute() that takes reference to evmc::Host class.
result execute(Host& host,
evmc_revision rev,
const evmc_message& msg,
const uint8_t* code,
size_t code_size) noexcept
{
return execute(Host::get_interface(), host.to_context(), rev, msg, code, code_size);
}

/// Executes code without the Host context.
///
/// The same as
/// execute(const evmc_host_interface&, evmc_host_context*, evmc_revision,
/// const evmc_message&, const uint8_t*, size_t),
/// but without providing the Host context and interface.
/// This method is for experimental precompiles support where execution is
/// guaranteed not to require any Host access.
result execute(evmc_revision rev,
const evmc_message& msg,
const uint8_t* code,
size_t code_size) noexcept
{
return result{
m_instance->execute(m_instance, nullptr, nullptr, rev, &msg, code, code_size)};
}

private:
evmc_vm* m_instance = nullptr;
};

inline VM::VM(evmc_vm* vm,
std::initializer_list<std::pair<const char*, const char*>> options) noexcept
: m_instance{vm}
{
return execute(Host::get_interface(), host.to_context(), rev, msg, code, code_size);
// This constructor is implemented outside of the class definition to workaround a doxygen bug.
for (const auto& option : options)
set_option(option.first, option.second);
}


Expand Down
71 changes: 71 additions & 0 deletions test/unittests/test_cpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,47 @@
#include <map>
#include <unordered_map>

class NullHost : public evmc::Host
{
public:
bool account_exists(const evmc::address&) noexcept final { return false; }

evmc::bytes32 get_storage(const evmc::address&, const evmc::bytes32&) noexcept final
{
return {};
}

evmc_storage_status set_storage(const evmc::address&,
const evmc::bytes32&,
const evmc::bytes32&) noexcept final
{
return {};
}

evmc::uint256be get_balance(const evmc::address&) noexcept final { return {}; }

size_t get_code_size(const evmc::address&) noexcept final { return 0; }

evmc::bytes32 get_code_hash(const evmc::address&) noexcept final { return {}; }

size_t copy_code(const evmc::address&, size_t, uint8_t*, size_t) noexcept final { return 0; }

void selfdestruct(const evmc::address&, const evmc::address&) noexcept final {}

evmc::result call(const evmc_message&) noexcept final { return evmc::result{evmc_result{}}; }

evmc_tx_context get_tx_context() noexcept final { return {}; }

evmc::bytes32 get_block_hash(int64_t) noexcept final { return {}; }

void emit_log(const evmc::address&,
const uint8_t*,
size_t,
const evmc::bytes32[],
size_t) noexcept final
{}
};

TEST(cpp, address)
{
evmc::address a;
Expand Down Expand Up @@ -285,6 +326,23 @@ TEST(cpp, vm_set_option)
EXPECT_EQ(vm.set_option("1", "2"), EVMC_SET_OPTION_INVALID_NAME);
}

TEST(cpp, vm_set_option_in_constructor)
{
static int num_calls = 0;
const auto set_option_method = [](evmc_vm* /*unused*/, const char* name, const char* value) {
++num_calls;
EXPECT_STREQ(name, "o");
EXPECT_EQ(value, std::to_string(num_calls));
return EVMC_SET_OPTION_INVALID_NAME;
};

evmc_vm raw{EVMC_ABI_VERSION, "", "", nullptr, nullptr, nullptr, set_option_method};
raw.destroy = [](evmc_vm*) {};

const auto vm = evmc::VM{&raw, {{"o", "1"}, {"o", "2"}}};
EXPECT_EQ(num_calls, 2);
}

TEST(cpp, vm_null)
{
evmc::VM vm;
Expand Down Expand Up @@ -365,6 +423,19 @@ TEST(cpp, vm_execute_precompiles)
EXPECT_TRUE(std::equal(input.begin(), input.end(), res.output_data));
}

TEST(cpp, vm_execute_with_null_host)
{
// This tests only if the used VM::execute() overload is at least implemented.
// We know that the example VM will not use the host context in this case.

auto host = NullHost{};

auto vm = evmc::VM{evmc_create_example_vm()};
evmc_message msg{};
auto res = vm.execute(host, EVMC_MAX_REVISION, msg, nullptr, 0);
EXPECT_EQ(res.status_code, EVMC_FAILURE);
}

TEST(cpp, host)
{
// Use example host to execute all methods from the C++ host wrapper.
Expand Down