Skip to content

Commit

Permalink
WIP eof: Loosen constant stack height requirement
Browse files Browse the repository at this point in the history
  • Loading branch information
gumb0 committed Jul 26, 2023
1 parent f93fe89 commit bd20842
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 26 deletions.
75 changes: 50 additions & 25 deletions lib/evmone/eof.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,13 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(

// Special values used for detecting errors.
static constexpr int32_t LOC_UNVISITED = -1; // Unvisited byte.
static constexpr int32_t LOC_IMMEDIATE = -2; // Immediate byte.

// Stack height in the header is limited to uint16_t,
// but keeping larger size for ease of calculation.
std::vector<int32_t> stack_heights(code.size(), LOC_UNVISITED);
stack_heights[0] = code_types[func_index].inputs;
std::vector<int32_t> stack_heights_min(code.size(), LOC_UNVISITED);
std::vector<int32_t> stack_heights_max(code.size(), LOC_UNVISITED);
stack_heights_min[0] = code_types[func_index].inputs;
stack_heights_max[0] = code_types[func_index].inputs;

for (size_t i = 0; i < code.size();)
{
Expand All @@ -334,35 +335,53 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
static_cast<int8_t>(code_types[fid].outputs - stack_height_required);
}

auto stack_height = stack_heights[i];
if (stack_height == LOC_UNVISITED)
auto stack_height_min = stack_heights_min[i];
if (stack_height_min == LOC_UNVISITED)
return EOFValidationError::unreachable_instructions;
auto stack_height_max = stack_heights_max[i];
assert(stack_height_max != LOC_UNVISITED);

if (stack_height < stack_height_required)
if (stack_height_min < stack_height_required)
return EOFValidationError::stack_underflow;

stack_height += stack_height_change;
stack_height_min = stack_height_min + stack_height_change;
stack_height_max = stack_height_max + stack_height_change;

// Determine size of immediate, including the special case of RJUMPV.
const size_t imm_size = (opcode == OP_RJUMPV) ?
(1 + /*count*/ size_t{code[i + 1]} * REL_OFFSET_SIZE) :
instr::traits[opcode].immediate_size;

// Mark immediate locations.
std::fill_n(&stack_heights[i + 1], imm_size, LOC_IMMEDIATE);

// Validates the successor instruction and updates its stack height.
const auto validate_successor = [&stack_heights](size_t successor_offset,
int32_t expected_stack_height) {
auto& successor_stack_height = stack_heights[successor_offset];
if (successor_stack_height == LOC_UNVISITED)
{
successor_stack_height = expected_stack_height;
const auto validate_successor =
[&stack_heights_min, &stack_heights_max](size_t current_offset, size_t successor_offset,
int32_t expected_stack_height_min, int32_t expected_stack_height_max) {
auto& successor_stack_height_min = stack_heights_min[successor_offset];
auto& successor_stack_height_max = stack_heights_max[successor_offset];
if (successor_offset <= current_offset) // backwards jump
{
assert(successor_stack_height_min != LOC_UNVISITED);
assert(successor_stack_height_max != LOC_UNVISITED);
// TODO can be relaxed to:
// return expected_stack_height_min >= successor_stack_height_min &&
// expected_stack_height_max <= successor_stack_height_max;
return expected_stack_height_min == successor_stack_height_min &&
expected_stack_height_max == successor_stack_height_max;
}
else if (successor_stack_height_min == LOC_UNVISITED) // forwards jump, new target
{
successor_stack_height_min = expected_stack_height_min;
successor_stack_height_max = expected_stack_height_max;
}
else // forwards jump, target known
{
successor_stack_height_min =
std::min(expected_stack_height_min, successor_stack_height_min);
successor_stack_height_max =
std::max(expected_stack_height_max, successor_stack_height_max);
}
return true;
}
else
return successor_stack_height == expected_stack_height;
};
};

const auto next = i + imm_size + 1; // Offset of the next instruction (may be invalid).

Expand All @@ -371,7 +390,7 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
{
if (next >= code.size())
return EOFValidationError::no_terminating_instruction;
if (!validate_successor(next, stack_height))
if (!validate_successor(i, next, stack_height_min, stack_height_max))
return EOFValidationError::stack_height_mismatch;
}

Expand All @@ -380,7 +399,8 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
{
const auto target_rel_offset = read_int16_be(&code[i + 1]);
const auto target = static_cast<int32_t>(i) + target_rel_offset + 3;
if (!validate_successor(static_cast<size_t>(target), stack_height))
if (!validate_successor(
i, static_cast<size_t>(target), stack_height_min, stack_height_max))
return EOFValidationError::stack_height_mismatch;
}
else if (opcode == OP_RJUMPV)
Expand All @@ -392,17 +412,22 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
{
const auto target_rel_offset = read_int16_be(&code[i + k * REL_OFFSET_SIZE + 2]);
const auto target = static_cast<int32_t>(next) + target_rel_offset;
if (!validate_successor(static_cast<size_t>(target), stack_height))
if (!validate_successor(
i, static_cast<size_t>(target), stack_height_min, stack_height_max))
return EOFValidationError::stack_height_mismatch;
}
}
else if (opcode == OP_RETF && stack_height != code_types[func_index].outputs)
// TODO either RETF has to clean the stack or we require min == max, i.e. RETF only in
// constant stack segment
else if (opcode == OP_RETF && (stack_height_min != code_types[func_index].outputs ||
stack_height_max != code_types[func_index].outputs))
return EOFValidationError::non_empty_stack_on_terminating_instruction;

i = next;
}

const auto max_stack_height = *std::max_element(stack_heights.begin(), stack_heights.end());
const auto max_stack_height =
*std::max_element(stack_heights_max.begin(), stack_heights_max.end());
return max_stack_height;
}

Expand Down
3 changes: 2 additions & 1 deletion test/unittests/eof_validation_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,8 @@ TEST(eof_validation, max_stack_heigh)
{
auto code = eof1_bytecode(rjumpi(2, 0) + 1 + OP_RETF, 1);

EXPECT_EQ(validate_eof(code), EOFValidationError::stack_height_mismatch);
EXPECT_EQ(
validate_eof(code), EOFValidationError::non_empty_stack_on_terminating_instruction);
}

{
Expand Down

0 comments on commit bd20842

Please sign in to comment.