diff --git a/Cargo.lock b/Cargo.lock index 255867dbe94008..2d697533fb94a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11966,6 +11966,7 @@ version = "0.1.0" dependencies = [ "ambassador", "bcs 0.1.4", + "better_any", "bytes", "claims", "crossbeam", diff --git a/api/types/src/convert.rs b/api/types/src/convert.rs index 7a9d699c20e12a..61b9e0f97af223 100644 --- a/api/types/src/convert.rs +++ b/api/types/src/convert.rs @@ -892,6 +892,11 @@ impl<'a, S: StateView> MoveConverter<'a, S> { MoveTypeLayout::Struct(struct_layout) => { self.try_into_vm_value_struct(struct_layout, val)? }, + MoveTypeLayout::Function(..) => { + // TODO(#15664): do we actually need this? It appears the code here is dead and + // nowhere used + bail!("unexpected move type {:?} for value {:?}", layout, val) + }, // Some values, e.g., signer or ones with custom serialization // (native), are not stored to storage and so we do not expect diff --git a/api/types/src/move_types.rs b/api/types/src/move_types.rs index 22342761857e0a..b8c995c92fc460 100644 --- a/api/types/src/move_types.rs +++ b/api/types/src/move_types.rs @@ -681,6 +681,10 @@ impl From<TypeTag> for MoveType { items: Box::new(MoveType::from(*v)), }, TypeTag::Struct(v) => MoveType::Struct((*v).into()), + TypeTag::Function(..) => { + // TODO(#15664): determine whether functions and closures need to be supported + panic!("functions not supported by API types") + }, } } } @@ -701,6 +705,10 @@ impl From<&TypeTag> for MoveType { items: Box::new(MoveType::from(v.as_ref())), }, TypeTag::Struct(v) => MoveType::Struct((&**v).into()), + TypeTag::Function(..) => { + // TODO(#15664): determine whether functions and closures need to be supported + panic!("functions not supported by API types") + }, } } } diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/misc.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/misc.rs index b9f4a8615c02e0..755458b36c24ae 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/misc.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/misc.rs @@ -125,6 +125,11 @@ where self.offset = 1; true } + + #[inline] + fn visit_closure(&mut self, depth: usize, len: usize) -> bool { + self.inner.visit_closure(depth, len) + } } struct AbstractValueSizeVisitor<'a> { @@ -200,6 +205,13 @@ impl<'a> ValueVisitor for AbstractValueSizeVisitor<'a> { true } + #[inline] + fn visit_closure(&mut self, _depth: usize, _len: usize) -> bool { + // TODO(#15664): introduce a dedicated gas parameter? + self.size += self.params.struct_; + true + } + #[inline] fn visit_vec(&mut self, _depth: usize, _len: usize) -> bool { self.size += self.params.vector; @@ -366,6 +378,13 @@ impl AbstractValueSizeGasParameters { false } + #[inline] + fn visit_closure(&mut self, _depth: usize, _len: usize) -> bool { + // TODO(#15664): independent gas parameter for closures? + self.res = Some(self.params.struct_); + false + } + #[inline] fn visit_vec(&mut self, _depth: usize, _len: usize) -> bool { self.res = Some(self.params.vector); @@ -509,6 +528,13 @@ impl AbstractValueSizeGasParameters { false } + #[inline] + fn visit_closure(&mut self, _depth: usize, _len: usize) -> bool { + // TODO(#15664): independent gas parameter + self.res = Some(self.params.struct_); + false + } + #[inline] fn visit_vec(&mut self, _depth: usize, _len: usize) -> bool { self.res = Some(self.params.vector); diff --git a/aptos-move/aptos-sdk-builder/src/common.rs b/aptos-move/aptos-sdk-builder/src/common.rs index 9914b5ea613cd5..1711ecaf3414d0 100644 --- a/aptos-move/aptos-sdk-builder/src/common.rs +++ b/aptos-move/aptos-sdk-builder/src/common.rs @@ -45,7 +45,7 @@ fn quote_type_as_format(type_tag: &TypeTag) -> Format { tag if &**tag == Lazy::force(&str_tag) => Format::Seq(Box::new(Format::U8)), _ => type_not_allowed(type_tag), }, - Signer => type_not_allowed(type_tag), + Signer | Function(..) => type_not_allowed(type_tag), } } @@ -122,7 +122,7 @@ pub(crate) fn mangle_type(type_tag: &TypeTag) -> String { tag if &**tag == Lazy::force(&str_tag) => "string".into(), _ => type_not_allowed(type_tag), }, - Signer => type_not_allowed(type_tag), + Signer | Function(..) => type_not_allowed(type_tag), } } diff --git a/aptos-move/aptos-sdk-builder/src/golang.rs b/aptos-move/aptos-sdk-builder/src/golang.rs index dbbbc734d0661d..17a847c0a8cebe 100644 --- a/aptos-move/aptos-sdk-builder/src/golang.rs +++ b/aptos-move/aptos-sdk-builder/src/golang.rs @@ -672,7 +672,7 @@ func encode_{}_argument(arg {}) []byte {{ U8 => ("U8Vector", default_stmt), _ => common::type_not_allowed(type_tag), }, - Struct(_) | Signer => common::type_not_allowed(type_tag), + Struct(_) | Signer | Function(..) => common::type_not_allowed(type_tag), }; writeln!( self.out, @@ -793,7 +793,7 @@ func decode_{0}_argument(arg aptostypes.TransactionArgument) (value {1}, err err tag if &**tag == Lazy::force(&str_tag) => "[]uint8".into(), _ => common::type_not_allowed(type_tag), }, - Signer => common::type_not_allowed(type_tag), + Signer | Function(..) => common::type_not_allowed(type_tag), } } @@ -820,7 +820,7 @@ func decode_{0}_argument(arg aptostypes.TransactionArgument) (value {1}, err err U8 => format!("(*aptostypes.TransactionArgument__U8Vector)(&{})", name), _ => common::type_not_allowed(type_tag), }, - Struct(_) | Signer => common::type_not_allowed(type_tag), + Struct(_) | Signer | Function(..) => common::type_not_allowed(type_tag), } } @@ -854,7 +854,7 @@ func decode_{0}_argument(arg aptostypes.TransactionArgument) (value {1}, err err tag if &**tag == Lazy::force(&str_tag) => Some("Bytes"), _ => common::type_not_allowed(type_tag), }, - Signer => common::type_not_allowed(type_tag), + Signer | Function(..) => common::type_not_allowed(type_tag), } } } diff --git a/aptos-move/aptos-sdk-builder/src/rust.rs b/aptos-move/aptos-sdk-builder/src/rust.rs index cc01736cb3748f..70980cd5d0a5e4 100644 --- a/aptos-move/aptos-sdk-builder/src/rust.rs +++ b/aptos-move/aptos-sdk-builder/src/rust.rs @@ -737,7 +737,7 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy<EntryFunctionDecoderMa U8 => ("U8Vector", "Some(value)".to_string()), _ => common::type_not_allowed(type_tag), }, - Struct(_) | Signer => common::type_not_allowed(type_tag), + Struct(_) | Signer | Function(..) => common::type_not_allowed(type_tag), }; writeln!( self.out, @@ -880,7 +880,7 @@ fn decode_{}_argument(arg: TransactionArgument) -> Option<{}> {{ tag if &**tag == Lazy::force(&str_tag) => "Vec<u8>".into(), _ => common::type_not_allowed(type_tag), }, - Signer => common::type_not_allowed(type_tag), + Signer | Function(..) => common::type_not_allowed(type_tag), } } @@ -909,7 +909,7 @@ fn decode_{}_argument(arg: TransactionArgument) -> Option<{}> {{ _ => common::type_not_allowed(type_tag), }, - Struct(_) | Signer => common::type_not_allowed(type_tag), + Struct(_) | Signer | Function(..) => common::type_not_allowed(type_tag), } } } diff --git a/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs b/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs index f34457f6ee2c08..b07b02cf33fe35 100644 --- a/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs +++ b/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs @@ -127,6 +127,7 @@ impl<'r, 'l> SessionExt<'r, 'l> { module_storage: &impl ModuleStorage, ) -> VMResult<(VMChangeSet, ModuleWriteSet)> { let move_vm = self.inner.get_move_vm(); + let function_extension = module_storage.as_function_value_extension(); let resource_converter = |value: Value, diff --git a/aptos-move/aptos-vm/src/verifier/transaction_arg_validation.rs b/aptos-move/aptos-vm/src/verifier/transaction_arg_validation.rs index d6a96952a88e77..f93d76ed899360 100644 --- a/aptos-move/aptos-vm/src/verifier/transaction_arg_validation.rs +++ b/aptos-move/aptos-vm/src/verifier/transaction_arg_validation.rs @@ -208,7 +208,7 @@ pub(crate) fn is_valid_txn_arg( let full_name = format!("{}::{}", st.module.short_str_lossless(), st.name); allowed_structs.contains_key(&full_name) }), - Signer | Reference(_) | MutableReference(_) | TyParam(_) => false, + Signer | Reference(_) | MutableReference(_) | TyParam(_) | Function { .. } => false, } } @@ -300,7 +300,9 @@ fn construct_arg( Err(invalid_signature()) } }, - Reference(_) | MutableReference(_) | TyParam(_) => Err(invalid_signature()), + Reference(_) | MutableReference(_) | TyParam(_) | Function { .. } => { + Err(invalid_signature()) + }, } } @@ -369,7 +371,9 @@ pub(crate) fn recursively_construct_arg( U64 => read_n_bytes(8, cursor, arg)?, U128 => read_n_bytes(16, cursor, arg)?, U256 | Address => read_n_bytes(32, cursor, arg)?, - Signer | Reference(_) | MutableReference(_) | TyParam(_) => return Err(invalid_signature()), + Signer | Reference(_) | MutableReference(_) | TyParam(_) | Function { .. } => { + return Err(invalid_signature()) + }, }; Ok(()) } diff --git a/aptos-move/framework/move-stdlib/src/natives/bcs.rs b/aptos-move/framework/move-stdlib/src/natives/bcs.rs index 0028d48319cbbd..e86962d074fb94 100644 --- a/aptos-move/framework/move-stdlib/src/natives/bcs.rs +++ b/aptos-move/framework/move-stdlib/src/natives/bcs.rs @@ -193,10 +193,11 @@ fn constant_serialized_size(ty_layout: &MoveTypeLayout) -> (u64, PartialVMResult MoveTypeLayout::Signer => Ok(None), // vectors have no constant size MoveTypeLayout::Vector(_) => Ok(None), - // enums have no constant size + // enums and functions have no constant size MoveTypeLayout::Struct( MoveStructLayout::RuntimeVariants(_) | MoveStructLayout::WithVariants(_), - ) => Ok(None), + ) + | MoveTypeLayout::Function(..) => Ok(None), MoveTypeLayout::Struct(MoveStructLayout::Runtime(fields)) => { let mut total = Some(0); for field in fields { diff --git a/aptos-move/framework/src/natives/string_utils.rs b/aptos-move/framework/src/natives/string_utils.rs index 28e9fd50b103fa..2867fef853fa01 100644 --- a/aptos-move/framework/src/natives/string_utils.rs +++ b/aptos-move/framework/src/natives/string_utils.rs @@ -18,7 +18,7 @@ use move_core_types::{ use move_vm_runtime::native_functions::NativeFunction; use move_vm_types::{ loaded_data::runtime_types::Type, - values::{Reference, Struct, Value, Vector, VectorRef}, + values::{Closure, Reference, Struct, Value, Vector, VectorRef}, }; use smallvec::{smallvec, SmallVec}; use std::{collections::VecDeque, fmt::Write, ops::Deref}; @@ -350,6 +350,23 @@ fn native_format_impl( )?; out.push('}'); }, + MoveTypeLayout::Function(_) => { + let (fun, args) = val.value_as::<Closure>()?.unpack(); + let data = context + .context + .function_value_extension() + .get_serialization_data(fun.as_ref())?; + out.push_str(&fun.to_stable_string()); + format_vector( + context, + data.captured_layouts.iter(), + args.collect(), + depth, + !context.single_line, + out, + )?; + out.push(')'); + }, // This is unreachable because we check layout at the start. Still, return // an error to be safe. diff --git a/aptos-move/script-composer/src/helpers.rs b/aptos-move/script-composer/src/helpers.rs index 2bbab803fcfdc5..adb27fe8bea597 100644 --- a/aptos-move/script-composer/src/helpers.rs +++ b/aptos-move/script-composer/src/helpers.rs @@ -29,6 +29,11 @@ pub(crate) fn import_type_tag( type_tag: &TypeTag, module_resolver: &BTreeMap<ModuleId, CompiledModule>, ) -> PartialVMResult<SignatureToken> { + let to_list = |script_builder: &mut CompiledScriptBuilder, ts: &[TypeTag]| { + ts.iter() + .map(|t| import_type_tag(script_builder, t, module_resolver)) + .collect::<PartialVMResult<Vec<_>>>() + }; Ok(match type_tag { TypeTag::Address => SignatureToken::Address, TypeTag::U8 => SignatureToken::U8, @@ -53,13 +58,15 @@ pub(crate) fn import_type_tag( } else { SignatureToken::StructInstantiation( struct_idx, - s.type_args - .iter() - .map(|ty| import_type_tag(script_builder, ty, module_resolver)) - .collect::<PartialVMResult<Vec<_>>>()?, + to_list(script_builder, &s.type_args)?, ) } }, + TypeTag::Function(f) => SignatureToken::Function( + to_list(script_builder, &f.args)?, + to_list(script_builder, &f.results)?, + f.abilities, + ), }) } diff --git a/testsuite/fuzzer/fuzz/fuzz_targets/move/utils/helpers.rs b/testsuite/fuzzer/fuzz/fuzz_targets/move/utils/helpers.rs index 15960f5f920aab..52c3973dec322e 100644 --- a/testsuite/fuzzer/fuzz/fuzz_targets/move/utils/helpers.rs +++ b/testsuite/fuzzer/fuzz/fuzz_targets/move/utils/helpers.rs @@ -6,7 +6,10 @@ use aptos_language_e2e_tests::{account::Account, executor::FakeExecutor}; use arbitrary::Arbitrary; use move_binary_format::file_format::CompiledModule; -use move_core_types::value::{MoveStructLayout, MoveTypeLayout}; +use move_core_types::{ + function::MoveFunctionLayout, + value::{MoveStructLayout, MoveTypeLayout}, +}; #[macro_export] macro_rules! tdbg { @@ -82,6 +85,9 @@ pub(crate) fn is_valid_layout(layout: &MoveTypeLayout) -> bool { } fields.iter().all(is_valid_layout) }, + L::Function(MoveFunctionLayout(args, results, _)) => { + args.iter().chain(results).all(is_valid_layout) + }, L::Struct(_) => { // decorated layouts not supported false diff --git a/third_party/move/move-binary-format/src/constant.rs b/third_party/move/move-binary-format/src/constant.rs index 77d995b4323490..bd6407714db9d3 100644 --- a/third_party/move/move-binary-format/src/constant.rs +++ b/third_party/move/move-binary-format/src/constant.rs @@ -40,6 +40,7 @@ fn construct_ty_for_constant(layout: &MoveTypeLayout) -> Option<SignatureToken> construct_ty_for_constant(l.as_ref())?, ))), MoveTypeLayout::Struct(_) => None, + MoveTypeLayout::Function(_) => None, MoveTypeLayout::Bool => Some(SignatureToken::Bool), // It is not possible to have native layout for constant values. diff --git a/third_party/move/move-binary-format/src/errors.rs b/third_party/move/move-binary-format/src/errors.rs index 5fe9af70b5a1d2..01d4ea7072a720 100644 --- a/third_party/move/move-binary-format/src/errors.rs +++ b/third_party/move/move-binary-format/src/errors.rs @@ -469,6 +469,10 @@ impl PartialVMError { })) } + pub fn new_invariant_violation(msg: impl ToString) -> PartialVMError { + Self::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(msg.to_string()) + } + pub fn major_status(&self) -> StatusCode { self.0.major_status } diff --git a/third_party/move/move-binary-format/src/normalized.rs b/third_party/move/move-binary-format/src/normalized.rs index d938f8fbcebfe7..037f6f472f8643 100644 --- a/third_party/move/move-binary-format/src/normalized.rs +++ b/third_party/move/move-binary-format/src/normalized.rs @@ -398,6 +398,7 @@ impl From<TypeTag> for Type { name: s.name, type_arguments: s.type_args.into_iter().map(|ty| ty.into()).collect(), }, + TypeTag::Function(_) => panic!("function types not supported in normalized types"), } } } diff --git a/third_party/move/move-compiler/src/cfgir/ast.rs b/third_party/move/move-compiler/src/cfgir/ast.rs index 12788db3550656..e7e2de089dd855 100644 --- a/third_party/move/move-compiler/src/cfgir/ast.rs +++ b/third_party/move/move-compiler/src/cfgir/ast.rs @@ -323,6 +323,7 @@ impl AstDebug for MoveValue { }, V::Struct(_) => panic!("ICE struct constants not supported"), V::Signer(_) => panic!("ICE signer constants not supported"), + V::Closure(_) => panic!("ICE closures not supported"), } } } diff --git a/third_party/move/move-core/types/src/function.rs b/third_party/move/move-core/types/src/function.rs index 1543c2f92396c0..bb12be5d67d022 100644 --- a/third_party/move/move-core/types/src/function.rs +++ b/third_party/move/move-core/types/src/function.rs @@ -1,7 +1,13 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use serde::{Deserialize, Serialize}; +use crate::{ + ability::AbilitySet, + identifier::Identifier, + language_storage::{FunctionTag, ModuleId, TypeTag}, + value::{MoveTypeLayout, MoveValue}, +}; +use serde::{de::Error, ser::SerializeSeq, Deserialize, Serialize}; use std::fmt; /// A `ClosureMask` is a value which determines how to distinguish those function arguments @@ -99,6 +105,19 @@ impl ClosureMask { i } + /// Return the # of captured arguments in the mask + pub fn captured_count(&self) -> u16 { + let mut i = 0; + let mut mask = self.0; + while mask != 0 { + if mask & 0x1 != 0 { + i += 1 + } + mask >>= 1; + } + i + } + pub fn merge_placeholder_strings( &self, arity: usize, @@ -110,3 +129,179 @@ impl ClosureMask { self.compose(captured, provided) } } + +/// Function type layout, with arguments and result types. +#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq)] +#[cfg_attr( + any(test, feature = "fuzzing"), + derive(arbitrary::Arbitrary, dearbitrary::Dearbitrary) +)] +pub struct MoveFunctionLayout( + pub Vec<MoveTypeLayout>, + pub Vec<MoveTypeLayout>, + pub AbilitySet, +); + +/// A closure (function value). The closure stores the name of the +/// function and it's type instantiation, as well as the closure +/// mask and the captured values together with their layout. The latter +/// allows to deserialize closures context free (without needing +/// to lookup information about the function and its dependencies). +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr( + any(test, feature = "fuzzing"), + derive(arbitrary::Arbitrary, dearbitrary::Dearbitrary) +)] +pub struct MoveClosure { + pub module_id: ModuleId, + pub fun_id: Identifier, + pub fun_inst: Vec<TypeTag>, + pub mask: ClosureMask, + pub captured: Vec<(MoveTypeLayout, MoveValue)>, +} + +#[allow(unused)] // Currently, we do not use the expected function layout +pub(crate) struct ClosureVisitor<'a>(pub(crate) &'a MoveFunctionLayout); + +impl<'d, 'a> serde::de::Visitor<'d> for ClosureVisitor<'a> { + type Value = MoveClosure; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("Closure") + } + + fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> + where + A: serde::de::SeqAccess<'d>, + { + let module_id = read_required_value::<_, ModuleId>(&mut seq)?; + let fun_id = read_required_value::<_, Identifier>(&mut seq)?; + let fun_inst = read_required_value::<_, Vec<TypeTag>>(&mut seq)?; + let mask = read_required_value::<_, ClosureMask>(&mut seq)?; + let mut captured = vec![]; + for _ in 0..mask.captured_count() { + let layout = read_required_value::<_, MoveTypeLayout>(&mut seq)?; + match seq.next_element_seed(&layout)? { + Some(v) => captured.push((layout, v)), + None => return Err(A::Error::invalid_length(captured.len(), &self)), + } + } + // If the sequence length is known, check whether there are no extra values + if matches!(seq.size_hint(), Some(remaining) if remaining != 0) { + return Err(A::Error::invalid_length(captured.len(), &self)); + } + Ok(MoveClosure { + module_id, + fun_id, + fun_inst, + mask, + captured, + }) + } +} + +fn read_required_value<'a, A, T>(seq: &mut A) -> Result<T, A::Error> +where + A: serde::de::SeqAccess<'a>, + T: serde::de::Deserialize<'a>, +{ + match seq.next_element::<T>()? { + Some(x) => Ok(x), + None => Err(A::Error::custom("expected more elements")), + } +} + +impl serde::Serialize for MoveClosure { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + let MoveClosure { + module_id, + fun_id, + fun_inst, + mask, + captured, + } = self; + let mut s = serializer.serialize_seq(Some(4 + captured.len()))?; + s.serialize_element(module_id)?; + s.serialize_element(fun_id)?; + s.serialize_element(fun_inst)?; + s.serialize_element(mask)?; + for (l, v) in captured { + s.serialize_element(l)?; + s.serialize_element(v)?; + } + s.end() + } +} + +impl fmt::Display for MoveFunctionLayout { + fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { + let fmt_list = |l: &[MoveTypeLayout]| { + l.iter() + .map(|t| t.to_string()) + .collect::<Vec<_>>() + .join(", ") + }; + let MoveFunctionLayout(args, results, abilities) = self; + write!( + f, + "|{}|{}{}", + fmt_list(args), + fmt_list(results), + abilities.display_postfix() + ) + } +} + +impl TryInto<FunctionTag> for &MoveFunctionLayout { + type Error = anyhow::Error; + + fn try_into(self) -> Result<FunctionTag, Self::Error> { + let into_list = |ts: &[MoveTypeLayout]| { + ts.iter() + .map(|t| t.try_into()) + .collect::<Result<Vec<TypeTag>, _>>() + }; + Ok(FunctionTag { + args: into_list(&self.0)?, + results: into_list(&self.1)?, + abilities: self.2, + }) + } +} + +impl fmt::Display for MoveClosure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let MoveClosure { + module_id, + fun_id, + fun_inst, + mask, + captured, + } = self; + let captured_str = mask + .merge_placeholder_strings( + mask.max_captured() + 1, + captured.iter().map(|v| v.1.to_string()).collect(), + ) + .unwrap_or_else(|| vec!["*invalid*".to_string()]) + .join(","); + let inst_str = if fun_inst.is_empty() { + "".to_string() + } else { + format!( + "<{}>", + fun_inst + .iter() + .map(|t| t.to_string()) + .collect::<Vec<_>>() + .join(",") + ) + }; + write!( + f, + // this will print `a::m::f<T>(a1,_,a2,_)` + "{}::{}{}({})", + module_id, fun_id, inst_str, captured_str + ) + } +} diff --git a/third_party/move/move-core/types/src/language_storage.rs b/third_party/move/move-core/types/src/language_storage.rs index 821b2e22c43d03..010417247d18d9 100644 --- a/third_party/move/move-core/types/src/language_storage.rs +++ b/third_party/move/move-core/types/src/language_storage.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ + ability::AbilitySet, account_address::AccountAddress, identifier::{IdentStr, Identifier}, parser::{parse_module_id, parse_struct_tag, parse_type_tag}, @@ -67,6 +68,15 @@ pub enum TypeTag { U32, #[serde(rename = "u256", alias = "U256")] U256, + + // NOTE: added in bytecode version v8 + Function( + #[serde( + serialize_with = "safe_serialize::type_tag_recursive_serialize", + deserialize_with = "safe_serialize::type_tag_recursive_deserialize" + )] + Box<FunctionTag>, + ), } impl TypeTag { @@ -82,6 +92,7 @@ impl TypeTag { /// to change and should not be used inside stable code. pub fn to_canonical_string(&self) -> String { use TypeTag::*; + match self { Bool => "bool".to_owned(), U8 => "u8".to_owned(), @@ -94,6 +105,25 @@ impl TypeTag { Signer => "signer".to_owned(), Vector(t) => format!("vector<{}>", t.to_canonical_string()), Struct(s) => s.to_canonical_string(), + Function(f) => { + let fmt_list = |l: &[TypeTag]| -> String { + l.iter() + .map(|t| t.to_canonical_string()) + .collect::<Vec<_>>() + .join(",") + }; + let FunctionTag { + args, + results, + abilities, + } = f.as_ref(); + format!( + "|{}|{}{}", + fmt_list(args), + fmt_list(results), + abilities.display_postfix() + ) + }, } } } @@ -193,6 +223,19 @@ impl FromStr for StructTag { } } +#[derive(Serialize, Deserialize, Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] +#[cfg_attr( + feature = "fuzzing", + derive(arbitrary::Arbitrary, dearbitrary::Dearbitrary) +)] +#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))] +#[cfg_attr(any(test, feature = "fuzzing"), proptest(no_params))] +pub struct FunctionTag { + pub args: Vec<TypeTag>, + pub results: Vec<TypeTag>, + pub abilities: AbilitySet, +} + /// Represents the initial key into global storage where we first index by the address, and then /// the struct tag #[derive(Serialize, Deserialize, Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] @@ -320,6 +363,7 @@ impl Display for TypeTag { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { match self { TypeTag::Struct(s) => write!(f, "{}", s), + TypeTag::Function(_) => write!(f, "{}", self.to_canonical_string()), TypeTag::Vector(ty) => write!(f, "vector<{}>", ty), TypeTag::U8 => write!(f, "u8"), TypeTag::U16 => write!(f, "u16"), @@ -334,6 +378,12 @@ impl Display for TypeTag { } } +impl Display for FunctionTag { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + TypeTag::Function(Box::new(self.clone())).fmt(f) + } +} + impl Display for ResourceKey { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { write!(f, "0x{}/{}", self.address.short_str_lossless(), self.type_) diff --git a/third_party/move/move-core/types/src/safe_serialize.rs b/third_party/move/move-core/types/src/safe_serialize.rs index 42ab4b4a897089..351e8e42ddfd9f 100644 --- a/third_party/move/move-core/types/src/safe_serialize.rs +++ b/third_party/move/move-core/types/src/safe_serialize.rs @@ -4,9 +4,6 @@ //! Custom serializers which track recursion nesting with a thread local, //! and otherwise delegate to the derived serializers. -//! -//! This is currently only implemented for type tags, but can be easily -//! generalized, as the only type-tag specific thing is the allowed nesting. use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cell::RefCell; diff --git a/third_party/move/move-core/types/src/transaction_argument.rs b/third_party/move/move-core/types/src/transaction_argument.rs index 701fbce112f484..da85bc7ccabe57 100644 --- a/third_party/move/move-core/types/src/transaction_argument.rs +++ b/third_party/move/move-core/types/src/transaction_argument.rs @@ -82,7 +82,7 @@ impl TryFrom<MoveValue> for TransactionArgument { }) .collect::<Result<Vec<u8>>>()?, ), - MoveValue::Signer(_) | MoveValue::Struct(_) => { + MoveValue::Signer(_) | MoveValue::Struct(_) | MoveValue::Closure(_) => { return Err(anyhow!("invalid transaction argument: {:?}", val)) }, MoveValue::U16(i) => TransactionArgument::U16(i), diff --git a/third_party/move/move-core/types/src/value.rs b/third_party/move/move-core/types/src/value.rs index f90fd9e6cec2cd..28805c5315e744 100644 --- a/third_party/move/move-core/types/src/value.rs +++ b/third_party/move/move-core/types/src/value.rs @@ -9,6 +9,7 @@ use crate::{ account_address::AccountAddress, + function::{ClosureVisitor, MoveClosure, MoveFunctionLayout}, ident_str, identifier::Identifier, language_storage::{ModuleId, StructTag, TypeTag}, @@ -135,6 +136,8 @@ pub enum MoveValue { U16(u16), U32(u32), U256(u256::U256), + // Added in bytecode version v8 + Closure(Box<MoveClosure>), } /// A layout associated with a named field @@ -263,6 +266,10 @@ pub enum MoveTypeLayout { // TODO[agg_v2](?): Do we need a layout here if we have custom serde // implementations available? Native(IdentifierMappingKind, Box<MoveTypeLayout>), + + // Added in bytecode version v8 + #[serde(rename(serialize = "fun", deserialize = "fun"))] + Function(MoveFunctionLayout), } impl MoveValue { @@ -274,6 +281,10 @@ impl MoveValue { bcs::to_bytes(self).ok() } + pub fn closure(c: MoveClosure) -> MoveValue { + Self::Closure(Box::new(c)) + } + pub fn vector_u8(v: Vec<u8>) -> Self { MoveValue::Vector(v.into_iter().map(MoveValue::U8).collect()) } @@ -556,6 +567,9 @@ impl<'d> serde::de::DeserializeSeed<'d> for &MoveTypeLayout { }, MoveTypeLayout::Signer => Err(D::Error::custom("cannot deserialize signer")), MoveTypeLayout::Struct(ty) => Ok(MoveValue::Struct(ty.deserialize(deserializer)?)), + MoveTypeLayout::Function(fun) => Ok(MoveValue::Closure(Box::new( + deserializer.deserialize_seq(ClosureVisitor(fun))?, + ))), MoveTypeLayout::Vector(layout) => Ok(MoveValue::Vector( deserializer.deserialize_seq(VectorElementVisitor(layout))?, )), @@ -750,6 +764,7 @@ impl serde::Serialize for MoveValue { fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { match self { MoveValue::Struct(s) => s.serialize(serializer), + MoveValue::Closure(c) => c.serialize(serializer), MoveValue::Bool(b) => serializer.serialize_bool(*b), MoveValue::U8(i) => serializer.serialize_u8(*i), MoveValue::U16(i) => serializer.serialize_u16(*i), @@ -877,6 +892,7 @@ impl fmt::Display for MoveTypeLayout { Address => write!(f, "address"), Vector(typ) => write!(f, "vector<{}>", typ), Struct(s) => fmt::Display::fmt(s, f), + Function(fun) => fmt::Display::fmt(fun, f), Signer => write!(f, "signer"), // TODO[agg_v2](cleanup): consider printing the tag as well. Native(_, typ) => write!(f, "native<{}>", typ), @@ -944,6 +960,7 @@ impl TryInto<TypeTag> for &MoveTypeLayout { MoveTypeLayout::Signer => TypeTag::Signer, MoveTypeLayout::Vector(v) => TypeTag::Vector(Box::new(v.as_ref().try_into()?)), MoveTypeLayout::Struct(v) => TypeTag::Struct(Box::new(v.try_into()?)), + MoveTypeLayout::Function(f) => TypeTag::Function(Box::new(f.try_into()?)), // Native layout variant is only used by MoveVM, and is irrelevant // for type tags which are used to key resources in the global state. @@ -981,6 +998,7 @@ impl fmt::Display for MoveValue { MoveValue::Signer(a) => write!(f, "signer({})", a.to_hex_literal()), MoveValue::Vector(v) => fmt_list(f, "vector[", v, "]"), MoveValue::Struct(s) => fmt::Display::fmt(s, f), + MoveValue::Closure(c) => fmt::Display::fmt(c, f), } } } diff --git a/third_party/move/move-ir/types/src/ast.rs b/third_party/move/move-ir/types/src/ast.rs index 40fa5695baf430..4718cf90841705 100644 --- a/third_party/move/move-ir/types/src/ast.rs +++ b/third_party/move/move-ir/types/src/ast.rs @@ -1807,7 +1807,7 @@ fn format_move_value(v: &MoveValue) -> String { .join(", "); format!("vector[{}]", items) }, - MoveValue::Struct(_) | MoveValue::Signer(_) => { + MoveValue::Struct(_) | MoveValue::Signer(_) | MoveValue::Closure(_) => { panic!("Should be inexpressible as a constant") }, MoveValue::U16(u) => format!("{}u16", u), diff --git a/third_party/move/move-model/src/builder/exp_builder.rs b/third_party/move/move-model/src/builder/exp_builder.rs index b515e254d08994..3cf66a1ca2f1e7 100644 --- a/third_party/move/move-model/src/builder/exp_builder.rs +++ b/third_party/move/move-model/src/builder/exp_builder.rs @@ -5484,30 +5484,10 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo Value::Vector(b) }, }, - (Type::Primitive(_), MoveValue::Vector(_)) - | (Type::Primitive(_), MoveValue::Struct(_)) - | (Type::Tuple(_), MoveValue::Vector(_)) - | (Type::Tuple(_), MoveValue::Struct(_)) - | (Type::Vector(_), MoveValue::Struct(_)) - | (Type::Struct(_, _, _), MoveValue::Vector(_)) - | (Type::Struct(_, _, _), MoveValue::Struct(_)) - | (Type::TypeParameter(_), MoveValue::Vector(_)) - | (Type::TypeParameter(_), MoveValue::Struct(_)) - | (Type::Reference(_, _), MoveValue::Vector(_)) - | (Type::Reference(_, _), MoveValue::Struct(_)) - | (Type::Fun(..), MoveValue::Vector(_)) - | (Type::Fun(..), MoveValue::Struct(_)) - | (Type::TypeDomain(_), MoveValue::Vector(_)) - | (Type::TypeDomain(_), MoveValue::Struct(_)) - | (Type::ResourceDomain(_, _, _), MoveValue::Vector(_)) - | (Type::ResourceDomain(_, _, _), MoveValue::Struct(_)) - | (Type::Error, MoveValue::Vector(_)) - | (Type::Error, MoveValue::Struct(_)) - | (Type::Var(_), MoveValue::Vector(_)) - | (Type::Var(_), MoveValue::Struct(_)) => { + _ => { self.error( loc, - &format!("Not yet supported constant value: {:?}", value), + &format!("Not supported constant value/type combination: {}", value), ); Value::Bool(false) }, diff --git a/third_party/move/move-model/src/ty.rs b/third_party/move/move-model/src/ty.rs index f3ba39c623ee05..69294a3b9f6b6c 100644 --- a/third_party/move/move-model/src/ty.rs +++ b/third_party/move/move-model/src/ty.rs @@ -24,7 +24,7 @@ use move_binary_format::{ }; use move_core_types::{ ability::{Ability, AbilitySet}, - language_storage::{StructTag, TypeTag}, + language_storage::{FunctionTag, StructTag, TypeTag}, u256::U256, }; use num::BigInt; @@ -1333,6 +1333,21 @@ impl Type { Struct(qid.module_id, qid.id, type_args) }, TypeTag::Vector(type_param) => Vector(Box::new(Self::from_type_tag(type_param, env))), + TypeTag::Function(fun) => { + let FunctionTag { + args, + results, + abilities, + } = fun.as_ref(); + let from_vec = |ts: &[TypeTag]| { + Type::tuple(ts.iter().map(|t| Type::from_type_tag(t, env)).collect_vec()) + }; + Fun( + Box::new(from_vec(args)), + Box::new(from_vec(results)), + *abilities, + ) + }, } } diff --git a/third_party/move/move-stdlib/src/natives/debug.rs b/third_party/move/move-stdlib/src/natives/debug.rs index 38876b7685fdb6..408b5a084e8994 100644 --- a/third_party/move/move-stdlib/src/natives/debug.rs +++ b/third_party/move/move-stdlib/src/natives/debug.rs @@ -460,6 +460,9 @@ mod testing { )?; } }, + MoveValue::Closure(clos) => { + write!(out, "{}", clos).map_err(fmt_error_to_partial_vm_error)?; + }, MoveValue::Struct(move_struct) => match move_struct { MoveStruct::WithTypes { _type_: type_, diff --git a/third_party/move/move-vm/runtime/Cargo.toml b/third_party/move/move-vm/runtime/Cargo.toml index 4b20b934b638ca..e3047e2a5ce068 100644 --- a/third_party/move/move-vm/runtime/Cargo.toml +++ b/third_party/move/move-vm/runtime/Cargo.toml @@ -37,7 +37,7 @@ hex = { workspace = true } move-binary-format = { workspace = true, features = ["fuzzing"] } move-compiler = { workspace = true } move-ir-compiler = { workspace = true } -move-vm-test-utils ={ workspace = true } +move-vm-test-utils = { workspace = true } proptest = { workspace = true } [features] diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs index f20f3b3b0cd98e..30faf540459e85 100644 --- a/third_party/move/move-vm/runtime/src/interpreter.rs +++ b/third_party/move/move-vm/runtime/src/interpreter.rs @@ -394,11 +394,9 @@ impl InterpreterImpl { // Charge gas let module_id = function.module_id().ok_or_else(|| { - let err = - PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) - .with_message( - "Failed to get native function module id".to_string(), - ); + let err = PartialVMError::new_invariant_violation( + "Failed to get native function module id".to_string(), + ); set_err_info!(current_frame, err) })?; gas_meter @@ -490,8 +488,9 @@ impl InterpreterImpl { let module_id = function .module_id() .ok_or_else(|| { - PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) - .with_message("Failed to get native function module id".to_string()) + PartialVMError::new_invariant_violation( + "Failed to get native function module id", + ) }) .map_err(|e| set_err_info!(current_frame, e))?; // Charge gas @@ -765,13 +764,9 @@ impl InterpreterImpl { // Paranoid check to protect us against incorrect native function implementations. A native function that // returns a different number of values than its declared types will trigger this check. if return_values.len() != function.return_tys().len() { - return Err( - PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) - .with_message( - "Arity mismatch: return value count does not match return type count" - .to_string(), - ), - ); + return Err(PartialVMError::new_invariant_violation( + "Arity mismatch: return value count does not match return type count", + )); } // Put return values on the top of the operand stack, where the caller will find them. // This is one of only two times the operand stack is shared across call stack frames; the other is in handling @@ -795,13 +790,14 @@ impl InterpreterImpl { Err(PartialVMError::new(StatusCode::ABORTED).with_sub_status(abort_code)) }, NativeResult::OutOfGas { partial_cost } => { - let err = match gas_meter.charge_native_function( - partial_cost, - Option::<std::iter::Empty<&Value>>::None, - ) { + let err = match gas_meter + .charge_native_function(partial_cost, Option::<std::iter::Empty<&Value>>::None) + { Err(err) if err.major_status() == StatusCode::OUT_OF_GAS => err, - Ok(_) | Err(_) => PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message( - "The partial cost returned by the native function did not cause the gas meter to trigger an OutOfGas error, at least one of them is violating the contract".to_string() + Ok(_) | Err(_) => PartialVMError::new_invariant_violation( + "The partial cost returned by the native function did \ + not cause the gas meter to trigger an OutOfGas error, at least \ + one of them is violating the contract", ), }; @@ -1609,6 +1605,18 @@ fn check_depth_of_type_impl( )?; check_depth!(formula.solve(&ty_arg_depths)) }, + Type::Function { args, results, .. } => { + let mut ty_max = depth; + for ty in args.iter().chain(results.iter()).map(|rc| rc.as_ref()) { + ty_max = ty_max.max(check_depth_of_type_impl( + resolver, + ty, + max_depth, + check_depth!(1), + )?); + } + ty_max + }, Type::TyParam(_) => { return Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) diff --git a/third_party/move/move-vm/runtime/src/loader/function.rs b/third_party/move/move-vm/runtime/src/loader/function.rs index e65b3c2c8ca78f..8ad45277fbbb13 100644 --- a/third_party/move/move-vm/runtime/src/loader/function.rs +++ b/third_party/move/move-vm/runtime/src/loader/function.rs @@ -8,8 +8,10 @@ use crate::{ Resolver, Script, }, native_functions::{NativeFunction, NativeFunctions, UnboxedNativeFunction}, + storage::ty_tag_converter::TypeTagConverter, ModuleStorage, }; +use better_any::{Tid, TidAble, TidExt}; use move_binary_format::{ access::ModuleAccess, binary_views::BinaryIndexedView, @@ -17,13 +19,21 @@ use move_binary_format::{ file_format::{Bytecode, CompiledModule, FunctionDefinitionIndex, Visibility}, }; use move_core_types::{ - ability::AbilitySet, identifier::Identifier, language_storage::ModuleId, vm_status::StatusCode, + ability::{Ability, AbilitySet}, + function::ClosureMask, + identifier::{IdentStr, Identifier}, + language_storage::{ModuleId, TypeTag}, + value::MoveTypeLayout, + vm_status::StatusCode, }; -use move_vm_types::loaded_data::{ - runtime_access_specifier::AccessSpecifier, - runtime_types::{StructIdentifier, Type}, +use move_vm_types::{ + loaded_data::{ + runtime_access_specifier::AccessSpecifier, + runtime_types::{StructIdentifier, Type}, + }, + values::{AbstractFunction, SerializedFunctionData}, }; -use std::{fmt::Debug, sync::Arc}; +use std::{cell::RefCell, cmp::Ordering, fmt::Debug, rc::Rc, sync::Arc}; /// A runtime function definition representation. pub struct Function { @@ -45,12 +55,14 @@ pub struct Function { } /// For loaded function representation, specifies the owner: a script or a module. +#[derive(Clone)] pub(crate) enum LoadedFunctionOwner { Script(Arc<Script>), Module(Arc<Module>), } /// A loaded runtime function representation along with type arguments used to instantiate it. +#[derive(Clone)] pub struct LoadedFunction { pub(crate) owner: LoadedFunctionOwner, // A set of verified type arguments provided for this definition. If @@ -60,16 +72,256 @@ pub struct LoadedFunction { pub(crate) function: Arc<Function>, } +/// A lazy loaded function, which can either be unresolved (as resulting +/// from deserialization) or resolved, and then forwarding to a +/// `LoadedFunction`. This is wrapped into a Rc so one can clone the +/// function while sharing the loading state. +#[derive(Clone, Tid)] +pub(crate) struct LazyLoadedFunction(pub(crate) Rc<RefCell<LazyLoadedFunctionState>>); + +#[derive(Clone)] +pub(crate) enum LazyLoadedFunctionState { + Unresolved { + data: SerializedFunctionData, + resolution_error: Option<PartialVMError>, + }, + Resolved { + fun: Rc<LoadedFunction>, + fun_inst: Vec<TypeTag>, + mask: ClosureMask, + }, +} + +impl LazyLoadedFunction { + pub(crate) fn new_unresolved(data: SerializedFunctionData) -> Self { + Self(Rc::new(RefCell::new(LazyLoadedFunctionState::Unresolved { + data, + resolution_error: None, + }))) + } + + #[allow(unused)] + pub(crate) fn new_resolved( + converter: &TypeTagConverter, + fun: Rc<LoadedFunction>, + mask: ClosureMask, + ) -> PartialVMResult<Self> { + let fun_inst = fun + .ty_args + .iter() + .map(|t| converter.ty_to_ty_tag(t)) + .collect::<PartialVMResult<Vec<_>>>()?; + Ok(Self(Rc::new(RefCell::new( + LazyLoadedFunctionState::Resolved { + fun, + fun_inst, + mask, + }, + )))) + } + + pub(crate) fn expect_this_impl( + fun: &dyn AbstractFunction, + ) -> PartialVMResult<&LazyLoadedFunction> { + fun.downcast_ref::<LazyLoadedFunction>().ok_or_else(|| { + PartialVMError::new_invariant_violation("unexpected abstract function implementation") + }) + } + + /// Access name components independent of resolution state. Since RefCell is in the play, + /// the accessor is passed in as a function. + pub(crate) fn with_name_and_inst<T>( + &self, + action: impl FnOnce(Option<&ModuleId>, &IdentStr, &[TypeTag]) -> T, + ) -> T { + match &*self.0.borrow() { + LazyLoadedFunctionState::Unresolved { + data: + SerializedFunctionData { + module_id, + fun_id, + fun_inst, + .. + }, + .. + } => action(Some(module_id), fun_id, fun_inst), + LazyLoadedFunctionState::Resolved { fun, fun_inst, .. } => { + action(fun.module_id(), fun.name_id(), fun_inst) + }, + } + } + + /// Executed an action with the resolved loaded function. If the function hasn't been + /// loaded yet, it will be loaded now. + #[allow(unused)] + pub(crate) fn with_resolved_function<T>( + &self, + storage: &dyn ModuleStorage, + action: impl FnOnce(Rc<LoadedFunction>) -> PartialVMResult<T>, + ) -> PartialVMResult<T> { + let mut state = self.0.borrow_mut(); + match &mut *state { + LazyLoadedFunctionState::Resolved { fun, .. } => action(fun.clone()), + LazyLoadedFunctionState::Unresolved { + resolution_error: Some(e), + .. + } => Err(e.clone()), + LazyLoadedFunctionState::Unresolved { + data: + SerializedFunctionData { + module_id, + fun_id, + fun_inst, + mask, + captured_layouts, + }, + resolution_error, + } => match Self::resolve( + storage, + module_id, + fun_id, + fun_inst, + *mask, + captured_layouts, + ) { + Ok(fun) => { + let result = action(fun.clone()); + *state = LazyLoadedFunctionState::Resolved { + fun, + fun_inst: fun_inst.clone(), + mask: *mask, + }; + result + }, + Err(e) => { + *resolution_error = Some(e.clone()); + Err(e) + }, + }, + } + } + + /// Resolves a function into a loaded function. This verifies existence of the named + /// function as well as whether it has the type used for deserializing the captured values. + fn resolve( + module_storage: &dyn ModuleStorage, + module_id: &ModuleId, + fun_id: &IdentStr, + fun_inst: &[TypeTag], + mask: ClosureMask, + captured_layouts: &[MoveTypeLayout], + ) -> PartialVMResult<Rc<LoadedFunction>> { + let (module, function) = module_storage + .fetch_function_definition(module_id.address(), module_id.name(), fun_id) + .map_err(|err| err.to_partial())?; + let ty_args = fun_inst + .iter() + .map(|t| module_storage.fetch_ty(t)) + .collect::<PartialVMResult<Vec<_>>>()?; + + // Verify that the function arguments match the types used for deserialization. + let captured_arg_types = mask.extract(function.param_tys(), true); + let converter = TypeTagConverter::new(module_storage.runtime_environment()); + let mut ok = captured_arg_types.len() == captured_layouts.len(); + if ok { + for (ty, layout) in captured_arg_types.into_iter().zip(captured_layouts) { + // Convert layout into TypeTag + let serialized_tag: TypeTag = layout.try_into().map_err(|_| { + PartialVMError::new_invariant_violation( + "unexpected type tag conversion failure", + ) + })?; + let arg_tag = converter.ty_to_ty_tag(ty)?; + if arg_tag != serialized_tag { + ok = false; + break; + } + } + } + if ok { + Ok(Rc::new(LoadedFunction { + owner: LoadedFunctionOwner::Module(module), + ty_args, + function, + })) + } else { + Err( + PartialVMError::new(StatusCode::FUNCTION_RESOLUTION_FAILURE).with_message( + "inconsistency between types of serialized captured \ + values and function parameter types" + .to_string(), + ), + ) + } + } +} + +impl AbstractFunction for LazyLoadedFunction { + fn closure_mask(&self) -> ClosureMask { + let state = self.0.borrow(); + match &*state { + LazyLoadedFunctionState::Resolved { mask, .. } => *mask, + LazyLoadedFunctionState::Unresolved { + data: SerializedFunctionData { mask, .. }, + .. + } => *mask, + } + } + + fn cmp_dyn(&self, other: &dyn AbstractFunction) -> PartialVMResult<Ordering> { + let other = LazyLoadedFunction::expect_this_impl(other)?; + self.with_name_and_inst(|mid1, fid1, inst1| { + other.with_name_and_inst(|mid2, fid2, inst2| { + Ok(mid1 + .cmp(&mid2) + .then_with(|| fid1.cmp(fid2)) + .then_with(|| inst1.cmp(inst2))) + }) + }) + } + + fn clone_dyn(&self) -> PartialVMResult<Box<dyn AbstractFunction>> { + Ok(Box::new(self.clone())) + } + + fn to_stable_string(&self) -> String { + self.with_name_and_inst(|module_id, fun_id, fun_inst| { + let prefix = if let Some(m) = module_id { + format!("0x{}::{}::", m.address(), m.name()) + } else { + "".to_string() + }; + let fun_inst_str = if fun_inst.is_empty() { + "".to_string() + } else { + format!( + "<{}>", + fun_inst + .iter() + .map(|t| t.to_canonical_string()) + .collect::<Vec<_>>() + .join(",") + ) + }; + format!("{}::{}:{}", prefix, fun_id, fun_inst_str) + }) + } +} + impl LoadedFunction { /// Returns type arguments used to instantiate the loaded function. pub fn ty_args(&self) -> &[Type] { &self.ty_args } + pub fn abilities(&self) -> AbilitySet { + self.function.abilities() + } + /// Returns the corresponding module id of this function, i.e., its address and module name. pub fn module_id(&self) -> Option<&ModuleId> { match &self.owner { - LoadedFunctionOwner::Module(m) => Some(m.self_id()), + LoadedFunctionOwner::Module(m) => Some(Module::self_id(m)), LoadedFunctionOwner::Script(_) => None, } } @@ -79,6 +331,11 @@ impl LoadedFunction { self.function.name() } + /// Returns the id of this function's name. + pub fn name_id(&self) -> &IdentStr { + self.function.name_id() + } + /// Returns true if the loaded function has friend or private visibility. pub fn is_friend_or_private(&self) -> bool { self.function.is_friend_or_private() @@ -254,6 +511,10 @@ impl Function { self.name.as_str() } + pub(crate) fn name_id(&self) -> &IdentStr { + &self.name + } + pub fn ty_param_abilities(&self) -> &[AbilitySet] { &self.ty_param_abilities } @@ -270,6 +531,36 @@ impl Function { &self.param_tys } + /// Creates the function type instance for this function. This requires cloning + /// the parameter and result types. + pub fn create_function_type(&self) -> Type { + Type::Function { + args: self + .param_tys + .iter() + .map(|t| triomphe::Arc::new(t.clone())) + .collect(), + results: self + .return_tys + .iter() + .map(|t| triomphe::Arc::new(t.clone())) + .collect(), + abilities: self.abilities(), + } + } + + /// Returns the abilities associated with this function, without consideration of any captured + /// closure arguments. By default, this is copy and drop, and if the function is + /// immutable (public), also store. + pub fn abilities(&self) -> AbilitySet { + let result = AbilitySet::singleton(Ability::Copy).add(Ability::Drop); + if !self.is_friend_or_private { + result.add(Ability::Store) + } else { + result + } + } + pub fn is_native(&self) -> bool { self.is_native } diff --git a/third_party/move/move-vm/runtime/src/loader/mod.rs b/third_party/move/move-vm/runtime/src/loader/mod.rs index 2753b3775b8502..b611f6ae395684 100644 --- a/third_party/move/move-vm/runtime/src/loader/mod.rs +++ b/third_party/move/move-vm/runtime/src/loader/mod.rs @@ -57,17 +57,22 @@ use crate::{ }, }; pub use function::{Function, LoadedFunction}; -pub(crate) use function::{FunctionHandle, FunctionInstantiation, LoadedFunctionOwner}; +pub(crate) use function::{ + FunctionHandle, FunctionInstantiation, LazyLoadedFunction, LazyLoadedFunctionState, + LoadedFunctionOwner, +}; pub use modules::Module; pub(crate) use modules::{LegacyModuleCache, LegacyModuleStorage, LegacyModuleStorageAdapter}; use move_binary_format::file_format::{ StructVariantHandleIndex, StructVariantInstantiationIndex, TypeParameterIndex, VariantFieldHandleIndex, VariantFieldInstantiationIndex, VariantIndex, }; +use move_core_types::language_storage::FunctionTag; use move_vm_metrics::{Timer, VM_TIMER}; use move_vm_types::{ loaded_data::runtime_types::{DepthFormula, StructLayout, TypeBuilder}, value_serde::FunctionValueExtension, + values::{AbstractFunction, SerializedFunctionData}, }; pub use script::Script; pub(crate) use script::ScriptCache; @@ -1662,6 +1667,26 @@ impl<'a> FunctionValueExtension for Resolver<'a> { }; function_value_extension.get_function_arg_tys(module_id, function_name, ty_arg_tags) } + + fn create_from_serialization_data( + &self, + data: SerializedFunctionData, + ) -> PartialVMResult<Box<dyn AbstractFunction>> { + let function_value_extension = FunctionValueExtensionAdapter { + module_storage: self.module_storage, + }; + function_value_extension.create_from_serialization_data(data) + } + + fn get_serialization_data( + &self, + fun: &dyn AbstractFunction, + ) -> PartialVMResult<SerializedFunctionData> { + let function_value_extension = FunctionValueExtensionAdapter { + module_storage: self.module_storage, + }; + function_value_extension.get_serialization_data(fun) + } } /// Maximal depth of a value in terms of type depth. @@ -1777,6 +1802,24 @@ impl LoaderV1 { Type::StructInstantiation { idx, ty_args, .. } => TypeTag::Struct(Box::new( self.struct_name_to_type_tag(*idx, ty_args, gas_context)?, )), + Type::Function { + args, + results, + abilities, + } => { + let to_vec = |ts: &[triomphe::Arc<Type>], + gas_ctx: &mut PseudoGasContext| + -> PartialVMResult<Vec<TypeTag>> { + ts.iter() + .map(|t| self.type_to_type_tag_impl(t, gas_ctx)) + .collect() + }; + TypeTag::Function(Box::new(FunctionTag { + args: to_vec(args, gas_context)?, + results: to_vec(results, gas_context)?, + abilities: *abilities, + })) + }, Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => { return Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) @@ -1893,6 +1936,18 @@ impl Loader { subst_struct_formula.scale(1); subst_struct_formula }, + Type::Function { + args, + results, + abilities: _, + } => DepthFormula::normalize( + args.iter() + .chain(results) + .map(|rc| { + self.calculate_depth_of_type(rc.as_ref(), module_store, module_storage) + }) + .collect::<PartialVMResult<Vec<_>>>()?, + ), }) } } @@ -1939,6 +1994,8 @@ impl Loader { // Matches the actual returned type to the expected type, binding any type args to the // necessary type as stored in the map. The expected type must be a concrete type (no TyParam). // Returns true if a successful match is made. +// TODO: is this really needed in presence of paranoid mode? This does a deep structural +// comparison and is expensive. fn match_return_type<'a>( returned: &Type, expected: &'a Type, @@ -1961,6 +2018,30 @@ fn match_return_type<'a>( (Type::Vector(ret_inner), Type::Vector(expected_inner)) => { match_return_type(ret_inner, expected_inner, map) }, + // Function types, the expected abilities need to be a subset of the provided ones, + // and recursively argument and result types need to match. + ( + Type::Function { + args, + results, + abilities, + }, + Type::Function { + args: exp_args, + results: exp_results, + abilities: exp_abilities, + }, + ) => { + exp_abilities.is_subset(*abilities) + && args + .iter() + .zip(exp_args) + .all(|(t, e)| match_return_type(t, e, map)) + && results + .iter() + .zip(exp_results) + .all(|(t, e)| match_return_type(t, e, map)) + }, // Abilities should not contribute to the equality check as they just serve for caching computations. // For structs the both need to be the same struct. ( @@ -2013,6 +2094,7 @@ fn match_return_type<'a>( | (Type::Signer, _) | (Type::Struct { .. }, _) | (Type::StructInstantiation { .. }, _) + | (Type::Function { .. }, _) | (Type::Vector(_), _) | (Type::MutableReference(_), _) | (Type::Reference(_), _) => false, diff --git a/third_party/move/move-vm/runtime/src/storage/module_storage.rs b/third_party/move/move-vm/runtime/src/storage/module_storage.rs index c96571b046cd1c..4a3e65a57b618c 100644 --- a/third_party/move/move-vm/runtime/src/storage/module_storage.rs +++ b/third_party/move/move-vm/runtime/src/storage/module_storage.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - loader::{Function, Module}, + loader::{Function, LazyLoadedFunction, LazyLoadedFunctionState, Module}, logging::expect_no_verification_errors, - WithRuntimeEnvironment, + LayoutConverter, StorageLayoutConverter, WithRuntimeEnvironment, }; use ambassador::delegatable_trait; use bytes::Bytes; @@ -26,6 +26,7 @@ use move_vm_types::{ loaded_data::runtime_types::{StructType, Type}, module_cyclic_dependency_error, module_linker_error, value_serde::FunctionValueExtension, + values::{AbstractFunction, SerializedFunctionData}, }; use std::sync::Arc; @@ -448,4 +449,58 @@ impl<'a> FunctionValueExtension for FunctionValueExtensionAdapter<'a> { }) .collect::<PartialVMResult<Vec<_>>>() } + + fn create_from_serialization_data( + &self, + data: SerializedFunctionData, + ) -> PartialVMResult<Box<dyn AbstractFunction>> { + Ok(Box::new(LazyLoadedFunction::new_unresolved(data))) + } + + fn get_serialization_data( + &self, + fun: &dyn AbstractFunction, + ) -> PartialVMResult<SerializedFunctionData> { + match &*LazyLoadedFunction::expect_this_impl(fun)?.0.borrow() { + LazyLoadedFunctionState::Unresolved { data, .. } => Ok(data.clone()), + LazyLoadedFunctionState::Resolved { + fun, + mask, + fun_inst, + } => { + let ty_converter = StorageLayoutConverter::new(self.module_storage); + let ty_builder = &self + .module_storage + .runtime_environment() + .vm_config() + .ty_builder; + let instantiate = |ty: &Type| -> PartialVMResult<Type> { + if fun.ty_args.is_empty() { + Ok(ty.clone()) + } else { + ty_builder.create_ty_with_subst(ty, &fun.ty_args) + } + }; + let captured_layouts = mask + .extract(fun.param_tys(), true) + .into_iter() + .map(|t| ty_converter.type_to_type_layout(&instantiate(t)?)) + .collect::<PartialVMResult<Vec<_>>>()?; + Ok(SerializedFunctionData { + module_id: fun + .module_id() + .ok_or_else(|| { + PartialVMError::new_invariant_violation( + "attempt to serialize a script function", + ) + })? + .clone(), + fun_id: fun.function.name.clone(), + fun_inst: fun_inst.clone(), + mask: *mask, + captured_layouts, + }) + }, + } + } } diff --git a/third_party/move/move-vm/runtime/src/storage/ty_layout_converter.rs b/third_party/move/move-vm/runtime/src/storage/ty_layout_converter.rs index 22bdb610c005bd..270368d7fee7ad 100644 --- a/third_party/move/move-vm/runtime/src/storage/ty_layout_converter.rs +++ b/third_party/move/move-vm/runtime/src/storage/ty_layout_converter.rs @@ -12,6 +12,7 @@ use crate::{ }; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ + function::MoveFunctionLayout, language_storage::StructTag, value::{IdentifierMappingKind, MoveFieldLayout, MoveStructLayout, MoveTypeLayout}, vm_status::StatusCode, @@ -151,6 +152,32 @@ pub(crate) trait LayoutConverterBase { *count += 1; self.struct_name_to_type_layout(*idx, ty_args, count, depth + 1)? }, + Type::Function { + args, + results, + abilities, + } => { + let mut identifier_mapping = false; + let mut to_list = |rcs: &[triomphe::Arc<Type>]| { + rcs.iter() + .map(|rc| { + self.type_to_type_layout_impl(rc.as_ref(), count, depth + 1) + .map(|(l, has)| { + identifier_mapping |= has; + l + }) + }) + .collect::<PartialVMResult<Vec<_>>>() + }; + ( + MoveTypeLayout::Function(MoveFunctionLayout( + to_list(args)?, + to_list(results)?, + *abilities, + )), + identifier_mapping, + ) + }, Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => { return Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) @@ -318,6 +345,24 @@ pub(crate) trait LayoutConverterBase { Type::StructInstantiation { idx, ty_args, .. } => { self.struct_name_to_fully_annotated_layout(*idx, ty_args, count, depth + 1)? }, + Type::Function { + args, + results, + abilities, + } => { + let mut to_list = |rcs: &[triomphe::Arc<Type>]| { + rcs.iter() + .map(|rc| { + self.type_to_fully_annotated_layout_impl(rc.as_ref(), count, depth + 1) + }) + .collect::<PartialVMResult<Vec<_>>>() + }; + MoveTypeLayout::Function(MoveFunctionLayout( + to_list(args)?, + to_list(results)?, + *abilities, + )) + }, Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => { return Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) diff --git a/third_party/move/move-vm/runtime/src/storage/ty_tag_converter.rs b/third_party/move/move-vm/runtime/src/storage/ty_tag_converter.rs index b082ccaa63d194..ca0e83fbcc8727 100644 --- a/third_party/move/move-vm/runtime/src/storage/ty_tag_converter.rs +++ b/third_party/move/move-vm/runtime/src/storage/ty_tag_converter.rs @@ -4,7 +4,7 @@ use crate::{loader::PseudoGasContext, RuntimeEnvironment}; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ - language_storage::{StructTag, TypeTag}, + language_storage::{FunctionTag, StructTag, TypeTag}, vm_status::StatusCode, }; use move_vm_types::loaded_data::runtime_types::{StructNameIndex, Type}; @@ -172,6 +172,26 @@ impl<'a> TypeTagConverter<'a> { TypeTag::Struct(Box::new(struct_tag)) }, + // Functions: recurse + Type::Function { + args, + results, + abilities, + } => { + let to_vec = |ts: &[triomphe::Arc<Type>], + gas_ctx: &mut PseudoGasContext| + -> PartialVMResult<Vec<TypeTag>> { + ts.iter() + .map(|t| self.ty_to_ty_tag_impl(t, gas_ctx)) + .collect() + }; + TypeTag::Function(Box::new(FunctionTag { + args: to_vec(args, gas_context)?, + results: to_vec(results, gas_context)?, + abilities: *abilities, + })) + }, + // References and type parameters cannot be converted to tags. Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => { return Err( diff --git a/third_party/move/move-vm/types/Cargo.toml b/third_party/move/move-vm/types/Cargo.toml index 858a924fc16412..cab1e94eaa89f0 100644 --- a/third_party/move/move-vm/types/Cargo.toml +++ b/third_party/move/move-vm/types/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" [dependencies] ambassador = { workspace = true } bcs = { workspace = true } +better_any = { workspace = true } bytes = { workspace = true } crossbeam = { workspace = true } dashmap = { workspace = true } diff --git a/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs b/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs index 60a648056d999e..2cb4ae958d06f5 100644 --- a/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs +++ b/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs @@ -17,7 +17,7 @@ use move_core_types::account_address::AccountAddress; use move_core_types::{ ability::{Ability, AbilitySet}, identifier::Identifier, - language_storage::{ModuleId, StructTag, TypeTag}, + language_storage::{FunctionTag, ModuleId, StructTag, TypeTag}, vm_status::{sub_status::unknown_invariant_violation::EPARANOID_FAILURE, StatusCode}, }; use serde::Serialize; @@ -26,7 +26,7 @@ use smallvec::{smallvec, SmallVec}; use std::{ cell::RefCell, cmp::max, - collections::{btree_map, BTreeMap}, + collections::{btree_map, BTreeMap, BTreeSet}, fmt, fmt::Debug, sync::Arc, @@ -287,6 +287,11 @@ pub enum Type { ty_args: TriompheArc<Vec<Type>>, ability: AbilityInfo, }, + Function { + args: Vec<TriompheArc<Type>>, + results: Vec<TriompheArc<Type>>, + abilities: AbilitySet, + }, Reference(Box<Type>), MutableReference(Box<Type>), TyParam(u16), @@ -329,6 +334,11 @@ impl<'a> Iterator for TypePreorderTraversalIter<'a> { }, StructInstantiation { ty_args, .. } => self.stack.extend(ty_args.iter().rev()), + + Function { args, results, .. } => { + self.stack.extend(args.iter().map(|rc| rc.as_ref())); + self.stack.extend(results.iter().map(|rc| rc.as_ref())) + }, } Some(ty) }, @@ -651,6 +661,7 @@ impl Type { .with_message(e.to_string()) }) }, + Type::Function { abilities, .. } => Ok(*abilities), } } @@ -717,13 +728,109 @@ impl Type { | Struct { .. } | Reference(..) | MutableReference(..) - | StructInstantiation { .. } => n += 1, + | StructInstantiation { .. } + | Function { .. } => n += 1, } } Ok(n) }) } + + /// Term unification of two types where the type parameters are considered + /// as variables. Uses and extends as needed the passed type parameter + /// substitution. Returns true if unification succeeds. + pub fn unify(&self, other: &Type, subs: &mut BTreeMap<u16, Type>) -> bool { + self.unify_impl(other, subs, &mut BTreeSet::new()).is_some() + } + + fn unify_impl( + &self, + other: &Type, + subs: &mut BTreeMap<u16, Type>, + visiting: &mut BTreeSet<u16>, + ) -> Option<()> { + use Type::*; + match (self, other) { + (TyParam(idx), ty) | (ty, TyParam(idx)) => { + if let Some(s) = subs.get(idx) { + if !visiting.insert(*idx) { + // Cyclic substitution + None + } else { + s.clone().unify_impl(ty, subs, visiting)?; + visiting.remove(idx); + Some(()) + } + } else { + subs.insert(*idx, ty.clone()); + Some(()) + } + }, + (Reference(ty1), Reference(ty2)) | (MutableReference(ty1), MutableReference(ty2)) => { + ty1.unify_impl(ty2.as_ref(), subs, visiting) + }, + ( + StructInstantiation { idx, ty_args, .. }, + StructInstantiation { + idx: idx2, + ty_args: ty_args2, + .. + }, + ) if idx == idx2 && ty_args.len() == ty_args2.len() => { + for (ty1, ty2) in ty_args.iter().zip(ty_args2.as_ref()) { + ty1.unify_impl(ty2, subs, visiting)? + } + Some(()) + }, + (Vector(ty1), Vector(ty2)) => ty1.unify_impl(ty2.as_ref(), subs, visiting), + ( + Function { + args, + results, + abilities, + }, + Function { + args: args2, + results: results2, + abilities: abilities2, + }, + ) if abilities == abilities2 + && args.len() == args2.len() + && results.len() == results2.len() => + { + for (ty1, ty2) in args.iter().zip(args2).chain(results.iter().zip(results2)) { + ty1.unify_impl(ty2.as_ref(), subs, visiting)? + } + Some(()) + }, + // The remaining combinations with recursive types can't match + ( + Vector(..) + | StructInstantiation { .. } + | Function { .. } + | Reference(..) + | MutableReference(..), + _, + ) + | ( + _, + Vector(..) + | StructInstantiation { .. } + | Function { .. } + | Reference(..) + | MutableReference(..), + ) => None, + // Everything else matched by non-recursive term equality + (ty1, ty2) => { + if ty1 == ty2 { + Some(()) + } else { + None + } + }, + } + } } impl fmt::Display for StructIdentifier { @@ -762,6 +869,17 @@ impl fmt::Display for Type { idx.0, ty_args.iter().map(|t| t.to_string()).join(",") ), + Function { + args, + results, + abilities, + } => write!( + f, + "|{}|{}{}", + args.iter().map(|t| t.to_string()).join(","), + results.iter().map(|t| t.to_string()).join(","), + abilities.display_postfix() + ), Reference(t) => write!(f, "&{}", t), MutableReference(t) => write!(f, "&mut {}", t), TyParam(no) => write!(f, "_{}", no), @@ -1141,6 +1259,36 @@ impl TypeBuilder { ability: ability.clone(), } }, + Function { + args, + results, + abilities, + } => { + let subs_elem = |count: &mut u64, + ty: &TriompheArc<Type>| + -> PartialVMResult<TriompheArc<Type>> { + Ok(TriompheArc::new(Self::apply_subst( + ty.as_ref(), + subst, + count, + depth + 1, + check, + )?)) + }; + let args = args + .iter() + .map(|ty| subs_elem(count, ty)) + .collect::<PartialVMResult<Vec<_>>>()?; + let results = results + .iter() + .map(|ty| subs_elem(count, ty)) + .collect::<PartialVMResult<Vec<_>>>()?; + Function { + args, + results, + abilities: *abilities, + } + }, }) } @@ -1200,6 +1348,26 @@ impl TypeBuilder { } } }, + T::Function(fun) => { + let FunctionTag { + args, + results, + abilities, + } = fun.as_ref(); + let mut to_list = |ts: &[TypeTag]| { + ts.iter() + .map(|t| { + self.create_ty_impl(t, resolver, count, depth + 1) + .map(TriompheArc::new) + }) + .collect::<VMResult<Vec<_>>>() + }; + Function { + args: to_list(args)?, + results: to_list(results)?, + abilities: *abilities, + } + }, }) } diff --git a/third_party/move/move-vm/types/src/value_serde.rs b/third_party/move/move-vm/types/src/value_serde.rs index dd9c22140ad26d..ac01bf69b79cd2 100644 --- a/third_party/move/move-vm/types/src/value_serde.rs +++ b/third_party/move/move-vm/types/src/value_serde.rs @@ -4,7 +4,10 @@ use crate::{ delayed_values::delayed_field_id::DelayedFieldID, loaded_data::runtime_types::Type, - values::{DeserializationSeed, SerializationReadyValue, Value}, + values::{ + AbstractFunction, DeserializationSeed, SerializationReadyValue, SerializedFunctionData, + Value, + }, }; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ @@ -15,7 +18,7 @@ use move_core_types::{ }; use std::cell::RefCell; -/// An extension to (de)serializer to lookup information about function values. +/// An extension to (de)serialize information about function values. pub trait FunctionValueExtension { /// Given the module's id and the function name, returns the parameter types of the /// corresponding function, instantiated with the provided set of type tags. @@ -25,6 +28,18 @@ pub trait FunctionValueExtension { function_name: &IdentStr, ty_arg_tags: Vec<TypeTag>, ) -> PartialVMResult<Vec<Type>>; + + /// Create an implementation of an `AbstractFunction` from the serialization data. + fn create_from_serialization_data( + &self, + data: SerializedFunctionData, + ) -> PartialVMResult<Box<dyn AbstractFunction>>; + + /// Get serialization data from an `AbstractFunction`. + fn get_serialization_data( + &self, + fun: &dyn AbstractFunction, + ) -> PartialVMResult<SerializedFunctionData>; } /// An extension to (de)serializer to lookup information about delayed fields. @@ -92,6 +107,14 @@ impl<'a> ValueSerDeContext<'a> { self } + pub fn required_function_extension(&self) -> PartialVMResult<&dyn FunctionValueExtension> { + self.function_extension.ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message( + "require function extension context for serialization of closures".to_string(), + ) + }) + } + /// Returns the same extension but without allowing the delayed fields. pub(crate) fn clone_without_delayed_fields(&self) -> Self { Self { diff --git a/third_party/move/move-vm/types/src/value_traversal.rs b/third_party/move/move-vm/types/src/value_traversal.rs index 54cfac487e5bc7..2fd6edc01f5280 100644 --- a/third_party/move/move-vm/types/src/value_traversal.rs +++ b/third_party/move/move-vm/types/src/value_traversal.rs @@ -3,7 +3,7 @@ use crate::{ delayed_values::error::code_invariant_error, - values::{Container, Value, ValueImpl}, + values::{Closure, Container, Value, ValueImpl}, }; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::vm_status::StatusCode; @@ -56,6 +56,12 @@ fn find_identifiers_in_value_impl( }, }, + ValueImpl::ClosureValue(Closure(_, captured)) => { + for val in captured.iter() { + find_identifiers_in_value_impl(val, identifiers)?; + } + }, + ValueImpl::Invalid | ValueImpl::ContainerRef(_) | ValueImpl::IndexedRef(_) => { return Err(PartialVMError::new( StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, diff --git a/third_party/move/move-vm/types/src/values/function_values_impl.rs b/third_party/move/move-vm/types/src/values/function_values_impl.rs new file mode 100644 index 00000000000000..6f7b9214693479 --- /dev/null +++ b/third_party/move/move-vm/types/src/values/function_values_impl.rs @@ -0,0 +1,208 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::values::{DeserializationSeed, SerializationReadyValue, VMValueCast, Value, ValueImpl}; +use better_any::Tid; +use move_binary_format::errors::{PartialVMError, PartialVMResult}; +use move_core_types::{ + function::{ClosureMask, MoveFunctionLayout}, + identifier::Identifier, + language_storage::{ModuleId, TypeTag}, + value::MoveTypeLayout, + vm_status::StatusCode, +}; +use serde::{ + de::Error as DeError, + ser::{Error, SerializeSeq}, + Deserialize, Serialize, +}; +use std::{ + cmp::Ordering, + fmt, + fmt::{Debug, Display, Formatter}, +}; + +/// A trait describing a function which can be executed. If this is a generic +/// function, the type instantiation is part of this. +/// The value system is agnostic about how this is implemented in the runtime. +/// The `FunctionValueExtension` trait describes how to construct and +/// deconstruct instances for serialization. +pub trait AbstractFunction: for<'a> Tid<'a> { + fn closure_mask(&self) -> ClosureMask; + fn cmp_dyn(&self, other: &dyn AbstractFunction) -> PartialVMResult<Ordering>; + fn clone_dyn(&self) -> PartialVMResult<Box<dyn AbstractFunction>>; + fn to_stable_string(&self) -> String; +} + +/// A closure, consisting of an abstract function descriptor and the captured arguments. +pub struct Closure( + pub(crate) Box<dyn AbstractFunction>, + pub(crate) Vec<ValueImpl>, +); + +/// The representation of a function in storage. +#[derive(Serialize, Deserialize, Clone)] +pub struct SerializedFunctionData { + pub module_id: ModuleId, + pub fun_id: Identifier, + pub fun_inst: Vec<TypeTag>, + pub mask: ClosureMask, + /// The layouts used for deserialization of the captured arguments + /// are stored so one can verify type consistency at + /// resolution time. It also allows to serialize an unresolved + /// closure, making unused closure data cheap in round trips. + pub captured_layouts: Vec<MoveTypeLayout>, +} + +impl Closure { + pub fn pack(fun: Box<dyn AbstractFunction>, captured: impl IntoIterator<Item = Value>) -> Self { + Self(fun, captured.into_iter().map(|v| v.0).collect()) + } + + pub fn unpack(self) -> (Box<dyn AbstractFunction>, impl Iterator<Item = Value>) { + let Self(fun, captured) = self; + (fun, captured.into_iter().map(Value)) + } + + pub fn into_call_data( + self, + args: Vec<Value>, + ) -> PartialVMResult<(Box<dyn AbstractFunction>, Vec<Value>)> { + let (fun, captured) = self.unpack(); + if let Some(all_args) = fun.closure_mask().compose(captured, args) { + Ok((fun, all_args)) + } else { + Err( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message("invalid closure mask".to_string()), + ) + } + } +} + +impl Debug for Closure { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self(fun, captured) = self; + write!(f, "Closure({}, {:?})", fun.to_stable_string(), captured) + } +} + +impl Display for Closure { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self(fun, captured) = self; + let captured = fun + .closure_mask() + .merge_placeholder_strings( + captured.len(), + captured.iter().map(|v| v.to_string()).collect(), + ) + .unwrap_or_else(|| vec!["*invalid*".to_string()]); + write!(f, "{}({})", fun.to_stable_string(), captured.join(",")) + } +} + +impl VMValueCast<Closure> for Value { + fn cast(self) -> PartialVMResult<Closure> { + match self.0 { + ValueImpl::ClosureValue(c) => Ok(c), + v => Err(PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR) + .with_message(format!("cannot cast {:?} to closure", v))), + } + } +} + +impl<'c, 'l, 'v> serde::Serialize + for SerializationReadyValue<'c, 'l, 'v, MoveFunctionLayout, Closure> +{ + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + let Closure(fun, captured) = self.value; + let fun_ext = self + .ctx + .required_function_extension() + .map_err(S::Error::custom)?; + let data = fun_ext + .get_serialization_data(fun.as_ref()) + .map_err(S::Error::custom)?; + let mut seq = serializer.serialize_seq(Some(4 + captured.len()))?; + seq.serialize_element(&data.module_id)?; + seq.serialize_element(&data.fun_id)?; + seq.serialize_element(&data.fun_inst)?; + seq.serialize_element(&data.mask)?; + for (layout, value) in data.captured_layouts.into_iter().zip(captured) { + seq.serialize_element(&layout)?; + seq.serialize_element(&SerializationReadyValue { + ctx: self.ctx, + layout: &layout, + value, + })? + } + seq.end() + } +} + +pub(crate) struct ClosureVisitor<'c, 'l>( + pub(crate) DeserializationSeed<'c, &'l MoveFunctionLayout>, +); + +impl<'d, 'c, 'l> serde::de::Visitor<'d> for ClosureVisitor<'c, 'l> { + type Value = Closure; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("Closure") + } + + fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> + where + A: serde::de::SeqAccess<'d>, + { + let fun_ext = self + .0 + .ctx + .required_function_extension() + .map_err(A::Error::custom)?; + let module_id = read_required_value::<_, ModuleId>(&mut seq)?; + let fun_id = read_required_value::<_, Identifier>(&mut seq)?; + let fun_inst = read_required_value::<_, Vec<TypeTag>>(&mut seq)?; + let mask = read_required_value::<_, ClosureMask>(&mut seq)?; + let mut captured_layouts = vec![]; + let mut captured = vec![]; + for _ in 0..mask.captured_count() { + let layout = read_required_value::<_, MoveTypeLayout>(&mut seq)?; + match seq.next_element_seed(DeserializationSeed { + ctx: self.0.ctx, + layout: &layout, + })? { + Some(v) => { + captured_layouts.push(layout); + captured.push(v.0) + }, + None => return Err(A::Error::invalid_length(captured.len(), &self)), + } + } + // If the sequence length is known, check whether there are no extra values + if matches!(seq.size_hint(), Some(remaining) if remaining != 0) { + return Err(A::Error::invalid_length(captured.len(), &self)); + } + let fun = fun_ext + .create_from_serialization_data(SerializedFunctionData { + module_id, + fun_id, + fun_inst, + mask, + captured_layouts, + }) + .map_err(A::Error::custom)?; + Ok(Closure(fun, captured)) + } +} + +fn read_required_value<'a, A, T>(seq: &mut A) -> Result<T, A::Error> +where + A: serde::de::SeqAccess<'a>, + T: serde::de::Deserialize<'a>, +{ + match seq.next_element::<T>()? { + Some(x) => Ok(x), + None => Err(A::Error::custom("expected more elements")), + } +} diff --git a/third_party/move/move-vm/types/src/values/mod.rs b/third_party/move/move-vm/types/src/values/mod.rs index 4796eb7fbd9472..31b773cffd3da9 100644 --- a/third_party/move/move-vm/types/src/values/mod.rs +++ b/third_party/move/move-vm/types/src/values/mod.rs @@ -2,6 +2,7 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 +pub mod function_values_impl; pub mod values_impl; #[cfg(test)] @@ -12,4 +13,5 @@ mod serialization_tests; #[cfg(all(test, feature = "fuzzing"))] mod value_prop_tests; +pub use function_values_impl::*; pub use values_impl::*; diff --git a/third_party/move/move-vm/types/src/values/values_impl.rs b/third_party/move/move-vm/types/src/values/values_impl.rs index 544ca10d2ac43a..7847c08efb6c5c 100644 --- a/third_party/move/move-vm/types/src/values/values_impl.rs +++ b/third_party/move/move-vm/types/src/values/values_impl.rs @@ -8,6 +8,7 @@ use crate::{ delayed_values::delayed_field_id::{DelayedFieldID, TryFromMoveValue, TryIntoMoveValue}, loaded_data::runtime_types::Type, value_serde::ValueSerDeContext, + values::function_values_impl::{AbstractFunction, Closure, ClosureVisitor}, views::{ValueView, ValueVisitor}, }; use itertools::Itertools; @@ -93,6 +94,11 @@ pub(crate) enum ValueImpl { DelayedFieldID { id: DelayedFieldID, }, + + /// A closure, consisting of a function reference and captured arguments. + /// Notice that captured arguments cannot be referenced, hence a closure is + /// not a container. + ClosureValue(Closure), } /// A container is a collection of values. It is used to represent data structures like a @@ -414,6 +420,14 @@ impl ValueImpl { // Native values can be copied because this is how read_ref operates, // and copying is an internal API. DelayedFieldID { id } => DelayedFieldID { id: *id }, + + ClosureValue(Closure(fun, captured)) => { + let captured = captured + .iter() + .map(|v| v.copy_value()) + .collect::<PartialVMResult<_>>()?; + ClosureValue(Closure(fun.clone_dyn()?, captured)) + }, }) } } @@ -537,6 +551,21 @@ impl ValueImpl { .with_message("cannot compare delayed values".to_string())) }, + (ClosureValue(Closure(fun1, captured1)), ClosureValue(Closure(fun2, captured2))) => { + if fun1.cmp_dyn(fun2.as_ref())? == Ordering::Equal + && captured1.len() == captured2.len() + { + for (v1, v2) in captured1.iter().zip(captured2.iter()) { + if !v1.equals(v2)? { + return Ok(false); + } + } + true + } else { + false + } + }, + (Invalid, _) | (U8(_), _) | (U16(_), _) @@ -549,6 +578,7 @@ impl ValueImpl { | (Container(_), _) | (ContainerRef(_), _) | (IndexedRef(_), _) + | (ClosureValue(_), _) | (DelayedFieldID { .. }, _) => { return Err( PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR).with_message(format!( @@ -587,6 +617,21 @@ impl ValueImpl { .with_message("cannot compare delayed values".to_string())) }, + (ClosureValue(Closure(fun1, captured1)), ClosureValue(Closure(fun2, captured2))) => { + let o = fun1.cmp_dyn(fun2.as_ref())?; + if o == Ordering::Equal { + for (v1, v2) in captured1.iter().zip(captured2.iter()) { + let o = v1.compare(v2)?; + if o != Ordering::Equal { + return Ok(o); + } + } + captured1.iter().len().cmp(&captured2.len()) + } else { + o + } + }, + (Invalid, _) | (U8(_), _) | (U16(_), _) @@ -599,6 +644,7 @@ impl ValueImpl { | (Container(_), _) | (ContainerRef(_), _) | (IndexedRef(_), _) + | (ClosureValue(_), _) | (DelayedFieldID { .. }, _) => { return Err( PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR).with_message(format!( @@ -1410,6 +1456,7 @@ impl ContainerRef { | ValueImpl::U256(_) | ValueImpl::Bool(_) | ValueImpl::Address(_) + | ValueImpl::ClosureValue(_) | ValueImpl::DelayedFieldID { .. } => ValueImpl::IndexedRef(IndexedRef { idx, container_ref: self.copy_value(), @@ -1504,6 +1551,7 @@ impl Locals { | ValueImpl::U256(_) | ValueImpl::Bool(_) | ValueImpl::Address(_) + | ValueImpl::ClosureValue(_) | ValueImpl::DelayedFieldID { .. } => Ok(Value(ValueImpl::IndexedRef(IndexedRef { idx, container_ref: ContainerRef::Local(Container::Locals(Rc::clone(&self.0))), @@ -1595,8 +1643,8 @@ impl Locals { return Err(PartialVMError::new( StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, ) - .with_message("moving container with dangling references".to_string()) - .with_sub_status(move_core_types::vm_status::sub_status::unknown_invariant_violation::EREFERENCE_COUNTING_FAILURE)); + .with_message("moving container with dangling references".to_string()) + .with_sub_status(move_core_types::vm_status::sub_status::unknown_invariant_violation::EREFERENCE_COUNTING_FAILURE)); } } } @@ -1789,6 +1837,13 @@ impl Value { it.into_iter().map(|v| v.0).collect(), ))))) } + + pub fn closure( + fun: Box<dyn AbstractFunction>, + captured: impl IntoIterator<Item = Value>, + ) -> Self { + Self(ValueImpl::ClosureValue(Closure::pack(fun, captured))) + } } /*************************************************************************************** @@ -2537,6 +2592,8 @@ pub const INDEX_OUT_OF_BOUNDS: u64 = NFE_VECTOR_ERROR_BASE + 1; pub const POP_EMPTY_VEC: u64 = NFE_VECTOR_ERROR_BASE + 2; pub const VEC_UNPACK_PARITY_MISMATCH: u64 = NFE_VECTOR_ERROR_BASE + 3; +// TODO: this check seems to be obsolete if paranoid mode is on, +// and should either be removed or move over to runtime_type_checks? fn check_elem_layout(ty: &Type, v: &Container) -> PartialVMResult<()> { match (ty, v) { (Type::U8, Container::VecU8(_)) @@ -2553,7 +2610,8 @@ fn check_elem_layout(ty: &Type, v: &Container) -> PartialVMResult<()> { (Type::Struct { .. }, Container::Vec(_)) | (Type::Signer, Container::Vec(_)) - | (Type::StructInstantiation { .. }, Container::Vec(_)) => Ok(()), + | (Type::StructInstantiation { .. }, Container::Vec(_)) + | (Type::Function { .. }, Container::Vec(_)) => Ok(()), (Type::Reference(_), _) | (Type::MutableReference(_), _) | (Type::TyParam(_), _) => Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) @@ -2571,7 +2629,8 @@ fn check_elem_layout(ty: &Type, v: &Container) -> PartialVMResult<()> { | (Type::Signer, _) | (Type::Vector(_), _) | (Type::Struct { .. }, _) - | (Type::StructInstantiation { .. }, _) => Err(PartialVMError::new( + | (Type::StructInstantiation { .. }, _) + | (Type::Function { .. }, _) => Err(PartialVMError::new( StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, ) .with_message(format!( @@ -2860,11 +2919,10 @@ impl Vector { Type::Signer | Type::Vector(_) | Type::Struct { .. } - | Type::StructInstantiation { - idx: _, ty_args: _, .. - } => Value(ValueImpl::Container(Container::Vec(Rc::new(RefCell::new( - elements.into_iter().map(|v| v.0).collect(), - ))))), + | Type::StructInstantiation { .. } + | Type::Function { .. } => Value(ValueImpl::Container(Container::Vec(Rc::new( + RefCell::new(elements.into_iter().map(|v| v.0).collect()), + )))), Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => { return Err( @@ -2971,6 +3029,9 @@ pub(crate) const LEGACY_REFERENCE_SIZE: AbstractMemorySize = AbstractMemorySize: /// The size of a struct in bytes pub(crate) const LEGACY_STRUCT_SIZE: AbstractMemorySize = AbstractMemorySize::new(2); +/// The size of a closure in bytes +pub(crate) const LEGACY_CLOSURE_SIZE: AbstractMemorySize = AbstractMemorySize::new(6); + impl Container { #[cfg(test)] fn legacy_size(&self) -> AbstractMemorySize { @@ -3038,6 +3099,12 @@ impl ValueImpl { // Legacy size is only used by event native functions (which should not even // be part of move-stdlib), so we should never see any delayed values here. DelayedFieldID { .. } => unreachable!("Delayed values do not have legacy size!"), + + ClosureValue(..) => { + // TODO(#15664): similarly as with delayed values, closures should not appear here, + // but this needs to be verified + unreachable!("Closures do not have legacy size!") + }, } } } @@ -3341,6 +3408,8 @@ impl Debug for ValueImpl { Self::ContainerRef(r) => write!(f, "ContainerRef({:?})", r), Self::IndexedRef(r) => write!(f, "IndexedRef({:?})", r), + Self::ClosureValue(c) => write!(f, "Function({:?})", c), + // Debug information must be deterministic, so we cannot print // inner fields. Self::DelayedFieldID { .. } => write!(f, "Delayed(?)"), @@ -3376,6 +3445,8 @@ impl Display for ValueImpl { Self::ContainerRef(r) => write!(f, "{}", r), Self::IndexedRef(r) => write!(f, "{}", r), + Self::ClosureValue(c) => write!(f, "{}", c), + // Display information must be deterministic, so we cannot print // inner fields. Self::DelayedFieldID { .. } => write!(f, "Delayed(?)"), @@ -3534,6 +3605,10 @@ pub mod debug { debug_write!(buf, "{}", x.to_hex()) } + fn print_closure<B: Write>(buf: &mut B, c: &Closure) -> PartialVMResult<()> { + debug_write!(buf, "{}", c) + } + fn print_value_impl<B: Write>(buf: &mut B, val: &ValueImpl) -> PartialVMResult<()> { match val { ValueImpl::Invalid => print_invalid(buf), @@ -3552,6 +3627,8 @@ pub mod debug { ValueImpl::ContainerRef(r) => print_container_ref(buf, r), ValueImpl::IndexedRef(r) => print_indexed_ref(buf, r), + ValueImpl::ClosureValue(c) => print_closure(buf, c), + ValueImpl::DelayedFieldID { .. } => print_delayed_value(buf), } } @@ -3722,6 +3799,14 @@ impl<'c, 'l, 'v> serde::Serialize .serialize(serializer) }, + // Functions. + (L::Function(fun_layout), ValueImpl::ClosureValue(clos)) => SerializationReadyValue { + ctx: self.ctx, + layout: fun_layout, + value: clos, + } + .serialize(serializer), + // Vectors. (L::Vector(layout), ValueImpl::Container(c)) => { let layout = layout.as_ref(); @@ -3986,6 +4071,16 @@ impl<'d, 'c> serde::de::DeserializeSeed<'d> for DeserializationSeed<'c, &MoveTyp }, }), + // Functions + L::Function(fun_layout) => { + let seed = DeserializationSeed { + ctx: self.ctx, + layout: fun_layout, + }; + let closure = deserializer.deserialize_seq(ClosureVisitor(seed))?; + Ok(Value(ValueImpl::ClosureValue(closure))) + }, + // Delayed values should always use custom deserialization. L::Native(kind, layout) => { match &self.ctx.delayed_fields_extension { @@ -4008,9 +4103,9 @@ impl<'d, 'c> serde::de::DeserializeSeed<'d> for DeserializationSeed<'c, &MoveTyp DelayedFieldID::try_from_move_value(layout, value, &()) .map_err(|_| { D::Error::custom(format!( - "Custom deserialization failed for {:?} with layout {}", - kind, layout - )) + "Custom deserialization failed for {:?} with layout {}", + kind, layout + )) })?; id }, @@ -4318,6 +4413,17 @@ impl Container { } } +impl Closure { + fn visit_impl(&self, visitor: &mut impl ValueVisitor, depth: usize) { + let Self(_, captured) = self; + if visitor.visit_closure(depth, captured.len()) { + for val in captured { + val.visit_impl(visitor, depth + 1); + } + } + } +} + impl ContainerRef { fn visit_impl(&self, visitor: &mut impl ValueVisitor, depth: usize) { use ContainerRef::*; @@ -4369,6 +4475,8 @@ impl ValueImpl { ContainerRef(r) => r.visit_impl(visitor, depth), IndexedRef(r) => r.visit_impl(visitor, depth), + ClosureValue(c) => c.visit_impl(visitor, depth), + DelayedFieldID { id } => visitor.visit_delayed(depth, *id), } } @@ -4631,6 +4739,14 @@ pub mod prop { .prop_map(move |vals| Value::struct_(Struct::pack(vals))) .boxed(), + L::Function(_function_layout) => { + // TODO(#15664): not clear how to generate closure values, we'd need + // some test functions for this, and generate `AbstractFunction` impls. + // As we do not generate function layouts in the first place, we can bail + // out here + unreachable!("unexpected function layout") + }, + // TODO[agg_v2](cleanup): double check what we should do here (i.e. if we should // even skip these kinds of layouts, or if need to construct a delayed value)? L::Native(_, layout) => value_strategy_with_layout(layout.as_ref()), diff --git a/third_party/move/move-vm/types/src/views.rs b/third_party/move/move-vm/types/src/views.rs index 3c11766c55841a..120e712adc89fc 100644 --- a/third_party/move/move-vm/types/src/views.rs +++ b/third_party/move/move-vm/types/src/views.rs @@ -1,7 +1,7 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use crate::delayed_values::delayed_field_id::DelayedFieldID; +use crate::{delayed_values::delayed_field_id::DelayedFieldID, values::LEGACY_CLOSURE_SIZE}; use move_core_types::{ account_address::AccountAddress, gas_algebra::AbstractMemorySize, language_storage::TypeTag, }; @@ -79,6 +79,11 @@ pub trait ValueView { true } + fn visit_closure(&mut self, _depth: usize, _len: usize) -> bool { + self.0 += LEGACY_CLOSURE_SIZE; + true + } + fn visit_vec(&mut self, _depth: usize, _len: usize) -> bool { self.0 += LEGACY_STRUCT_SIZE; true @@ -142,6 +147,7 @@ pub trait ValueVisitor { fn visit_address(&mut self, depth: usize, val: AccountAddress); fn visit_struct(&mut self, depth: usize, len: usize) -> bool; + fn visit_closure(&mut self, depth: usize, len: usize) -> bool; fn visit_vec(&mut self, depth: usize, len: usize) -> bool; fn visit_ref(&mut self, depth: usize, is_global: bool) -> bool; diff --git a/third_party/move/tools/move-bytecode-utils/src/layout.rs b/third_party/move/tools/move-bytecode-utils/src/layout.rs index 46863644cdc55b..00235b3b32b32a 100644 --- a/third_party/move/tools/move-bytecode-utils/src/layout.rs +++ b/third_party/move/tools/move-bytecode-utils/src/layout.rs @@ -14,6 +14,7 @@ use move_binary_format::{ }; use move_core_types::{ account_address::AccountAddress, + function::MoveFunctionLayout, identifier::{IdentStr, Identifier}, language_storage::{ModuleId, StructTag, TypeTag}, value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout}, @@ -374,6 +375,18 @@ impl TypeLayoutBuilder { compiled_module_view, layout_type, )?), + Function(f) => { + let build_list = |ts: &[TypeTag]| { + ts.iter() + .map(|t| Self::build(t, compiled_module_view, layout_type)) + .collect::<anyhow::Result<Vec<_>>>() + }; + MoveTypeLayout::Function(MoveFunctionLayout( + build_list(&f.args)?, + build_list(&f.results)?, + f.abilities, + )) + }, }) } diff --git a/third_party/move/tools/move-resource-viewer/src/fat_type.rs b/third_party/move/tools/move-resource-viewer/src/fat_type.rs index d4f8097a4f1789..0d9f75889d44b1 100644 --- a/third_party/move/tools/move-resource-viewer/src/fat_type.rs +++ b/third_party/move/tools/move-resource-viewer/src/fat_type.rs @@ -275,6 +275,10 @@ impl From<&TypeTag> for FatType { TypeTag::Vector(inner) => Vector(Box::new(inner.as_ref().into())), TypeTag::Struct(inner) => Struct(Box::new(inner.as_ref().into())), TypeTag::U256 => U256, + TypeTag::Function(..) => { + // TODO(#15664): implement functions for fat types + todo!("functions not supported by fat types") + }, } } } diff --git a/third_party/move/tools/move-resource-viewer/src/lib.rs b/third_party/move/tools/move-resource-viewer/src/lib.rs index 9142608d9ef68e..4eb0d33ec4a139 100644 --- a/third_party/move/tools/move-resource-viewer/src/lib.rs +++ b/third_party/move/tools/move-resource-viewer/src/lib.rs @@ -442,6 +442,10 @@ impl<V: CompiledModuleView> MoveValueAnnotator<V> { TypeTag::U256 => FatType::U256, TypeTag::U128 => FatType::U128, TypeTag::Vector(ty) => FatType::Vector(Box::new(self.resolve_type_impl(ty, limit)?)), + TypeTag::Function(..) => { + // TODO(#15664) implement functions for fat types" + todo!("functions for fat types") + }, }) } @@ -583,6 +587,10 @@ impl<V: CompiledModuleView> MoveValueAnnotator<V> { (MoveValue::Struct(s), FatType::Struct(ty)) => { AnnotatedMoveValue::Struct(self.annotate_struct(s, ty.as_ref(), limit)?) }, + (MoveValue::Closure(..), _) => { + // TODO(#15664) implement functions for annotated move values + todo!("functions not implemented") + }, (MoveValue::U8(_), _) | (MoveValue::U64(_), _) | (MoveValue::U128(_), _)