Skip to content

Commit

Permalink
Implement JUMPF and non-returning functions (#644)
Browse files Browse the repository at this point in the history
  • Loading branch information
gumb0 authored Nov 28, 2023
2 parents b1c9637 + 2bfb485 commit 56641dc
Show file tree
Hide file tree
Showing 16 changed files with 618 additions and 680 deletions.
2 changes: 1 addition & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ jobs:
~/tests/EIPTests/BlockchainTests/bcEIP1153-transientStorage
- download_execution_tests:
repo: ipsilon/tests
rev: eof-update-20230828
rev: eof-nrf-20231128
legacy: false
- run:
name: "State tests (EOF)"
Expand Down
2 changes: 1 addition & 1 deletion lib/evmone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ if(CABLE_COMPILER_GNULIKE)
target_compile_options(
evmone PRIVATE
-fno-exceptions
$<$<CXX_COMPILER_ID:GNU>:-Wstack-usage=2700>
$<$<CXX_COMPILER_ID:GNU>:-Wstack-usage=2800>
)
if(NOT SANITIZE MATCHES undefined)
# RTTI can be disabled except for UBSan which checks vptr integrity.
Expand Down
1 change: 1 addition & 0 deletions lib/evmone/advanced_instructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ constexpr std::array<instruction_exec_fn, 256> instruction_implementations = [](
table[OP_DATALOADN] = op_undefined;
table[OP_DATASIZE] = op_undefined;
table[OP_DATACOPY] = op_undefined;
table[OP_JUMPF] = op_undefined;

table[OP_DUPN] = op_undefined;
table[OP_SWAPN] = op_undefined;
Expand Down
1 change: 1 addition & 0 deletions lib/evmone/baseline_instruction_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ constexpr auto legacy_cost_tables = []() noexcept {
tables[EVMC_PRAGUE][OP_RJUMPV] = instr::undefined;
tables[EVMC_PRAGUE][OP_CALLF] = instr::undefined;
tables[EVMC_PRAGUE][OP_RETF] = instr::undefined;
tables[EVMC_PRAGUE][OP_JUMPF] = instr::undefined;
tables[EVMC_PRAGUE][OP_DATALOAD] = instr::undefined;
tables[EVMC_PRAGUE][OP_DATALOADN] = instr::undefined;
tables[EVMC_PRAGUE][OP_DATASIZE] = instr::undefined;
Expand Down
63 changes: 60 additions & 3 deletions lib/evmone/eof.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ constexpr auto MAX_STACK_HEIGHT = 0x03FF;
constexpr auto OUTPUTS_INPUTS_NUMBER_LIMIT = 0x7F;
constexpr auto REL_OFFSET_SIZE = sizeof(int16_t);
constexpr auto STACK_SIZE_LIMIT = 1024;
constexpr uint8_t NON_RETURNING_FUNCITON = 0x80;

using EOFSectionHeaders = std::array<std::vector<uint16_t>, MAX_SECTION + 1>;

Expand Down Expand Up @@ -195,13 +196,14 @@ std::variant<std::vector<EOFCodeType>, EOFValidationError> validate_types(
container[offset], container[offset + 1], read_uint16_be(&container[offset + 2]));
}

// check 1st section is (0, 0)
if (types[0].inputs != 0 || types[0].outputs != 0)
// check 1st section is (0, 0x80)
if (types[0].inputs != 0 || types[0].outputs != NON_RETURNING_FUNCITON)
return EOFValidationError::invalid_first_section_type;

for (const auto& t : types)
{
if (t.outputs > OUTPUTS_INPUTS_NUMBER_LIMIT || t.inputs > OUTPUTS_INPUTS_NUMBER_LIMIT)
if ((t.outputs > OUTPUTS_INPUTS_NUMBER_LIMIT && t.outputs != NON_RETURNING_FUNCITON) ||
t.inputs > OUTPUTS_INPUTS_NUMBER_LIMIT)
return EOFValidationError::inputs_outputs_num_above_limit;

if (t.max_stack_height > MAX_STACK_HEIGHT)
Expand All @@ -219,6 +221,8 @@ EOFValidationError validate_instructions(

const auto& cost_table = baseline::get_baseline_cost_table(rev, 1);

bool is_returning = false;

for (size_t i = 0; i < code.size(); ++i)
{
const auto op = code[i];
Expand All @@ -240,6 +244,23 @@ EOFValidationError validate_instructions(
const auto fid = read_uint16_be(&code[i + 1]);
if (fid >= header.types.size())
return EOFValidationError::invalid_code_section_index;
if (header.types[fid].outputs == NON_RETURNING_FUNCITON)
return EOFValidationError::callf_to_non_returning_function;
i += 2;
}
else if (op == OP_RETF)
{
is_returning = true;
static_assert(instr::traits[OP_RETF].immediate_size == 0);
}
else if (op == OP_JUMPF)
{
const auto fid = read_uint16_be(&code[i + 1]);
if (fid >= header.types.size())
return EOFValidationError::invalid_code_section_index;
// JUMPF into returning function means current function is returning.
if (header.types[fid].outputs != NON_RETURNING_FUNCITON)
is_returning = true;
i += 2;
}
else if (op == OP_DATALOADN)
Expand All @@ -253,6 +274,10 @@ EOFValidationError validate_instructions(
i += instr::traits[op].immediate_size;
}

const auto declared_returning = (header.types[code_idx].outputs != NON_RETURNING_FUNCITON);
if (is_returning != declared_returning)
return EOFValidationError::invalid_non_returning_flag;

return EOFValidationError::success;
}

Expand Down Expand Up @@ -355,9 +380,35 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
STACK_SIZE_LIMIT)
return EOFValidationError::stack_overflow;

// Instruction validation ensures target function is returning
assert(code_types[fid].outputs != NON_RETURNING_FUNCITON);
stack_height_change =
static_cast<int8_t>(code_types[fid].outputs - stack_height_required);
}
else if (opcode == OP_JUMPF)
{
const auto fid = read_uint16_be(&code[i + 1]);

if (stack_height + code_types[fid].max_stack_height - code_types[fid].inputs >
STACK_SIZE_LIMIT)
return EOFValidationError::stack_overflow;

if (code_types[fid].outputs == NON_RETURNING_FUNCITON)
{
stack_height_required = static_cast<int8_t>(code_types[fid].inputs);
}
else
{
if (code_types[func_index].outputs < code_types[fid].outputs)
return EOFValidationError::jumpf_destination_incompatible_outputs;

stack_height_required =
static_cast<int8_t>(code_types[func_index].outputs + code_types[fid].inputs -
code_types[fid].outputs);
if (stack_heights[i] > stack_height_required)
return EOFValidationError::non_empty_stack_on_terminating_instruction;
}
}

if (stack_height < stack_height_required)
return EOFValidationError::stack_underflow;
Expand Down Expand Up @@ -636,6 +687,12 @@ std::string_view get_error_message(EOFValidationError err) noexcept
return "invalid_code_section_index";
case EOFValidationError::invalid_dataloadn_index:
return "invalid_dataloadn_index";
case EOFValidationError::jumpf_destination_incompatible_outputs:
return "jumpf_destination_incompatible_outputs";
case EOFValidationError::invalid_non_returning_flag:
return "invalid_non_returning_flag";
case EOFValidationError::callf_to_non_returning_function:
return "callf_to_non_returning_function";
case EOFValidationError::impossible:
return "impossible";
}
Expand Down
3 changes: 3 additions & 0 deletions lib/evmone/eof.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ enum class EOFValidationError
stack_overflow,
invalid_code_section_index,
invalid_dataloadn_index,
jumpf_destination_incompatible_outputs,
invalid_non_returning_flag,
callf_to_non_returning_function,

impossible,
};
Expand Down
19 changes: 19 additions & 0 deletions lib/evmone/instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,25 @@ inline code_iterator retf(StackTop /*stack*/, ExecutionState& state, code_iterat
return p;
}

