Skip to content

Commit 7ad882c

Browse files
authored
Refactor subcontainer validation (#888)
Refactoring pulled out of #876 Main idea: 1. Instruction validation returns the list of references to subcontainers (instruction, subcontainer_idx) 1.1. (Not directly related, but I moved returning accessed section list into returned `InstructionValidationResult` struct instead of non-const reference argument) 3. The check for `EOFCREATE` with truncated data is moved out of instruction validation into being done once for a referenced container, after it is already validated. 4. This way we don't need to split subcontainer header validation and instructions validation and the loop over containers gets simpler. Then it gets handy for additional container kind validation in #876.
2 parents af17b74 + dc1d74c commit 7ad882c

File tree

1 file changed

+65
-42
lines changed

1 file changed

+65
-42
lines changed

lib/evmone/eof.cpp

+65-42
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <queue>
1818
#include <span>
1919
#include <stack>
20+
#include <unordered_set>
2021
#include <variant>
2122
#include <vector>
2223

@@ -309,17 +310,27 @@ std::variant<EOF1Header, EOFValidationError> validate_header(
309310
};
310311
}
311312

312-
EOFValidationError validate_instructions(evmc_revision rev, const EOF1Header& header,
313-
std::span<const uint16_t> subcontainer_data_offsets,
314-
std::span<const uint16_t> subcontainer_data_sizes, size_t code_idx, bytes_view container,
315-
std::queue<uint16_t>& code_sections_worklist) noexcept
313+
/// Result of validating instructions in a code section.
314+
struct InstructionValidationResult
315+
{
316+
/// Pairs of (container_index, opcode) of all opcodes referencing subcontainers in this section.
317+
std::vector<std::pair<uint8_t, Opcode>> subcontainer_references;
318+
/// Set of accessed code section indices.
319+
// TODO: Vector can be used here in case unordered_set causes performance issues.
320+
std::unordered_set<uint16_t> accessed_code_sections;
321+
};
322+
323+
std::variant<InstructionValidationResult, EOFValidationError> validate_instructions(
324+
evmc_revision rev, const EOF1Header& header, size_t code_idx, bytes_view container) noexcept
316325
{
317326
const bytes_view code{header.get_code(container, code_idx)};
318327
assert(!code.empty()); // guaranteed by EOF headers validation
319328

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

322331
bool is_returning = false;
332+
std::unordered_set<uint16_t> accessed_code_sections;
333+
std::vector<std::pair<uint8_t, Opcode>> subcontainer_references;
323334

324335
for (size_t i = 0; i < code.size(); ++i)
325336
{
@@ -345,7 +356,7 @@ EOFValidationError validate_instructions(evmc_revision rev, const EOF1Header& he
345356
if (header.types[fid].outputs == NON_RETURNING_FUNCTION)
346357
return EOFValidationError::callf_to_non_returning_function;
347358
if (code_idx != fid)
348-
code_sections_worklist.push(fid);
359+
accessed_code_sections.insert(fid);
349360
i += 2;
350361
}
351362
else if (op == OP_RETF)
@@ -362,7 +373,7 @@ EOFValidationError validate_instructions(evmc_revision rev, const EOF1Header& he
362373
if (header.types[fid].outputs != NON_RETURNING_FUNCTION)
363374
is_returning = true;
364375
if (code_idx != fid)
365-
code_sections_worklist.push(fid);
376+
accessed_code_sections.insert(fid);
366377
i += 2;
367378
}
368379
else if (op == OP_DATALOADN)
@@ -378,11 +389,7 @@ EOFValidationError validate_instructions(evmc_revision rev, const EOF1Header& he
378389
if (container_idx >= header.container_sizes.size())
379390
return EOFValidationError::invalid_container_section_index;
380391

381-
const auto subcontainer_end =
382-
subcontainer_data_offsets[container_idx] + subcontainer_data_sizes[container_idx];
383-
if (op == OP_EOFCREATE && subcontainer_end != header.container_sizes[container_idx])
384-
return EOFValidationError::eofcreate_with_truncated_container;
385-
392+
subcontainer_references.emplace_back(container_idx, Opcode{op});
386393
++i;
387394
}
388395
else
@@ -393,7 +400,7 @@ EOFValidationError validate_instructions(evmc_revision rev, const EOF1Header& he
393400
if (is_returning != declared_returning)
394401
return EOFValidationError::invalid_non_returning_flag;
395402

396-
return EOFValidationError::success;
403+
return InstructionValidationResult{subcontainer_references, accessed_code_sections};
397404
}
398405

399406
/// Validates that that we don't rjump inside an instruction's immediate.
@@ -633,42 +640,33 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
633640

634641
EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) noexcept
635642
{
636-
const auto error_or_header = validate_header(rev, main_container);
637-
if (const auto* error = std::get_if<EOFValidationError>(&error_or_header))
638-
return *error;
639-
const auto& main_container_header = std::get<EOF1Header>(error_or_header);
640-
641-
// Queue of (container, header) pairs left to process
642-
std::queue<std::pair<bytes_view, EOF1Header>> container_queue;
643-
container_queue.emplace(main_container, main_container_header);
643+
struct ContainerValidation
644+
{
645+
bytes_view bytes;
646+
bool referenced_by_eofcreate = false;
647+
};
648+
// Queue of containers left to process
649+
std::queue<ContainerValidation> container_queue;
650+
container_queue.push({main_container, false});
644651

645652
while (!container_queue.empty())
646653
{
647-
// Validate subcontainer headers and get their data sections
648-
const auto& [container, header] = container_queue.front();
649-
std::vector<uint16_t> subcontainer_data_offsets;
650-
std::vector<uint16_t> subcontainer_data_sizes;
651-
for (size_t subcont_idx = 0; subcont_idx < header.container_sizes.size(); ++subcont_idx)
652-
{
653-
const bytes_view subcontainer{header.get_container(container, subcont_idx)};
654+
const auto& [container, referenced_by_eofcreate] = container_queue.front();
654655

655-
auto error_subcont_or_header = validate_header(rev, subcontainer);
656-
if (const auto* error_subcont =
657-
std::get_if<EOFValidationError>(&error_subcont_or_header))
658-
return *error_subcont;
656+
// Validate header
657+
auto error_or_header = validate_header(rev, container);
658+
if (const auto* error = std::get_if<EOFValidationError>(&error_or_header))
659+
return *error;
659660

660-
auto& subcont_header = std::get<EOF1Header>(error_subcont_or_header);
661-
662-
subcontainer_data_offsets.push_back(subcont_header.data_offset);
663-
subcontainer_data_sizes.push_back(subcont_header.data_size);
664-
665-
container_queue.emplace(subcontainer, std::move(subcont_header));
666-
}
661+
auto& header = std::get<EOF1Header>(error_or_header);
667662

668663
// Validate code sections
669664
std::vector<bool> visited_code_sections(header.code_sizes.size());
670665
std::queue<uint16_t> code_sections_queue({0});
671666

667+
const auto subcontainer_count = header.container_sizes.size();
668+
std::vector<bool> subcontainer_referenced_by_eofcreate(subcontainer_count, false);
669+
672670
while (!code_sections_queue.empty())
673671
{
674672
const auto code_idx = code_sections_queue.front();
@@ -680,11 +678,25 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n
680678
visited_code_sections[code_idx] = true;
681679

682680
// Validate instructions
683-
const auto error_instr = validate_instructions(rev, header, subcontainer_data_offsets,
684-
subcontainer_data_sizes, code_idx, container, code_sections_queue);
681+
const auto instr_validation_result_or_error =
682+
validate_instructions(rev, header, code_idx, container);
683+
if (const auto* error =
684+
std::get_if<EOFValidationError>(&instr_validation_result_or_error))
685+
return *error;
686+
687+
const auto& [subcontainer_references, accessed_code_sections] =
688+
std::get<InstructionValidationResult>(instr_validation_result_or_error);
689+
690+
// Mark what instructions referenced which subcontainers.
691+
for (const auto& [index, opcode] : subcontainer_references)
692+
{
693+
if (opcode == OP_EOFCREATE)
694+
subcontainer_referenced_by_eofcreate[index] = true;
695+
}
685696

686-
if (error_instr != EOFValidationError::success)
687-
return error_instr;
697+
// TODO(C++23): can use push_range()
698+
for (const auto section_id : accessed_code_sections)
699+
code_sections_queue.push(section_id);
688700

689701
// Validate jump destinations
690702
if (!validate_rjump_destinations(header.get_code(container, code_idx)))
@@ -703,6 +715,17 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n
703715
visited_code_sections.end())
704716
return EOFValidationError::unreachable_code_sections;
705717

718+
if (referenced_by_eofcreate && !header.can_init(container.size()))
719+
return EOFValidationError::eofcreate_with_truncated_container;
720+
721+
// Enqueue subcontainers
722+
for (size_t subcont_idx = 0; subcont_idx < subcontainer_count; ++subcont_idx)
723+
{
724+
const bytes_view subcontainer{header.get_container(container, subcont_idx)};
725+
726+
container_queue.push({subcontainer, subcontainer_referenced_by_eofcreate[subcont_idx]});
727+
}
728+
706729
container_queue.pop();
707730
}
708731

0 commit comments

Comments
 (0)