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

Support serialization of fixed-size arrays in abi_serializer. #1918

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 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
81 changes: 51 additions & 30 deletions libraries/chain/abi_serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,17 +218,23 @@ namespace eosio { namespace chain {
return ends_with(type, "[]");
}

bool abi_serializer::is_szarray(const string_view& type)const {
std::optional<fc::unsigned_int> abi_serializer::is_szarray(const string_view& type)const {
heifner marked this conversation as resolved.
Show resolved Hide resolved
auto pos1 = type.find_last_of('[');
auto pos2 = type.find_last_of(']');
if(pos1 == string_view::npos || pos2 == string_view::npos) return false;
if(pos1 == string_view::npos || pos2 == string_view::npos)
return {};
auto pos = pos1 + 1;
if(pos == pos2) return false;
if(pos == pos2)
return {};

fc::unsigned_int sz = 0;
while(pos < pos2) {
if( ! (type[pos] >= '0' && type[pos] <= '9') ) return false;
if( ! (type[pos] >= '0' && type[pos] <= '9') )
return {};
sz = 10 * sz + (type[pos] - '0');
++pos;
}
return true;
return sz;
}

bool abi_serializer::is_optional(const string_view& type)const {
Expand Down Expand Up @@ -400,8 +406,26 @@ namespace eosio { namespace chain {
auto h = ctx.enter_scope();
auto rtype = resolve_type(type);
auto ftype = fundamental_type(rtype);
auto btype = built_in_types.find(ftype );
if( btype != built_in_types.end() ) {
auto fixed_array_sz = is_szarray(rtype);

auto read_array = [&](fc::unsigned_int::base_uint sz) {
ctx.hint_array_type_if_in_array();
vector<fc::variant> vars;
vars.reserve(sz);
auto h1 = ctx.push_to_path( impl::array_index_path_item{} );
for( fc::unsigned_int::base_uint i = 0; i < sz; ++i ) {
ctx.set_array_index_of_path_back(i);
auto v = _binary_to_variant(ftype, stream, ctx);
// The exception below is commented out to allow array of optional as input data
//EOS_ASSERT( !v.is_null(), unpack_exception, "Invalid packed array '${p}'", ("p", ctx.get_path_string()) );
vars.emplace_back(std::move(v));
}
return fc::variant( std::move(vars) );
};

if (fixed_array_sz) {
return read_array(*fixed_array_sz);
} else if( auto btype = built_in_types.find(ftype ); btype != built_in_types.end() ) {
try {
return btype->second.first(stream, is_array(rtype), is_optional(rtype), ctx.get_yield_function());
heifner marked this conversation as resolved.
Show resolved Hide resolved
} EOS_RETHROW_EXCEPTIONS( unpack_exception, "Unable to unpack ${class} type '${type}' while processing '${p}'",
Expand All @@ -414,21 +438,7 @@ namespace eosio { namespace chain {
try {
fc::raw::unpack(stream, size);
} EOS_RETHROW_EXCEPTIONS( unpack_exception, "Unable to unpack size of array '${p}'", ("p", ctx.get_path_string()) )
vector<fc::variant> vars;
auto h1 = ctx.push_to_path( impl::array_index_path_item{} );
for( decltype(size.value) i = 0; i < size; ++i ) {
ctx.set_array_index_of_path_back(i);
auto v = _binary_to_variant(ftype, stream, ctx);
// The exception below is commented out to allow array of optional as input data
//EOS_ASSERT( !v.is_null(), unpack_exception, "Invalid packed array '${p}'", ("p", ctx.get_path_string()) );
vars.emplace_back(std::move(v));
}
// QUESTION: Why would the assert below ever fail?
EOS_ASSERT( vars.size() == size.value,
unpack_exception,
"packed size does not match unpacked array size, packed size ${p} actual size ${a}",
("p", size)("a", vars.size()) );
return fc::variant( std::move(vars) );
return read_array(size.value);
} else if ( is_optional(rtype) ) {
char flag;
try {
Expand Down Expand Up @@ -496,14 +506,9 @@ namespace eosio { namespace chain {
auto v_itr = variants.end();
auto s_itr = structs.end();

auto btype = built_in_types.find(fundamental_type(rtype));
if( btype != built_in_types.end() ) {
btype->second.second(var, ds, is_array(rtype), is_optional(rtype), ctx.get_yield_function());
} else if ( is_array(rtype) ) {
ctx.hint_array_type_if_in_array();
vector<fc::variant> vars = var.get_array();
fc::raw::pack(ds, (fc::unsigned_int)vars.size());
auto fixed_array_sz = is_szarray(rtype);

auto pack_array = [&](const vector<fc::variant>& vars) {
auto h1 = ctx.push_to_path( impl::array_index_path_item{} );
auto h2 = ctx.disallow_extensions_unless(false);

Expand All @@ -512,7 +517,23 @@ namespace eosio { namespace chain {
ctx.set_array_index_of_path_back(i);
_variant_to_binary(fundamental_type(rtype), var, ds, ctx);
++i;
}
}
};

if (fixed_array_sz) {
size_t sz = *fixed_array_sz;
ctx.hint_array_type_if_in_array();
const vector<fc::variant>& vars = var.get_array();
EOS_ASSERT( vars.size() == sz, pack_exception,
"Incorrect number of values provided (${a}) for fixed-size (${b}) array type", ("a", sz)("b", vars.size()));
pack_array(vars);
} else if( auto btype = built_in_types.find(fundamental_type(rtype)); btype != built_in_types.end() ) {
btype->second.second(var, ds, is_array(rtype), is_optional(rtype), ctx.get_yield_function());
} else if ( is_array(rtype) ) {
ctx.hint_array_type_if_in_array();
const vector<fc::variant>& vars = var.get_array();
fc::raw::pack(ds, (fc::unsigned_int)vars.size());
pack_array(vars);
} else if( is_optional(rtype) ) {
char flag = !var.is_null();
fc::raw::pack(ds, flag);
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/include/eosio/chain/abi_serializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct abi_serializer {
/// @return string_view of `t` or internal string type
std::string_view resolve_type(const std::string_view& t)const;
bool is_array(const std::string_view& type)const;
bool is_szarray(const std::string_view& type)const;
std::optional<fc::unsigned_int> is_szarray(const std::string_view& type)const;
bool is_optional(const std::string_view& type)const;
bool is_type( const std::string_view& type, const yield_function_t& yield )const;
bool is_type(const std::string_view& type, const fc::microseconds& max_serialization_time)const;
Expand Down
21 changes: 11 additions & 10 deletions libraries/libfc/include/fc/io/varint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
namespace fc {

struct unsigned_int {
unsigned_int( uint32_t v = 0 ):value(v){}
using base_uint = uint32_t;
unsigned_int( base_uint v = 0 ):value(v){}

template<typename T>
unsigned_int( T v ):value(v){}
Expand All @@ -14,22 +15,22 @@ struct unsigned_int {

unsigned_int& operator=( int32_t v ) { value = v; return *this; }

uint32_t value;
base_uint value;

friend bool operator==( const unsigned_int& i, const uint32_t& v ) { return i.value == v; }
friend bool operator==( const uint32_t& i, const unsigned_int& v ) { return i == v.value; }
friend bool operator==( const unsigned_int& i, const base_uint& v ) { return i.value == v; }
friend bool operator==( const base_uint& i, const unsigned_int& v ) { return i == v.value; }
friend bool operator==( const unsigned_int& i, const unsigned_int& v ) { return i.value == v.value; }

friend bool operator!=( const unsigned_int& i, const uint32_t& v ) { return i.value != v; }
friend bool operator!=( const uint32_t& i, const unsigned_int& v ) { return i != v.value; }
friend bool operator!=( const unsigned_int& i, const base_uint& v ) { return i.value != v; }
friend bool operator!=( const base_uint& i, const unsigned_int& v ) { return i != v.value; }
friend bool operator!=( const unsigned_int& i, const unsigned_int& v ) { return i.value != v.value; }

friend bool operator<( const unsigned_int& i, const uint32_t& v ) { return i.value < v; }
friend bool operator<( const uint32_t& i, const unsigned_int& v ) { return i < v.value; }
friend bool operator<( const unsigned_int& i, const base_uint& v ) { return i.value < v; }
friend bool operator<( const base_uint& i, const unsigned_int& v ) { return i < v.value; }
friend bool operator<( const unsigned_int& i, const unsigned_int& v ) { return i.value < v.value; }

friend bool operator>=( const unsigned_int& i, const uint32_t& v ) { return i.value >= v; }
friend bool operator>=( const uint32_t& i, const unsigned_int& v ) { return i >= v.value; }
friend bool operator>=( const unsigned_int& i, const base_uint& v ) { return i.value >= v; }
friend bool operator>=( const base_uint& i, const unsigned_int& v ) { return i >= v.value; }
friend bool operator>=( const unsigned_int& i, const unsigned_int& v ) { return i.value >= v.value; }
};

Expand Down
Loading