inline code_iterator jumpf(StackTop stack, ExecutionState& state, code_iterator pos) noexcept
{
const auto index = read_uint16_be(&pos[1]);
const auto& header = state.analysis.baseline->eof_header;
const auto stack_size = &stack.top() - state.stack_space.bottom();

const auto callee_required_stack_size =
header.types[index].max_stack_height - header.types[index].inputs;
if (stack_size + callee_required_stack_size > StackSpace::limit)
{
state.status = EVMC_STACK_OVERFLOW;
return nullptr;
}

const auto offset = header.code_offsets[index] - header.code_offsets[0];
const auto code = state.analysis.baseline->executable_code;
return code.data() + offset;
}

template <evmc_status_code StatusCode>
inline TermResult return_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept
{
Expand Down
1 change: 1 addition & 0 deletions lib/evmone/instructions_opcodes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ enum Opcode : uint8_t
OP_RJUMPV = 0xe2,
OP_CALLF = 0xe3,
OP_RETF = 0xe4,
OP_JUMPF = 0xe5,

OP_DUPN = 0xe6,
OP_SWAPN = 0xe7,
Expand Down
2 changes: 2 additions & 0 deletions lib/evmone/instructions_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ constexpr inline GasCostTable gas_costs = []() noexcept {
table[EVMC_PRAGUE][OP_RJUMPV] = 4;
table[EVMC_PRAGUE][OP_CALLF] = 5;
table[EVMC_PRAGUE][OP_RETF] = 3;
table[EVMC_PRAGUE][OP_JUMPF] = 5;
table[EVMC_PRAGUE][OP_DATALOAD] = 4;
table[EVMC_PRAGUE][OP_DATALOADN] = 3;
table[EVMC_PRAGUE][OP_DATASIZE] = 2;
Expand Down Expand Up @@ -403,6 +404,7 @@ constexpr inline std::array<Traits, 256> traits = []() noexcept {
table[OP_STATICCALL] = {"STATICCALL", 0, false, 6, -5, EVMC_BYZANTIUM};
table[OP_CALLF] = {"CALLF", 2, false, 0, 0, EVMC_PRAGUE};
table[OP_RETF] = {"RETF", 0, true, 0, 0, EVMC_PRAGUE};
table[OP_JUMPF] = {"JUMPF", 2, true, 0, 0, EVMC_PRAGUE};
table[OP_REVERT] = {"REVERT", 0, true, 2, -2, EVMC_BYZANTIUM};
table[OP_INVALID] = {"INVALID", 0, true, 0, 0, EVMC_FRONTIER};
table[OP_SELFDESTRUCT] = {"SELFDESTRUCT", 0, true, 1, -1, EVMC_FRONTIER};
Expand Down
2 changes: 1 addition & 1 deletion lib/evmone/instructions_xmacro.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@
ON_OPCODE_IDENTIFIER(OP_RJUMPV, rjumpv) \
ON_OPCODE_IDENTIFIER(OP_CALLF, callf) \
ON_OPCODE_IDENTIFIER(OP_RETF, retf) \
ON_OPCODE_UNDEFINED(0xe5) \
ON_OPCODE_IDENTIFIER(OP_JUMPF, jumpf) \
ON_OPCODE_IDENTIFIER(OP_DUPN, dupn) \
ON_OPCODE_IDENTIFIER(OP_SWAPN, swapn) \
ON_OPCODE_IDENTIFIER(OP_DATALOAD, dataload) \
Expand Down
23 changes: 14 additions & 9 deletions test/unittests/eof_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <evmone/eof.hpp>
#include <gtest/gtest.h>
#include <test/utils/bytecode.hpp>
#include <test/utils/utils.hpp>

using namespace evmone;
Expand Down Expand Up @@ -41,20 +42,24 @@ TEST(eof, read_valid_eof1_header)
for (int i = 0; i < 256; ++i)
section_size_1_256 += "0001";

std::string section_types_256;
for (int i = 0; i < 256; ++i)
section_types_256 += "00800000";

const TestCase test_cases[] = {
{"EF00 01 010004 0200010001 040000 00 00000000 00", 4, 0, {1}},
{"EF00 01 010004 0200010006 040000 00 00000002 600160005500", 4, 0, {6}},
{"EF00 01 010004 0200010001 040001 00 00000000 00 AA", 4, 1, {1}},
{"EF00 01 010004 0200010006 040004 00 00000002 600160005500 AABBCCDD", 4, 4, {6}},
{"EF00 01 01000C 020003000100020003 040000 00 000000000000000000000000 00 5B00 5B5B00", 12,
{"EF00 01 010004 0200010001 040000 00 00800000 00", 4, 0, {1}},
{"EF00 01 010004 0200010006 040000 00 00800002 600160005500", 4, 0, {6}},
{"EF00 01 010004 0200010001 040001 00 00800000 00 AA", 4, 1, {1}},
{"EF00 01 010004 0200010006 040004 00 00800002 600160005500 AABBCCDD", 4, 4, {6}},
{"EF00 01 01000C 020003000100020003 040000 00 008000000080000000800000 00 5B00 5B5B00", 12,
0, {1, 2, 3}},
{"EF00 01 01000C 020003000100020003 040004 00 000000000000000000000000 00 5B00 5B5B00 "
{"EF00 01 01000C 020003000100020003 040004 00 008000000080000000800000 00 5B00 5B5B00 "
"FFFFFFFF",
12, 4, {1, 2, 3}},
{"EF00 01 010004 0200010100 041000 00 00000000" + nops_255 + "00" + std::string(8192, 'F'),
{"EF00 01 010004 0200010100 041000 00 00800000" + nops_255 + "00" + std::string(8192, 'F'),
4, 4096, {256}},
{"EF00 01 010400 020100" + section_size_1_256 + " 041000 00 " +
std::string(4 * 256 * 2, '0') + std::string(512, '0') + std::string(8192, 'F'),
{"EF00 01 010400 020100" + section_size_1_256 + " 041000 00 " + section_types_256 +
std::string(512, '0') + std::string(8192, 'F'),
4 * 256, 4096, std::vector<uint16_t>(256, 1)},
};

Expand Down
Loading

0 comments on commit 56641dc

Please sign in to comment.