/* This file is part of TON Blockchain Library. TON Blockchain Library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. TON Blockchain Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . Copyright 2017-2020 Telegram Systems LLP */ #include "vm/stack.hpp" #include "vm/continuation.h" #include "vm/box.hpp" #include "vm/atom.h" #include "vm/vmstate.h" #include "vm/boc.h" #include "td/utils/misc.h" namespace td { template class td::Cnt; template class td::Ref>; template class td::Cnt>; template class td::Ref>>; } // namespace td namespace vm { // from_object_t from_object{}; const char* exception_messages[(int)(Excno::total)] = { "normal termination", "alternative termination", "stack underflow", "stack overflow", "integer overflow", "integer out of range", "invalid opcode", "type check error", "cell overflow", "cell underflow", "dictionary error", "unknown error", "fatal error"}; const char* get_exception_msg(Excno exc_no) { if (exc_no >= Excno::none && exc_no < Excno::total) { return exception_messages[static_cast(exc_no)]; } else { return "unknown vm exception"; } } bool StackEntry::is_list(const StackEntry* se) { Ref tuple; while (!se->empty()) { tuple = se->as_tuple_range(2, 2); if (tuple.is_null()) { return false; } se = &tuple->at(1); } return true; } static const char HEX_digits[] = "0123456789ABCDEF"; std::string str_to_hex(std::string data, std::string prefix) { prefix.reserve(prefix.size() + data.size() * 2); for (char c : data) { prefix += HEX_digits[(c >> 4) & 15]; prefix += HEX_digits[c & 15]; } return prefix; } std::string StackEntry::to_string() const { std::ostringstream os; dump(os); return std::move(os).str(); } std::string StackEntry::to_lisp_string() const { std::ostringstream os; print_list(os); return std::move(os).str(); } void StackEntry::dump(std::ostream& os, bool verbose) const { switch (tp) { case t_null: os << "(null)"; break; case t_int: os << dec_string(as_int()); break; case t_cell: if (ref.not_null()) { if (verbose) { std::string serialized = "???"; auto boc = vm::std_boc_serialize(as_cell()); if (boc.is_ok()) { serialized = td::buffer_to_hex(boc.move_as_ok().as_slice()); } os << "C{" << serialized << "}"; } else { os << "C{" << *as_cell() << "}"; } } else { os << "C{null}"; } break; case t_builder: if (ref.not_null()) { os << "BC{" << *as_builder() << "}"; } else { os << "BC{null}"; } break; case t_slice: { if (ref.not_null()) { os << "CS{"; static_cast>(ref)->dump(os, 1, false); os << '}'; } else { os << "CS{null}"; } break; } case t_string: os << "\"" << as_string() << "\""; break; case t_bytes: os << "BYTES:" << str_to_hex(as_bytes()); break; case t_box: { os << "Box{" << (const void*)&*ref << "}"; break; } case t_atom: os << as_atom(); break; case t_tuple: { const auto& tuple = *static_cast>(ref); auto n = tuple.size(); if (!n) { os << "[]"; } else if (n == 1) { os << "[ "; tuple[0].dump(os); os << " ]"; } else { os << "[ "; for (const auto& entry : tuple) { entry.dump(os); os << ' '; } os << ']'; } break; } case t_object: { os << "Object{" << (const void*)&*ref << "}"; break; } case t_vmcont: { if (ref.not_null()) { if (verbose) { os << "Cont{" << *as_cont() << "}"; } else { os << "Cont{" << as_cont()->type() << "}"; } } else { os << "Cont{null}"; } break; } default: os << "???"; } } void StackEntry::print_list(std::ostream& os, bool verbose) const { switch (tp) { case t_null: os << "()"; break; case t_tuple: { const auto& tuple = *static_cast>(ref); if (is_list()) { os << '('; tuple[0].print_list(os, verbose); print_list_tail(os, &tuple[1]); break; } auto n = tuple.size(); if (!n) { os << "[]"; } else if (n == 1) { os << "["; tuple[0].print_list(os, verbose); os << "]"; } else { os << "["; unsigned c = 0; for (const auto& entry : tuple) { if (c++) { os << " "; } entry.print_list(os, verbose); } os << ']'; } break; } default: dump(os, verbose); } } void StackEntry::print_list_tail(std::ostream& os, const StackEntry* se) { Ref tuple; while (!se->empty()) { tuple = se->as_tuple_range(2, 2); if (tuple.is_null()) { os << " . "; se->print_list(os); break; } os << ' '; tuple->at(0).print_list(os); se = &tuple->at(1); } os << ')'; } StackEntry StackEntry::make_list(std::vector&& elems) { StackEntry tail; std::size_t n = elems.size(); while (n > 0) { --n; tail = StackEntry{vm::make_tuple_ref(std::move(elems[n]), tail)}; } return tail; } StackEntry StackEntry::make_list(const std::vector& elems) { StackEntry tail; std::size_t n = elems.size(); while (n > 0) { --n; tail = StackEntry{vm::make_tuple_ref(elems[n], tail)}; } return tail; } StackEntry::StackEntry(Ref stack_ref) : ref(std::move(stack_ref)), tp(t_stack) { } StackEntry::StackEntry(Ref cont_ref) : ref(std::move(cont_ref)), tp(t_vmcont) { } Ref StackEntry::as_cont() const& { return as(); } Ref StackEntry::as_cont() && { return move_as(); } StackEntry::StackEntry(Ref box_ref) : ref(std::move(box_ref)), tp(t_box) { } Ref StackEntry::as_box() const& { return as(); } Ref StackEntry::as_box() && { return move_as(); } StackEntry::StackEntry(Ref tuple_ref) : ref(std::move(tuple_ref)), tp(t_tuple) { } StackEntry::StackEntry(const std::vector& tuple_components) : ref(Ref{true, tuple_components}), tp(t_tuple) { } StackEntry::StackEntry(std::vector&& tuple_components) : ref(Ref{true, std::move(tuple_components)}), tp(t_tuple) { } Ref StackEntry::as_tuple() const& { return as(); } Ref StackEntry::as_tuple() && { return move_as(); } Ref StackEntry::as_tuple_range(unsigned max_len, unsigned min_len) const& { auto t = as(); if (t.not_null() && t->size() <= max_len && t->size() >= min_len) { return t; } else { return {}; } } Ref StackEntry::as_tuple_range(unsigned max_len, unsigned min_len) && { auto t = move_as(); if (t.not_null() && t->size() <= max_len && t->size() >= min_len) { return t; } else { return {}; } } StackEntry::StackEntry(Ref atom_ref) : ref(std::move(atom_ref)), tp(t_atom) { } Ref StackEntry::as_atom() const& { return as(); } Ref StackEntry::as_atom() && { return move_as(); } bool StackEntry::for_each_scalar(const std::function& func) const { auto t = as(); if (t.not_null()) { for (const auto& entry : *t) { if (!entry.for_each_scalar(func)) { return false; } } return true; } else { return func(*this); } } void StackEntry::for_each_scalar(const std::function& func) const { auto t = as(); if (t.not_null()) { for (const auto& entry : *t) { entry.for_each_scalar(func); } } else { func(*this); } } const StackEntry& tuple_index(const Ref& tup, unsigned idx) { if (idx >= tup->size()) { throw VmError{Excno::range_chk, "tuple index out of range"}; } return (*tup)[idx]; } StackEntry tuple_extend_index(const Ref& tup, unsigned idx) { if (tup.is_null() || idx >= tup->size()) { return {}; } else { return tup->at(idx); } } unsigned tuple_extend_set_index(Ref& tup, unsigned idx, StackEntry&& value, bool force) { if (tup.is_null()) { if (value.empty() && !force) { return 0; } tup = Ref{true, idx + 1}; tup.unique_write().at(idx) = std::move(value); return idx + 1; } if (tup->size() <= idx) { if (value.empty() && !force) { return 0; } auto& tuple = tup.write(); tuple.resize(idx + 1); tuple.at(idx) = std::move(value); return idx + 1; } else { tup.write().at(idx) = std::move(value); return (unsigned)tup->size(); } } Stack::Stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top) { push_from_stack(old_stack, copy_elem, skip_top); } Stack::Stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top) { push_from_stack(old_stack, copy_elem, skip_top); } void Stack::push_from_stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top) { unsigned n = old_stack.depth(); if (skip_top > n || copy_elem > n - skip_top) { throw VmError{Excno::stk_und, "cannot construct stack from another one: not enough elements"}; } stack.reserve(stack.size() + copy_elem); auto it = old_stack.stack.cend() - skip_top; std::copy(it - copy_elem, it, std::back_inserter(stack)); } void Stack::push_from_stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top) { unsigned n = old_stack.depth(); if (skip_top > n || copy_elem > n - skip_top) { throw VmError{Excno::stk_und, "cannot construct stack from another one: not enough elements"}; } stack.reserve(stack.size() + copy_elem); auto it = old_stack.stack.cend() - skip_top; std::move(it - copy_elem, it, std::back_inserter(stack)); } void Stack::move_from_stack(Stack& old_stack, unsigned copy_elem) { unsigned n = old_stack.depth(); if (copy_elem > n) { throw VmError{Excno::stk_und, "cannot construct stack from another one: not enough elements"}; } LOG(DEBUG) << "moving " << copy_elem << " top elements to another stack\n"; stack.reserve(stack.size() + copy_elem); auto it = old_stack.stack.cend(); std::move(it - copy_elem, it, std::back_inserter(stack)); old_stack.pop_many(copy_elem); } void Stack::pop_null() { check_underflow(1); if (!pop().empty()) { throw VmError{Excno::type_chk, "not an null"}; } } td::RefInt256 Stack::pop_int() { check_underflow(1); td::RefInt256 res = pop().as_int(); if (res.is_null()) { throw VmError{Excno::type_chk, "not an integer"}; } return res; } td::RefInt256 Stack::pop_int_finite() { auto res = pop_int(); if (!res->is_valid()) { throw VmError{Excno::int_ov}; } return res; } bool Stack::pop_bool() { return sgn(pop_int_finite()) != 0; } long long Stack::pop_long() { return pop_int()->to_long(); } long long Stack::pop_long_range(long long max, long long min) { auto res = pop_long(); if (res > max || res < min) { throw VmError{Excno::range_chk}; } return res; } int Stack::pop_smallint_range(int max, int min) { return (int)pop_long_range(max, min); } Ref Stack::pop_cell() { check_underflow(1); auto res = pop().as_cell(); if (res.is_null()) { throw VmError{Excno::type_chk, "not a cell"}; } return res; } Ref Stack::pop_maybe_cell() { check_underflow(1); auto tmp = pop(); if (tmp.empty()) { return {}; } auto res = std::move(tmp).as_cell(); if (res.is_null()) { throw VmError{Excno::type_chk, "not a cell"}; } return res; } Ref Stack::pop_builder() { check_underflow(1); auto res = pop().as_builder(); if (res.is_null()) { throw VmError{Excno::type_chk, "not a cell builder"}; } return res; } Ref Stack::pop_cellslice() { check_underflow(1); auto res = pop().as_slice(); if (res.is_null()) { throw VmError{Excno::type_chk, "not a cell slice"}; } return res; } std::string Stack::pop_string() { check_underflow(1); auto res = pop().as_string_ref(); if (res.is_null()) { throw VmError{Excno::type_chk, "not a string"}; } return *res; } std::string Stack::pop_bytes() { check_underflow(1); auto res = pop().as_bytes_ref(); if (res.is_null()) { throw VmError{Excno::type_chk, "not a bytes chunk"}; } return *res; } Ref Stack::pop_cont() { check_underflow(1); auto res = pop().as_cont(); if (res.is_null()) { throw VmError{Excno::type_chk, "not a continuation"}; } return res; } Ref Stack::pop_box() { check_underflow(1); auto res = pop().as_box(); if (res.is_null()) { throw VmError{Excno::type_chk, "not a box"}; } return res; } Ref Stack::pop_tuple() { check_underflow(1); auto res = pop().as_tuple(); if (res.is_null()) { throw VmError{Excno::type_chk, "not a tuple"}; } return res; } Ref Stack::pop_tuple_range(unsigned max_len, unsigned min_len) { check_underflow(1); auto res = pop().as_tuple(); if (res.is_null() || res->size() > max_len || res->size() < min_len) { throw VmError{Excno::type_chk, "not a tuple of valid size"}; } return res; } Ref Stack::pop_maybe_tuple() { check_underflow(1); auto val = pop(); if (val.empty()) { return {}; } auto res = std::move(val).as_tuple(); if (res.is_null()) { throw VmError{Excno::type_chk, "not a tuple"}; } return res; } Ref Stack::pop_maybe_tuple_range(unsigned max_len) { check_underflow(1); auto val = pop(); if (val.empty()) { return {}; } auto res = std::move(val).as_tuple(); if (res.is_null() || res->size() > max_len) { throw VmError{Excno::type_chk, "not a tuple of valid size"}; } return res; } Ref Stack::pop_atom() { check_underflow(1); auto res = pop().as_atom(); if (res.is_null()) { throw VmError{Excno::type_chk, "not an atom"}; } return res; } void Stack::push_null() { push({}); } void Stack::push_int(td::RefInt256 val) { if (!val->signed_fits_bits(257)) { throw VmError{Excno::int_ov}; } push(std::move(val)); } void Stack::push_int_quiet(td::RefInt256 val, bool quiet) { if (!val->signed_fits_bits(257)) { if (!quiet) { throw VmError{Excno::int_ov}; } else if (val->is_valid()) { push(td::make_refint()); return; } } push(std::move(val)); } void Stack::push_string(std::string str) { push(std::move(str)); } void Stack::push_string(td::Slice slice) { push(slice.str()); } void Stack::push_bytes(std::string str) { push(std::move(str), true); } void Stack::push_bytes(td::Slice slice) { push(slice.str(), true); } void Stack::push_cell(Ref cell) { push(std::move(cell)); } void Stack::push_maybe_cell(Ref cell) { push_maybe(std::move(cell)); } void Stack::push_builder(Ref cb) { push(std::move(cb)); } void Stack::push_smallint(long long val) { push(td::make_refint(val)); } void Stack::push_bool(bool val) { push_smallint(val ? -1 : 0); } void Stack::push_cont(Ref cont) { push(std::move(cont)); } void Stack::push_box(Ref box) { push(std::move(box)); } void Stack::push_tuple(Ref tuple) { push(std::move(tuple)); } void Stack::push_maybe_tuple(Ref tuple) { if (tuple.not_null()) { push(std::move(tuple)); } else { push_null(); } } void Stack::push_tuple(const std::vector& components) { push(components); } void Stack::push_tuple(std::vector&& components) { push(std::move(components)); } void Stack::push_atom(Ref atom) { push(std::move(atom)); } Ref Stack::split_top(unsigned top_cnt, unsigned drop_cnt) { unsigned n = depth(); if (top_cnt > n || drop_cnt > n - top_cnt) { return Ref{}; } Ref new_stk = Ref{true}; if (top_cnt) { new_stk.unique_write().move_from_stack(*this, top_cnt); } if (drop_cnt) { pop_many(drop_cnt); } return new_stk; } void Stack::dump(std::ostream& os, int mode) const { os << " [ "; if (mode & 2) { for (const auto& x : stack) { x.print_list(os, mode & 4); os << ' '; } } else { for (const auto& x : stack) { x.dump(os, mode & 4); os << ' '; } } os << "] "; if (mode & 1) { os << std::endl; } } void Stack::push_cellslice(Ref cs) { push(std::move(cs)); } void Stack::push_maybe_cellslice(Ref cs) { push_maybe(std::move(cs)); } bool Stack::for_each_scalar(const std::function& func) const { for (const auto& v : stack) { if (!v.for_each_scalar(func)) { return false; } } return true; } void Stack::for_each_scalar(const std::function& func) const { for (const auto& v : stack) { v.for_each_scalar(func); } } /* * * SERIALIZE/DESERIALIZE STACK VALUES * */ bool StackEntry::serialize(vm::CellBuilder& cb, int mode) const { auto* vsi = VmStateInterface::get(); if (vsi && !vsi->register_op()) { return false; } switch (tp) { case t_null: return cb.store_long_bool(0, 8); // vm_stk_null#00 = VmStackValue; case t_int: { auto val = as_int(); if (!val->is_valid()) { // vm_stk_nan#02ff = VmStackValue; return cb.store_long_bool(0x02ff, 16); } else if (!(mode & 1) && val->signed_fits_bits(64)) { // vm_stk_tinyint#01 value:int64 = VmStackValue; return cb.store_long_bool(1, 8) && cb.store_int256_bool(std::move(val), 64); } else { // vm_stk_int#0201_ value:int257 = VmStackValue; return cb.store_long_bool(0x0200 / 2, 15) && cb.store_int256_bool(std::move(val), 257); } } case t_cell: // vm_stk_cell#03 cell:^Cell = VmStackValue; return cb.store_long_bool(3, 8) && cb.store_ref_bool(as_cell()); case t_slice: { // _ cell:^Cell st_bits:(## 10) end_bits:(## 10) { st_bits <= end_bits } // st_ref:(#<= 4) end_ref:(#<= 4) { st_ref <= end_ref } = VmCellSlice; const auto& cs = *static_cast>(ref); return ((mode & 0x1000) || cb.store_long_bool(4, 8)) // vm_stk_slice#04 _:VmCellSlice = VmStackValue; && cb.store_ref_bool(cs.get_base_cell()) // _ cell:^Cell && cb.store_long_bool(cs.cur_pos(), 10) // st_bits:(## 10) && cb.store_long_bool(cs.cur_pos() + cs.size(), 10) // end_bits:(## 10) && cb.store_long_bool(cs.cur_ref(), 3) // st_ref:(#<= 4) && cb.store_long_bool(cs.cur_ref() + cs.size_refs(), 3); // end_ref:(#<= 4) } case t_builder: // vm_stk_builder#05 cell:^Cell = VmStackValue; return cb.store_long_bool(5, 8) && cb.store_ref_bool(as_builder()->finalize_copy()); case t_vmcont: // vm_stk_cont#06 cont:VmCont = VmStackValue; return !(mode & 2) && cb.store_long_bool(6, 8) && as_cont()->serialize(cb); case t_tuple: { const auto& tuple = *static_cast>(ref); auto n = tuple.size(); // vm_stk_tuple#07 len:(## 16) data:(VmTuple len) = VmStackValue; Ref head, tail; vm::CellBuilder cb2; for (std::size_t i = 0; i < n; i++) { std::swap(head, tail); if (i > 1 && !(cb2.store_ref_bool(std::move(tail)) && cb2.store_ref_bool(std::move(head)) && cb2.finalize_to(head))) { return false; } if (!(tuple[i].serialize(cb2, mode) && cb2.finalize_to(tail))) { return false; } } return cb.store_long_bool(7, 8) && cb.store_long_bool(n, 16) && (head.is_null() || cb.store_ref_bool(head)) && (tail.is_null() || cb.store_ref_bool(tail)); } default: return false; } } bool StackEntry::deserialize(CellSlice& cs, int mode) { auto* vsi = VmStateInterface::get(); if (vsi && !vsi->register_op()) { return false; } clear(); int t = (mode & 0xf000) ? ((mode >> 12) & 15) : (int)cs.prefetch_ulong(8); switch (t) { case 0: // vm_stk_null#00 = VmStackValue; return cs.advance(8); case 1: { // vm_stk_tinyint#01 value:int64 = VmStackValue; td::RefInt256 val; return !(mode & 1) && cs.advance(8) && cs.fetch_int256_to(64, val) && set_int(std::move(val)); } case 2: { t = (int)cs.prefetch_ulong(16) & 0x1ff; if (t == 0xff) { // vm_stk_nan#02ff = VmStackValue; return cs.advance(16) && set_int(td::make_refint()); } else { // vm_stk_int#0201_ value:int257 = VmStackValue; td::RefInt256 val; return cs.fetch_ulong(15) == 0x0200 / 2 && cs.fetch_int256_to(257, val) && set_int(std::move(val)); } } case 3: { // vm_stk_cell#03 cell:^Cell = VmStackValue; return cs.have_refs() && cs.advance(8) && set(t_cell, cs.fetch_ref()); } case 4: { // _ cell:^Cell st_bits:(## 10) end_bits:(## 10) { st_bits <= end_bits } // st_ref:(#<= 4) end_ref:(#<= 4) { st_ref <= end_ref } = VmCellSlice; // vm_stk_slice#04 _:VmCellSlice = VmStackValue; unsigned st_bits, end_bits, st_ref, end_ref; Ref cell; Ref csr; return ((mode & 0xf000) || cs.advance(8)) // vm_stk_slice#04 && cs.fetch_ref_to(cell) // cell:^Cell && cs.fetch_uint_to(10, st_bits) // st_bits:(## 10) && cs.fetch_uint_to(10, end_bits) // end_bits:(## 10) && st_bits <= end_bits // { st_bits <= end_bits } && cs.fetch_uint_to(3, st_ref) // st_ref:(#<= 4) && cs.fetch_uint_to(3, end_ref) // end_ref:(#<= 4) && st_ref <= end_ref && end_ref <= 4 // { st_ref <= end_ref } && (csr = load_cell_slice_ref(std::move(cell))).not_null() // load cell slice && csr->have(end_bits, end_ref) && csr.write().skip_last(csr->size() - end_bits, csr->size_refs() - end_ref) && csr.write().skip_first(st_bits, st_ref) && set(t_slice, std::move(csr)); } case 5: { // vm_stk_builder#05 cell:^Cell = VmStackValue; Ref cell; Ref csr; Ref cb{true}; return cs.advance(8) && cs.fetch_ref_to(cell) && (csr = load_cell_slice_ref(std::move(cell))).not_null() && cb.write().append_cellslice_bool(std::move(csr)) && set(t_builder, std::move(cb)); } case 6: { // vm_stk_cont#06 cont:VmCont = VmStackValue; Ref cont; return !(mode & 2) && cs.advance(8) && Continuation::deserialize_to(cs, cont, mode) && set(t_vmcont, std::move(cont)); } case 7: { // vm_stk_tuple#07 len:(## 16) data:(VmTuple len) = VmStackValue; int n; if (!(cs.advance(8) && cs.fetch_uint_to(16, n))) { return false; } Ref tuple{true, n}; auto& t = tuple.write(); if (n > 1) { Ref head, tail; n--; if (!(cs.fetch_ref_to(head) && cs.fetch_ref_to(tail) && t[n].deserialize(std::move(tail), mode))) { return false; } vm::CellSlice cs2; while (--n > 0) { if (!(cs2.load(std::move(head)) && cs2.fetch_ref_to(head) && cs2.fetch_ref_to(tail) && cs2.empty_ext() && t[n].deserialize(std::move(tail), mode))) { return false; } } if (!t[0].deserialize(std::move(head), mode)) { return false; } } else if (n == 1) { return cs.have_refs() && t[0].deserialize(cs.fetch_ref(), mode) && set(t_tuple, std::move(tuple)); } return set(t_tuple, std::move(tuple)); } default: return false; } } bool StackEntry::deserialize(Ref cell, int mode) { if (cell.is_null()) { clear(); return false; } CellSlice cs = load_cell_slice(std::move(cell)); return deserialize(cs, mode) && cs.empty_ext(); } bool Stack::serialize(vm::CellBuilder& cb, int mode) const { auto* vsi = VmStateInterface::get(); if (vsi && !vsi->register_op()) { return false; } try { // vm_stack#_ depth:(## 24) stack:(VmStackList depth) = VmStack; unsigned n = depth(); if (!cb.store_ulong_rchk_bool(n, 24)) { // vm_stack#_ depth:(## 24) return false; } if (!n) { return true; } vm::CellBuilder cb2; Ref rest = cb2.finalize(); // vm_stk_nil#_ = VmStackList 0; for (unsigned i = 0; i < n - 1; i++) { // vm_stk_cons#_ {n:#} rest:^(VmStackList n) tos:VmStackValue = VmStackList (n + 1); if (!(cb2.store_ref_bool(std::move(rest)) && stack[i].serialize(cb2, mode) && cb2.finalize_to(rest))) { return false; } } return cb.store_ref_bool(std::move(rest)) && stack[n - 1].serialize(cb, mode); } catch (CellBuilder::CellCreateError) { return false; } catch (CellBuilder::CellWriteError) { return false; } } bool Stack::deserialize(vm::CellSlice& cs, int mode) { auto* vsi = VmStateInterface::get(); if (vsi && !vsi->register_op()) { return false; } clear(); // vm_stack#_ depth:(## 24) stack:(VmStackList depth) = VmStack; int n; if (!cs.fetch_uint_to(24, n)) { return false; } if (!n) { return true; } stack.resize(n); Ref rest; if (!(cs.fetch_ref_to(rest) && stack[n - 1].deserialize(cs, mode))) { clear(); return false; } for (int i = n - 2; i >= 0; --i) { // vm_stk_cons#_ {n:#} rest:^(VmStackList n) tos:VmStackValue = VmStackList (n + 1); vm::CellSlice cs2 = load_cell_slice(std::move(rest)); if (!(cs2.fetch_ref_to(rest) && stack[i].deserialize(cs2, mode) && cs2.empty_ext())) { clear(); return false; } } if (!load_cell_slice(std::move(rest)).empty_ext()) { clear(); return false; } return true; } bool Stack::deserialize_to(vm::CellSlice& cs, Ref& stack, int mode) { stack = Ref{true}; if (stack.unique_write().deserialize(cs, mode)) { return true; } else { stack.clear(); return false; } } } // namespace vm