diff --git a/language/move-binary-format/serializer-tests/tests/serializer_tests.rs b/language/move-binary-format/serializer-tests/tests/serializer_tests.rs index bacd57b72a..1374249d39 100644 --- a/language/move-binary-format/serializer-tests/tests/serializer_tests.rs +++ b/language/move-binary-format/serializer-tests/tests/serializer_tests.rs @@ -2,7 +2,7 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use move_binary_format::file_format::CompiledModule; +use move_binary_format::file_format::*; use proptest::prelude::*; proptest! { @@ -34,3 +34,27 @@ proptest! { prop_assert_eq!(module, deserialized_module); } } + +#[test] +fn single_fp_test() { + let mut module = empty_module(); + + module + .signatures + .push(Signature(vec![SignatureToken::Function(Box::new( + FunctionType { + parameters: vec![SignatureToken::U8], + return_: vec![SignatureToken::U128], + }, + ))])); + + let mut serialized = Vec::with_capacity(65536); + module + .serialize(&mut serialized) + .expect("serialization should work"); + + let deserialized_module = CompiledModule::deserialize_no_check_bounds(&serialized) + .expect("deserialization should work"); + + assert_eq!(module, deserialized_module); +} diff --git a/language/move-binary-format/src/binary_views.rs b/language/move-binary-format/src/binary_views.rs index 43a002dc6f..039c9f67e5 100644 --- a/language/move-binary-format/src/binary_views.rs +++ b/language/move-binary-format/src/binary_views.rs @@ -10,10 +10,10 @@ use crate::{ AbilitySet, AddressIdentifierIndex, CodeUnit, CompiledScript, Constant, ConstantPoolIndex, FieldHandle, FieldHandleIndex, FieldInstantiation, FieldInstantiationIndex, FunctionDefinition, FunctionDefinitionIndex, FunctionHandle, FunctionHandleIndex, - FunctionInstantiation, FunctionInstantiationIndex, IdentifierIndex, ModuleHandle, - ModuleHandleIndex, Signature, SignatureIndex, SignatureToken, StructDefInstantiation, - StructDefInstantiationIndex, StructDefinition, StructDefinitionIndex, StructHandle, - StructHandleIndex, + FunctionInstantiation, FunctionInstantiationIndex, FunctionType, IdentifierIndex, + ModuleHandle, ModuleHandleIndex, Signature, SignatureIndex, SignatureToken, + StructDefInstantiation, StructDefInstantiationIndex, StructDefinition, + StructDefinitionIndex, StructHandle, StructHandleIndex, }, CompiledModule, }; @@ -261,6 +261,7 @@ impl<'a> BinaryIndexedView<'a> { Reference(_) | MutableReference(_) => Ok(AbilitySet::REFERENCES), Signer => Ok(AbilitySet::SIGNER), + Function(_) => Ok(AbilitySet::FUNCTION), TypeParameter(idx) => Ok(constraints[*idx as usize]), Vector(ty) => AbilitySet::polymorphic_abilities( AbilitySet::VECTOR, @@ -314,6 +315,16 @@ impl<'a> BinaryIndexedView<'a> { BinaryIndexedView::Script(script) => script.version(), } } + + pub fn function_type_from_handle(&self, fh_idx: FunctionHandleIndex) -> FunctionType { + let fh = self.function_handle_at(fh_idx); + let parameters = self.signature_at(fh.parameters).0.clone(); + let return_ = self.signature_at(fh.return_).0.clone(); + FunctionType { + parameters, + return_, + } + } } const EMPTY_SIGNATURE: &Signature = &Signature(vec![]); diff --git a/language/move-binary-format/src/check_bounds.rs b/language/move-binary-format/src/check_bounds.rs index c60fa080c5..03f2e3c88c 100644 --- a/language/move-binary-format/src/check_bounds.rs +++ b/language/move-binary-format/src/check_bounds.rs @@ -415,12 +415,12 @@ impl<'a> BoundsChecker<'a> { } } } - Call(idx) => self.check_code_unit_bounds_impl( + Call(idx) | GetFunctionPointer(idx) => self.check_code_unit_bounds_impl( self.view.function_handles(), *idx, bytecode_offset, )?, - CallGeneric(idx) => { + CallGeneric(idx) | GetFunctionPointerGeneric(idx) => { self.check_code_unit_bounds_impl( self.view.function_instantiations(), *idx, @@ -513,7 +513,8 @@ impl<'a> BoundsChecker<'a> { | VecPushBack(idx) | VecPopBack(idx) | VecUnpack(idx, _) - | VecSwap(idx) => { + | VecSwap(idx) + | CallFunctionPointer(idx) => { self.check_code_unit_bounds_impl( self.view.signatures(), *idx, @@ -544,7 +545,7 @@ impl<'a> BoundsChecker<'a> { for ty in ty.preorder_traversal() { match ty { Bool | U8 | U16 | U32 | U64 | U128 | U256 | Address | Signer | TypeParameter(_) - | Reference(_) | MutableReference(_) | Vector(_) => (), + | Reference(_) | MutableReference(_) | Vector(_) | Function(_) => (), Struct(idx) => { check_bounds_impl(self.view.struct_handles(), *idx)?; if let Some(sh) = self.view.struct_handles().get(idx.into_index()) { @@ -612,7 +613,8 @@ impl<'a> BoundsChecker<'a> { | Reference(_) | MutableReference(_) | Vector(_) - | StructInstantiation(_, _) => (), + | StructInstantiation(_, _) + | Function(_) => (), } } Ok(()) diff --git a/language/move-binary-format/src/constant.rs b/language/move-binary-format/src/constant.rs index a6df17d72a..a86af249d0 100644 --- a/language/move-binary-format/src/constant.rs +++ b/language/move-binary-format/src/constant.rs @@ -22,7 +22,8 @@ fn sig_to_ty(sig: &SignatureToken) -> Option { | SignatureToken::MutableReference(_) | SignatureToken::Struct(_) | SignatureToken::TypeParameter(_) - | SignatureToken::StructInstantiation(_, _) => None, + | SignatureToken::StructInstantiation(_, _) + | SignatureToken::Function(_) => None, } } diff --git a/language/move-binary-format/src/deserializer.rs b/language/move-binary-format/src/deserializer.rs index 33ca060009..3955ac5b66 100644 --- a/language/move-binary-format/src/deserializer.rs +++ b/language/move-binary-format/src/deserializer.rs @@ -986,6 +986,12 @@ fn load_signature_token(cursor: &mut VersionedCursor) -> BinaryLoaderResult, }, + Function { + params_len: usize, + parameters: Vec, + return_len: usize, + return_: Vec, + }, } impl TypeBuilder { @@ -1012,6 +1018,46 @@ fn load_signature_token(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + if parameters.len() < params_len { + parameters.push(tok); + if parameters.len() == params_len && return_len == 0 { + T::Saturated(SignatureToken::Function(Box::new(FunctionType { + parameters, + return_, + }))) + } else { + T::Function { + params_len, + parameters, + return_len, + return_, + } + } + } else if return_.len() < return_len { + return_.push(tok); + if return_.len() == return_len { + T::Saturated(SignatureToken::Function(Box::new(FunctionType { + parameters, + return_, + }))) + } else { + T::Function { + params_len, + parameters, + return_len, + return_, + } + } + } else { + unreachable!("invalid type constructor application") + } + } _ => unreachable!("invalid type constructor application"), } } @@ -1041,6 +1087,14 @@ fn load_signature_token(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + return Err( + PartialVMError::new(StatusCode::MALFORMED).with_message(format!( + "u16, u32, u256 integers not supported in bytecode version {}", + cursor.version() + )), + ); + } _ => (), }; @@ -1078,6 +1132,17 @@ fn load_signature_token(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + let params_len = load_type_parameter_count(cursor)?; + let return_len = load_type_parameter_count(cursor)?; + + T::Function { + params_len, + parameters: vec![], + return_len, + return_: vec![], + } + } }) } else { Err(PartialVMError::new(StatusCode::MALFORMED) @@ -1478,6 +1543,20 @@ fn load_code(cursor: &mut VersionedCursor, code: &mut Vec) -> BinaryLo _ => (), }; + match opcode { + Opcodes::CALL_FUNC_PTR | Opcodes::GET_FUNC_PTR | Opcodes::GET_FUNC_PTR_GENERIC + if (cursor.version() < VERSION_7) => + { + return Err( + PartialVMError::new(StatusCode::MALFORMED).with_message(format!( + "Function Pointer not supported in bytecode version {}", + cursor.version() + )), + ); + } + _ => (), + }; + // conversion let bytecode = match opcode { Opcodes::POP => Bytecode::Pop, @@ -1594,6 +1673,14 @@ fn load_code(cursor: &mut VersionedCursor, code: &mut Vec) -> BinaryLo Opcodes::CAST_U16 => Bytecode::CastU16, Opcodes::CAST_U32 => Bytecode::CastU32, Opcodes::CAST_U256 => Bytecode::CastU256, + + Opcodes::GET_FUNC_PTR => { + Bytecode::GetFunctionPointer(load_function_handle_index(cursor)?) + } + Opcodes::GET_FUNC_PTR_GENERIC => { + Bytecode::GetFunctionPointerGeneric(load_function_inst_index(cursor)?) + } + Opcodes::CALL_FUNC_PTR => Bytecode::CallFunctionPointer(load_signature_index(cursor)?), }; code.push(bytecode); } @@ -1641,6 +1728,7 @@ impl SerializedType { 0xD => Ok(SerializedType::U16), 0xE => Ok(SerializedType::U32), 0xF => Ok(SerializedType::U256), + 0xFF => Ok(SerializedType::FUNCTION), _ => Err(PartialVMError::new(StatusCode::UNKNOWN_SERIALIZED_TYPE)), } } @@ -1774,6 +1862,9 @@ impl Opcodes { 0x4B => Ok(Opcodes::CAST_U16), 0x4C => Ok(Opcodes::CAST_U32), 0x4D => Ok(Opcodes::CAST_U256), + 0x4E => Ok(Opcodes::GET_FUNC_PTR), + 0x4F => Ok(Opcodes::GET_FUNC_PTR_GENERIC), + 0x50 => Ok(Opcodes::CALL_FUNC_PTR), _ => Err(PartialVMError::new(StatusCode::UNKNOWN_OPCODE)), } } diff --git a/language/move-binary-format/src/file_format.rs b/language/move-binary-format/src/file_format.rs index e1bb74957d..5f9f323368 100644 --- a/language/move-binary-format/src/file_format.rs +++ b/language/move-binary-format/src/file_format.rs @@ -297,6 +297,20 @@ pub struct FunctionHandle { pub type_parameters: Vec, } +/// A `FunctionType` is the type of a function pointer. +/// +/// It's similar to function handle but don't have module names and type parameters. All type parameters will be fully instantiated at type creation time. +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(any(test, feature = "fuzzing"), derive(proptest_derive::Arbitrary))] +#[cfg_attr(any(test, feature = "fuzzing"), proptest(params = "usize"))] +#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))] +pub struct FunctionType { + /// The list of arguments to the function. + pub parameters: Vec, + /// The list of return types. + pub return_: Vec, +} + /// A field access info (owner type and offset) #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(any(test, feature = "fuzzing"), derive(proptest_derive::Arbitrary))] @@ -634,6 +648,8 @@ impl AbilitySet { /// Abilities for `Bool`, `U8`, `U64`, `U128`, and `Address` pub const PRIMITIVES: AbilitySet = Self((Ability::Copy as u8) | (Ability::Drop as u8) | (Ability::Store as u8)); + /// Abilities for Function Pointer + pub const FUNCTION: AbilitySet = Self((Ability::Copy as u8) | (Ability::Drop as u8)); /// Abilities for `Reference` and `MutableReference` pub const REFERENCES: AbilitySet = Self((Ability::Copy as u8) | (Ability::Drop as u8)); /// Abilities for `Signer` @@ -868,6 +884,8 @@ pub enum SignatureToken { U32, /// Unsigned integers, 256 bits length. U256, + /// Types for a function pointer at runtime. + Function(Box), } /// An iterator to help traverse the `SignatureToken` in a non-recursive fashion to avoid @@ -895,6 +913,11 @@ impl<'a> Iterator for SignatureTokenPreorderTraversalIter<'a> { self.stack.extend(inner_toks.iter().rev()) } + Function(func_ty) => { + self.stack.extend(func_ty.return_.iter().rev()); + self.stack.extend(func_ty.parameters.iter().rev()); + } + Signer | Bool | Address | U8 | U16 | U32 | U64 | U128 | U256 | Struct(_) | TypeParameter(_) => (), } @@ -928,6 +951,13 @@ impl<'a> Iterator for SignatureTokenPreorderTraversalIterWithDepth<'a> { .stack .extend(inner_toks.iter().map(|tok| (tok, depth + 1)).rev()), + Function(func_ty) => { + self.stack + .extend(func_ty.return_.iter().map(|tok| (tok, depth + 1)).rev()); + self.stack + .extend(func_ty.parameters.iter().map(|tok| (tok, depth + 1)).rev()); + } + Signer | Bool | Address | U8 | U16 | U32 | U64 | U128 | U256 | Struct(_) | TypeParameter(_) => (), } @@ -995,6 +1025,11 @@ impl std::fmt::Debug for SignatureToken { SignatureToken::Reference(boxed) => write!(f, "Reference({:?})", boxed), SignatureToken::MutableReference(boxed) => write!(f, "MutableReference({:?})", boxed), SignatureToken::TypeParameter(idx) => write!(f, "TypeParameter({:?})", idx), + SignatureToken::Function(func_ty) => write!( + f, + "Function({:?} => {:?})", + func_ty.parameters, func_ty.return_ + ), } } } @@ -1021,7 +1056,8 @@ impl SignatureToken { | Signer | Struct(_) | StructInstantiation(_, _) - | Vector(_) => SignatureTokenKind::Value, + | Vector(_) + | Function(_) => SignatureTokenKind::Value, // TODO: This is a temporary hack to please the verifier. SignatureTokenKind will soon // be completely removed. `SignatureTokenView::kind()` should be used instead. TypeParameter(_) => SignatureTokenKind::Value, @@ -1041,7 +1077,8 @@ impl SignatureToken { | StructInstantiation(_, _) | Reference(_) | MutableReference(_) - | TypeParameter(_) => false, + | TypeParameter(_) + | Function(_) => false, } } @@ -1052,6 +1089,12 @@ impl SignatureToken { matches!(self, Reference(_) | MutableReference(_)) } + /// Returns true if the `SignatureToken` is a function pointer + pub fn is_function(&self) -> bool { + use SignatureToken::*; + matches!(self, Function(_)) + } + /// Returns true if the `SignatureToken` is a mutable reference. pub fn is_mutable_reference(&self) -> bool { use SignatureToken::*; @@ -1079,7 +1122,8 @@ impl SignatureToken { | StructInstantiation(_, _) | Reference(_) | MutableReference(_) - | TypeParameter(_) => false, + | TypeParameter(_) + | Function(_) => false, } } @@ -1635,6 +1679,14 @@ pub enum Bytecode { /// /// ```..., integer_value -> ..., u256_value``` CastU256, + /// Function Pointers + /// + /// + GetFunctionPointer(FunctionHandleIndex), + + GetFunctionPointerGeneric(FunctionInstantiationIndex), + + CallFunctionPointer(SignatureIndex), } impl ::std::fmt::Debug for Bytecode { @@ -1717,6 +1769,9 @@ impl ::std::fmt::Debug for Bytecode { Bytecode::VecPopBack(a) => write!(f, "VecPopBack({})", a), Bytecode::VecUnpack(a, n) => write!(f, "VecUnpack({}, {})", a, n), Bytecode::VecSwap(a) => write!(f, "VecSwap({})", a), + Bytecode::CallFunctionPointer(a) => write!(f, "CallFuncPtr({})", a), + Bytecode::GetFunctionPointer(idx) => write!(f, "GetFuncPtr({:?})", idx), + Bytecode::GetFunctionPointerGeneric(idx) => write!(f, "GetFuncPtrGeneric({:?})", idx), } } } diff --git a/language/move-binary-format/src/file_format_common.rs b/language/move-binary-format/src/file_format_common.rs index 392090b574..17f39962bb 100644 --- a/language/move-binary-format/src/file_format_common.rs +++ b/language/move-binary-format/src/file_format_common.rs @@ -124,6 +124,7 @@ pub enum SerializedType { U16 = 0xD, U32 = 0xE, U256 = 0xF, + FUNCTION = 0xFF, } #[rustfmt::skip] @@ -218,6 +219,10 @@ pub enum Opcodes { CAST_U16 = 0x4B, CAST_U32 = 0x4C, CAST_U256 = 0x4D, + + GET_FUNC_PTR = 0x4E, + GET_FUNC_PTR_GENERIC = 0x4F, + CALL_FUNC_PTR = 0x50, } /// Upper limit on the binary size @@ -405,8 +410,12 @@ pub const VERSION_5: u32 = 5; /// + u16, u32, u256 integers and corresponding Ld, Cast bytecodes pub const VERSION_6: u32 = 6; +/// Version 7: changes compared with version 6 +/// + function pointers? +pub const VERSION_7: u32 = 7; + // Mark which version is the latest version -pub const VERSION_MAX: u32 = VERSION_6; +pub const VERSION_MAX: u32 = VERSION_7; // Mark which oldest version is supported. // TODO(#145): finish v4 compatibility; as of now, only metadata is implemented @@ -625,6 +634,9 @@ pub fn instruction_key(instruction: &Bytecode) -> u8 { CastU16 => Opcodes::CAST_U16, CastU32 => Opcodes::CAST_U32, CastU256 => Opcodes::CAST_U256, + GetFunctionPointer(_) => Opcodes::GET_FUNC_PTR, + GetFunctionPointerGeneric(_) => Opcodes::GET_FUNC_PTR_GENERIC, + CallFunctionPointer(_) => Opcodes::CALL_FUNC_PTR, }; opcode as u8 } diff --git a/language/move-binary-format/src/normalized.rs b/language/move-binary-format/src/normalized.rs index 183ae569c0..9eec39365d 100644 --- a/language/move-binary-format/src/normalized.rs +++ b/language/move-binary-format/src/normalized.rs @@ -60,6 +60,12 @@ pub enum Type { U32, #[serde(rename = "u256")] U256, + // TBD: Which bytecode version should we use here? + #[serde(rename = "function")] + Function { + arguments: Vec, + return_: Vec, + }, } /// Normalized version of a `FieldDefinition`. The `name` is included even though it is @@ -179,6 +185,18 @@ impl Type { TypeParameter(i) => Type::TypeParameter(*i), Reference(t) => Type::Reference(Box::new(Type::new(m, t))), MutableReference(t) => Type::MutableReference(Box::new(Type::new(m, t))), + Function(func_ty) => Type::Function { + arguments: func_ty + .parameters + .iter() + .map(|tok| Type::new(m, tok)) + .collect(), + return_: func_ty + .return_ + .iter() + .map(|tok| Type::new(m, tok)) + .collect(), + }, } } @@ -198,6 +216,9 @@ impl Type { Signer => true, Struct { type_arguments, .. } => type_arguments.iter().all(|t| t.is_closed()), Vector(t) | Reference(t) | MutableReference(t) => t.is_closed(), + Function { arguments, return_ } => { + arguments.iter().all(|t| t.is_closed()) && return_.iter().all(|t| t.is_closed()) + } } } @@ -205,7 +226,7 @@ impl Type { use Type::*; Some(if self.is_closed() { match self { - Reference(_) | MutableReference(_) => return None, + Reference(_) | MutableReference(_) | Function { .. } => return None, Bool => TypeTag::Bool, U8 => TypeTag::U8, U16 => TypeTag::U16, @@ -276,6 +297,10 @@ impl Type { .get(*i as usize) .expect("Type parameter index out of bound") .clone(), + Function { arguments, return_ } => Function { + arguments: arguments.iter().map(|ty| ty.subst(type_args)).collect(), + return_: return_.iter().map(|ty| ty.subst(type_args)).collect(), + }, } } } @@ -417,6 +442,9 @@ impl std::fmt::Display for Type { Type::Reference(r) => write!(f, "&{}", r), Type::MutableReference(r) => write!(f, "&mut {}", r), Type::TypeParameter(i) => write!(f, "T{:?}", i), + Type::Function { arguments, return_ } => { + write!(f, "Function({:?} => {:?})", arguments, return_) + } } } } diff --git a/language/move-binary-format/src/proptest_types/functions.rs b/language/move-binary-format/src/proptest_types/functions.rs index 30c9a28af8..38121e383a 100644 --- a/language/move-binary-format/src/proptest_types/functions.rs +++ b/language/move-binary-format/src/proptest_types/functions.rs @@ -896,6 +896,10 @@ impl BytecodeGen { match token { U8 | U16 | U32 | U64 | U128 | U256 | Bool | Address | Signer | Struct(_) | TypeParameter(_) => true, + Function(ty) => { + ty.return_.iter().all(BytecodeGen::check_signature_token) + && ty.parameters.iter().all(BytecodeGen::check_signature_token) + } Vector(element_token) => BytecodeGen::check_signature_token(element_token), StructInstantiation(_, type_arguments) => type_arguments .iter() diff --git a/language/move-binary-format/src/proptest_types/signature.rs b/language/move-binary-format/src/proptest_types/signature.rs index 5b94c12925..2afbe086bf 100644 --- a/language/move-binary-format/src/proptest_types/signature.rs +++ b/language/move-binary-format/src/proptest_types/signature.rs @@ -3,8 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::file_format::{ - Ability, AbilitySet, Signature, SignatureToken, StructHandle, StructHandleIndex, TableIndex, - TypeParameterIndex, + Ability, AbilitySet, FunctionType, Signature, SignatureToken, StructHandle, StructHandleIndex, + TableIndex, TypeParameterIndex, }; use proptest::{ collection::{vec, SizeRange}, @@ -98,6 +98,7 @@ pub enum SignatureTokenGen { Vector(Box), Reference(Box), MutableReference(Box), + Function(Vec, Vec), } impl SignatureTokenGen { @@ -107,6 +108,7 @@ impl SignatureTokenGen { (1, Self::reference_strategy().boxed()), (1, Self::mutable_reference_strategy().boxed()), (1, Self::vector_strategy().boxed()), + (1, Self::function_strategy().boxed()), ]) } @@ -157,6 +159,14 @@ impl SignatureTokenGen { Self::owned_strategy().prop_map(|atom| SignatureTokenGen::MutableReference(Box::new(atom))) } + pub fn function_strategy() -> impl Strategy { + ( + vec(Self::owned_strategy(), 5), + vec(Self::owned_strategy(), 5), + ) + .prop_map(|(params, return_)| SignatureTokenGen::Function(params, return_)) + } + pub fn materialize(self, struct_handles: &[StructHandle]) -> SignatureToken { use SignatureTokenGen::*; match self { @@ -169,6 +179,16 @@ impl SignatureTokenGen { U256 => SignatureToken::U256, Address => SignatureToken::Address, Signer => SignatureToken::Signer, + Function(params_gen, return_gen) => SignatureToken::Function(Box::new(FunctionType { + parameters: params_gen + .into_iter() + .map(|gen| gen.materialize(struct_handles)) + .collect(), + return_: return_gen + .into_iter() + .map(|gen| gen.materialize(struct_handles)) + .collect(), + })), Struct(idx) => { let struct_handles_len = struct_handles.len(); if struct_handles_len == 0 { diff --git a/language/move-binary-format/src/proptest_types/types.rs b/language/move-binary-format/src/proptest_types/types.rs index 8879daa9e6..e7f1b7fc79 100644 --- a/language/move-binary-format/src/proptest_types/types.rs +++ b/language/move-binary-format/src/proptest_types/types.rs @@ -62,6 +62,7 @@ impl StDefnMaterializeState { match ty { Bool | U8 | U16 | U32 | U64 | U128 | U256 | Address => AbilitySet::PRIMITIVES, + Function(_) => AbilitySet::FUNCTION, Reference(_) | MutableReference(_) => AbilitySet::REFERENCES, Signer => AbilitySet::SIGNER, diff --git a/language/move-binary-format/src/serializer.rs b/language/move-binary-format/src/serializer.rs index 3a63bf5717..6428092c60 100644 --- a/language/move-binary-format/src/serializer.rs +++ b/language/move-binary-format/src/serializer.rs @@ -670,6 +670,11 @@ fn serialize_signature_token_single_node_impl( binary.push(SerializedType::TYPE_PARAMETER as u8)?; serialize_type_parameter_index(binary, *idx)?; } + SignatureToken::Function(func_ty) => { + binary.push(SerializedType::FUNCTION as u8)?; + serialize_signature_size(binary, func_ty.parameters.len())?; + serialize_signature_size(binary, func_ty.return_.len())?; + } } Ok(()) } @@ -748,6 +753,16 @@ fn serialize_instruction_inner( major_version )); } + Bytecode::CallFunctionPointer(_) + | Bytecode::GetFunctionPointer(_) + | Bytecode::GetFunctionPointerGeneric(_) + if (major_version < VERSION_7) => + { + return Err(anyhow!( + "Function pointers not supported in bytecode version {}", + major_version + )); + } _ => (), }; @@ -960,6 +975,18 @@ fn serialize_instruction_inner( Bytecode::CastU16 => binary.push(Opcodes::CAST_U16 as u8), Bytecode::CastU32 => binary.push(Opcodes::CAST_U32 as u8), Bytecode::CastU256 => binary.push(Opcodes::CAST_U256 as u8), + Bytecode::GetFunctionPointer(idx) => { + binary.push(Opcodes::GET_FUNC_PTR as u8)?; + serialize_function_handle_index(binary, idx) + } + Bytecode::GetFunctionPointerGeneric(method_idx) => { + binary.push(Opcodes::GET_FUNC_PTR_GENERIC as u8)?; + serialize_function_inst_index(binary, method_idx) + } + Bytecode::CallFunctionPointer(sig_idx) => { + binary.push(Opcodes::CALL_FUNC_PTR as u8)?; + serialize_signature_index(binary, sig_idx) + } }; res?; Ok(()) diff --git a/language/move-bytecode-verifier/src/acquires_list_verifier.rs b/language/move-bytecode-verifier/src/acquires_list_verifier.rs index 8454c56eb8..768d09e257 100644 --- a/language/move-bytecode-verifier/src/acquires_list_verifier.rs +++ b/language/move-bytecode-verifier/src/acquires_list_verifier.rs @@ -169,7 +169,29 @@ impl<'a> AcquiresVerifier<'a> { | Bytecode::VecPushBack(_) | Bytecode::VecPopBack(_) | Bytecode::VecUnpack(..) - | Bytecode::VecSwap(_) => Ok(()), + | Bytecode::VecSwap(_) + | Bytecode::CallFunctionPointer(_) => Ok(()), + Bytecode::GetFunctionPointer(fh_idx) => self.assert_no_acquire(*fh_idx, offset), + Bytecode::GetFunctionPointerGeneric(idx) => { + let fi = self.module.function_instantiation_at(*idx); + self.assert_no_acquire(fi.handle, offset) + } + } + } + + fn assert_no_acquire( + &mut self, + fh_idx: FunctionHandleIndex, + offset: CodeOffset, + ) -> PartialVMResult<()> { + let function_handle = self.module.function_handle_at(fh_idx); + if !self + .function_acquired_resources(function_handle, fh_idx) + .is_empty() + { + Err(self.error(StatusCode::MISSING_ACQUIRES_ANNOTATION, offset)) + } else { + Ok(()) } } diff --git a/language/move-bytecode-verifier/src/dependencies.rs b/language/move-bytecode-verifier/src/dependencies.rs index f24b75487f..444e1b8f37 100644 --- a/language/move-bytecode-verifier/src/dependencies.rs +++ b/language/move-bytecode-verifier/src/dependencies.rs @@ -474,6 +474,10 @@ fn compare_types( Ok(()) } } + (SignatureToken::Function(ty1), SignatureToken::Function(ty2)) => { + compare_cross_module_signatures(context, &ty1.parameters, &ty2.parameters, def_module)?; + compare_cross_module_signatures(context, &ty1.return_, &ty2.return_, def_module) + } (SignatureToken::Bool, _) | (SignatureToken::U8, _) | (SignatureToken::U64, _) @@ -488,7 +492,8 @@ fn compare_types( | (SignatureToken::TypeParameter(_), _) | (SignatureToken::U16, _) | (SignatureToken::U32, _) - | (SignatureToken::U256, _) => Err(PartialVMError::new(StatusCode::TYPE_MISMATCH)), + | (SignatureToken::U256, _) + | (SignatureToken::Function(_), _) => Err(PartialVMError::new(StatusCode::TYPE_MISMATCH)), } } diff --git a/language/move-bytecode-verifier/src/instantiation_loops.rs b/language/move-bytecode-verifier/src/instantiation_loops.rs index 9af7dff237..a663ebd5da 100644 --- a/language/move-bytecode-verifier/src/instantiation_loops.rs +++ b/language/move-bytecode-verifier/src/instantiation_loops.rs @@ -154,6 +154,11 @@ impl<'a> InstantiationLoopChecker<'a> { rec(type_params, ty); } } + Function(func_ty) => { + for ty in func_ty.parameters.iter().chain(func_ty.return_.iter()) { + rec(type_params, ty); + } + } } } diff --git a/language/move-bytecode-verifier/src/instruction_consistency.rs b/language/move-bytecode-verifier/src/instruction_consistency.rs index 52cb7cd00c..fc8fcdf356 100644 --- a/language/move-bytecode-verifier/src/instruction_consistency.rs +++ b/language/move-bytecode-verifier/src/instruction_consistency.rs @@ -142,13 +142,69 @@ impl<'a> InstructionConsistency<'a> { // List out the other options explicitly so there's a compile error if a new // bytecode gets added. - FreezeRef | Pop | Ret | Branch(_) | BrTrue(_) | BrFalse(_) | LdU8(_) | LdU16(_) - | LdU32(_) | LdU64(_) | LdU128(_) | LdU256(_) | LdConst(_) | CastU8 | CastU16 - | CastU32 | CastU64 | CastU128 | CastU256 | LdTrue | LdFalse | ReadRef - | WriteRef | Add | Sub | Mul | Mod | Div | BitOr | BitAnd | Xor | Shl | Shr - | Or | And | Not | Eq | Neq | Lt | Gt | Le | Ge | CopyLoc(_) | MoveLoc(_) - | StLoc(_) | MutBorrowLoc(_) | ImmBorrowLoc(_) | VecLen(_) | VecImmBorrow(_) - | VecMutBorrow(_) | VecPushBack(_) | VecPopBack(_) | VecSwap(_) | Abort | Nop => (), + FreezeRef + | Pop + | Ret + | Branch(_) + | BrTrue(_) + | BrFalse(_) + | LdU8(_) + | LdU16(_) + | LdU32(_) + | LdU64(_) + | LdU128(_) + | LdU256(_) + | LdConst(_) + | CastU8 + | CastU16 + | CastU32 + | CastU64 + | CastU128 + | CastU256 + | LdTrue + | LdFalse + | ReadRef + | WriteRef + | Add + | Sub + | Mul + | Mod + | Div + | BitOr + | BitAnd + | Xor + | Shl + | Shr + | Or + | And + | Not + | Eq + | Neq + | Lt + | Gt + | Le + | Ge + | CopyLoc(_) + | MoveLoc(_) + | StLoc(_) + | MutBorrowLoc(_) + | ImmBorrowLoc(_) + | VecLen(_) + | VecImmBorrow(_) + | VecMutBorrow(_) + | VecPushBack(_) + | VecPopBack(_) + | VecSwap(_) + | Abort + | Nop + | CallFunctionPointer(_) => (), + GetFunctionPointer(idx) => { + self.check_function_op(offset, *idx, /* generic */ false)?; + } + GetFunctionPointerGeneric(idx) => { + let func_inst = self.resolver.function_instantiation_at(*idx); + self.check_function_op(offset, func_inst.handle, /* generic */ true)?; + } } } Ok(()) diff --git a/language/move-bytecode-verifier/src/locals_safety/mod.rs b/language/move-bytecode-verifier/src/locals_safety/mod.rs index e552b887b3..f4b80292f6 100644 --- a/language/move-bytecode-verifier/src/locals_safety/mod.rs +++ b/language/move-bytecode-verifier/src/locals_safety/mod.rs @@ -151,7 +151,10 @@ fn execute_inner( | Bytecode::VecPushBack(_) | Bytecode::VecPopBack(_) | Bytecode::VecUnpack(..) - | Bytecode::VecSwap(_) => (), + | Bytecode::VecSwap(_) + | Bytecode::CallFunctionPointer(_) + | Bytecode::GetFunctionPointer(_) + | Bytecode::GetFunctionPointerGeneric(_) => (), }; Ok(()) } diff --git a/language/move-bytecode-verifier/src/reference_safety/mod.rs b/language/move-bytecode-verifier/src/reference_safety/mod.rs index dfd77df384..33d0734080 100644 --- a/language/move-bytecode-verifier/src/reference_safety/mod.rs +++ b/language/move-bytecode-verifier/src/reference_safety/mod.rs @@ -16,8 +16,9 @@ use move_binary_format::{ binary_views::{BinaryIndexedView, FunctionView}, errors::{PartialVMError, PartialVMResult}, file_format::{ - Bytecode, CodeOffset, FunctionDefinitionIndex, FunctionHandle, IdentifierIndex, - SignatureIndex, SignatureToken, StructDefinition, StructFieldInformation, + Bytecode, CodeOffset, FunctionDefinitionIndex, FunctionHandle, FunctionType, + IdentifierIndex, Signature, SignatureIndex, SignatureToken, StructDefinition, + StructFieldInformation, }, safe_assert, safe_unwrap, }; @@ -91,6 +92,32 @@ fn call( Ok(()) } +fn call_func_pointer( + verifier: &mut ReferenceSafetyAnalysis, + state: &mut AbstractState, + offset: CodeOffset, + function_ty: &FunctionType, +) -> PartialVMResult<()> { + // The first argument is a function pointer type. + verifier.stack.pop().unwrap(); + + let arguments = function_ty + .parameters + .iter() + .map(|_| verifier.stack.pop().unwrap()) + .rev() + .collect(); + + // Function Pointer cannot acqure resources; + let acquired_resources = BTreeSet::new(); + let return_ = Signature(function_ty.return_.clone()); + let values = state.call(offset, arguments, &acquired_resources, &return_)?; + for value in values { + verifier.stack.push(value) + } + Ok(()) +} + fn num_fields(struct_def: &StructDefinition) -> usize { match &struct_def.field_information { StructFieldInformation::Native => 0, @@ -403,6 +430,32 @@ fn execute_inner( let vec_ref = safe_unwrap!(verifier.stack.pop()); state.vector_op(offset, vec_ref, true)?; } + Bytecode::CallFunctionPointer(sig_idx) => { + match &verifier.resolver.signature_at(*sig_idx).0[0] { + SignatureToken::Function(function_ty) => { + call_func_pointer(verifier, state, offset, &function_ty)?; + } + _ => { + return Err( + PartialVMError::new(StatusCode::TYPE_MISMATCH).at_code_offset( + verifier.function_view.index().unwrap_or_default(), + offset, + ), + ) + } + } + } + Bytecode::GetFunctionPointer(_) | Bytecode::GetFunctionPointerGeneric(_) => { + // Push a naive function pointer type because it is not a reference. + verifier + .stack + .push( + state.value_for(&SignatureToken::Function(Box::new(FunctionType { + parameters: vec![], + return_: vec![], + }))), + ); + } }; Ok(()) } diff --git a/language/move-bytecode-verifier/src/signature.rs b/language/move-bytecode-verifier/src/signature.rs index 3894b4b448..556fad5310 100644 --- a/language/move-bytecode-verifier/src/signature.rs +++ b/language/move-bytecode-verifier/src/signature.rs @@ -137,7 +137,7 @@ impl<'a> SignatureChecker<'a> { use Bytecode::*; for (offset, instr) in code.code.iter().enumerate() { let result = match instr { - CallGeneric(idx) => { + CallGeneric(idx) | GetFunctionPointerGeneric(idx) => { let func_inst = self.resolver.function_instantiation_at(*idx); let func_handle = self.resolver.function_handle_at(func_inst.handle); let type_arguments = &self.resolver.signature_at(func_inst.type_parameters).0; @@ -202,14 +202,67 @@ impl<'a> SignatureChecker<'a> { // List out the other options explicitly so there's a compile error if a new // bytecode gets added. - Pop | Ret | Branch(_) | BrTrue(_) | BrFalse(_) | LdU8(_) | LdU16(_) | LdU32(_) - | LdU64(_) | LdU128(_) | LdU256(_) | LdConst(_) | CastU8 | CastU16 | CastU32 - | CastU64 | CastU128 | CastU256 | LdTrue | LdFalse | Call(_) | Pack(_) - | Unpack(_) | ReadRef | WriteRef | FreezeRef | Add | Sub | Mul | Mod | Div - | BitOr | BitAnd | Xor | Shl | Shr | Or | And | Not | Eq | Neq | Lt | Gt | Le - | Ge | CopyLoc(_) | MoveLoc(_) | StLoc(_) | MutBorrowLoc(_) | ImmBorrowLoc(_) - | MutBorrowField(_) | ImmBorrowField(_) | MutBorrowGlobal(_) - | ImmBorrowGlobal(_) | Exists(_) | MoveTo(_) | MoveFrom(_) | Abort | Nop => Ok(()), + Pop + | Ret + | Branch(_) + | BrTrue(_) + | BrFalse(_) + | LdU8(_) + | LdU16(_) + | LdU32(_) + | LdU64(_) + | LdU128(_) + | LdU256(_) + | LdConst(_) + | CastU8 + | CastU16 + | CastU32 + | CastU64 + | CastU128 + | CastU256 + | LdTrue + | LdFalse + | Call(_) + | Pack(_) + | Unpack(_) + | ReadRef + | WriteRef + | FreezeRef + | Add + | Sub + | Mul + | Mod + | Div + | BitOr + | BitAnd + | Xor + | Shl + | Shr + | Or + | And + | Not + | Eq + | Neq + | Lt + | Gt + | Le + | Ge + | CopyLoc(_) + | MoveLoc(_) + | StLoc(_) + | MutBorrowLoc(_) + | ImmBorrowLoc(_) + | MutBorrowField(_) + | ImmBorrowField(_) + | MutBorrowGlobal(_) + | ImmBorrowGlobal(_) + | Exists(_) + | MoveTo(_) + | MoveFrom(_) + | Abort + | Nop + | GetFunctionPointer(_) + | CallFunctionPointer(_) => Ok(()), }; result.map_err(|err| { err.append_message_with_separator(' ', format!("at offset {} ", offset)) @@ -259,7 +312,8 @@ impl<'a> SignatureChecker<'a> { | SignatureToken::U128 | SignatureToken::U256 | SignatureToken::Address - | SignatureToken::Signer => {} + | SignatureToken::Signer + | SignatureToken::Function(_) => {} } Ok(()) } @@ -293,7 +347,7 @@ impl<'a> SignatureChecker<'a> { use SignatureToken::*; match ty { U8 | U16 | U32 | U64 | U128 | U256 | Bool | Address | Signer | Struct(_) - | TypeParameter(_) => Ok(()), + | TypeParameter(_) | Function(_) => Ok(()), Reference(_) | MutableReference(_) => { // TODO: Prop tests expect us to NOT check the inner types. // Revisit this once we rework prop tests. @@ -363,7 +417,8 @@ impl<'a> SignatureChecker<'a> { | SignatureToken::U128 | SignatureToken::U256 | SignatureToken::Address - | SignatureToken::Signer => Ok(()), + | SignatureToken::Signer + | SignatureToken::Function(_) => Ok(()), } } diff --git a/language/move-bytecode-verifier/src/stack_usage_verifier.rs b/language/move-bytecode-verifier/src/stack_usage_verifier.rs index bb4b112e5c..2ea3120a02 100644 --- a/language/move-bytecode-verifier/src/stack_usage_verifier.rs +++ b/language/move-bytecode-verifier/src/stack_usage_verifier.rs @@ -14,7 +14,10 @@ use move_binary_format::{ binary_views::{BinaryIndexedView, FunctionView}, control_flow_graph::{BlockId, ControlFlowGraph}, errors::{PartialVMError, PartialVMResult}, - file_format::{Bytecode, CodeUnit, FunctionDefinitionIndex, Signature, StructFieldInformation}, + file_format::{ + Bytecode, CodeOffset, CodeUnit, FunctionDefinitionIndex, Signature, SignatureToken, + StructFieldInformation, + }, }; use move_core_types::vm_status::StatusCode; @@ -55,7 +58,7 @@ impl<'a> StackUsageVerifier<'a> { let block_start = cfg.block_start(block_id); let mut overall_push = 0; for i in block_start..=cfg.block_end(block_id) { - let (num_pops, num_pushes) = self.instruction_effect(&code[i as usize])?; + let (num_pops, num_pushes) = self.instruction_effect(&code[i as usize], i)?; if let Some(new_pushes) = u64::checked_add(overall_push, num_pushes) { overall_push = new_pushes }; @@ -112,7 +115,11 @@ impl<'a> StackUsageVerifier<'a> { /// The effect of an instruction is a tuple where the first element /// is the number of pops it does, and the second element is the number /// of pushes it does - fn instruction_effect(&self, instruction: &Bytecode) -> PartialVMResult<(u64, u64)> { + fn instruction_effect( + &self, + instruction: &Bytecode, + offset: CodeOffset, + ) -> PartialVMResult<(u64, u64)> { Ok(match instruction { // Instructions that pop, but don't push Bytecode::Pop @@ -262,6 +269,17 @@ impl<'a> StackUsageVerifier<'a> { }; (1, field_count as u64) } + Bytecode::GetFunctionPointer(_) | Bytecode::GetFunctionPointerGeneric(_) => (0, 1), + Bytecode::CallFunctionPointer(idx) => match &self.resolver.signature_at(*idx).0[0] { + SignatureToken::Function(func_ty) => ( + func_ty.parameters.len() as u64 + 1, + func_ty.return_.len() as u64, + ), + _ => { + return Err(PartialVMError::new(StatusCode::TYPE_MISMATCH) + .at_code_offset(self.current_function(), offset)) + } + }, }) } diff --git a/language/move-bytecode-verifier/src/struct_defs.rs b/language/move-bytecode-verifier/src/struct_defs.rs index 00c9746863..2e49e2ec22 100644 --- a/language/move-bytecode-verifier/src/struct_defs.rs +++ b/language/move-bytecode-verifier/src/struct_defs.rs @@ -118,7 +118,9 @@ impl<'a> StructDefGraphBuilder<'a> { T::Reference(_) | T::MutableReference(_) => { return Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) - .with_message("Reference field when checking recursive structs".to_owned()), + .with_message( + "Reference fieldr when checking recursive structs".to_owned(), + ), ) } T::Vector(inner) => self.add_signature_token(neighbors, cur_idx, inner)?, @@ -130,6 +132,8 @@ impl<'a> StructDefGraphBuilder<'a> { .insert(*struct_def_idx); } } + // TODO: Is this safe? + T::Function(_func_ty) => (), T::StructInstantiation(sh_idx, inners) => { if let Some(struct_def_idx) = self.handle_to_def.get(sh_idx) { neighbors diff --git a/language/move-bytecode-verifier/src/type_safety.rs b/language/move-bytecode-verifier/src/type_safety.rs index eb1ed2df18..2662b4a871 100644 --- a/language/move-bytecode-verifier/src/type_safety.rs +++ b/language/move-bytecode-verifier/src/type_safety.rs @@ -11,7 +11,7 @@ use move_binary_format::{ errors::{PartialVMError, PartialVMResult}, file_format::{ AbilitySet, Bytecode, CodeOffset, FieldHandleIndex, FunctionDefinitionIndex, - FunctionHandle, LocalIndex, Signature, SignatureToken, SignatureToken as ST, + FunctionHandle, FunctionType, LocalIndex, Signature, SignatureToken, SignatureToken as ST, StructDefinition, StructDefinitionIndex, StructFieldInformation, StructHandleIndex, }, safe_unwrap, @@ -150,7 +150,7 @@ fn borrow_loc( ) -> PartialVMResult<()> { let loc_signature = verifier.local_at(idx).clone(); - if loc_signature.is_reference() { + if loc_signature.is_reference() || loc_signature.is_function() { return Err(verifier.error(StatusCode::BORROWLOC_REFERENCE_ERROR, offset)); } @@ -834,6 +834,72 @@ fn verify_instr( } verifier.stack.push(ST::U256); } + Bytecode::CallFunctionPointer(sig_idx) => { + let operand_ty = safe_unwrap!(verifier.stack.pop()); + let declared_element_type = &verifier.resolver.signature_at(*sig_idx).0[0]; + + if &operand_ty != declared_element_type { + return Err(verifier.error(StatusCode::TYPE_MISMATCH, offset)); + } + + if let SignatureToken::Function(func_ty) = operand_ty { + for parameter in func_ty.parameters.iter().rev() { + let arg = safe_unwrap!(verifier.stack.pop()); + if &arg != parameter { + return Err(verifier.error(StatusCode::CALL_TYPE_MISMATCH_ERROR, offset)); + } + } + for return_type in func_ty.return_.into_iter() { + verifier.stack.push(return_type); + } + } else { + return Err(verifier.error(StatusCode::TYPE_MISMATCH, offset)); + } + } + Bytecode::GetFunctionPointer(fh_idx) => { + let function_handle = verifier.resolver.function_handle_at(*fh_idx); + verifier + .stack + .push(SignatureToken::Function(Box::new(FunctionType { + parameters: verifier + .resolver + .signature_at(function_handle.parameters) + .0 + .clone(), + return_: verifier + .resolver + .signature_at(function_handle.return_) + .0 + .clone(), + }))); + } + Bytecode::GetFunctionPointerGeneric(fi_idx) => { + let function_inst = verifier.resolver.function_instantiation_at(*fi_idx); + + let function_handle = verifier.resolver.function_handle_at(function_inst.handle); + let type_actuals = verifier + .resolver + .signature_at(function_inst.type_parameters); + + verifier + .stack + .push(SignatureToken::Function(Box::new(FunctionType { + parameters: verifier + .resolver + .signature_at(function_handle.parameters) + .0 + .iter() + .map(|tok| instantiate(tok, type_actuals)) + .collect(), + return_: verifier + .resolver + .signature_at(function_handle.return_) + .0 + .iter() + .map(|tok| instantiate(tok, type_actuals)) + .collect(), + }))); + } }; Ok(()) } @@ -880,6 +946,18 @@ fn instantiate(token: &SignatureToken, subst: &Signature) -> SignatureToken { debug_assert!((*idx as usize) < subst.len()); subst.0[*idx as usize].clone() } + SignatureToken::Function(func_ty) => Function(Box::new(FunctionType { + parameters: func_ty + .parameters + .iter() + .map(|ty| instantiate(ty, subst)) + .collect(), + return_: func_ty + .return_ + .iter() + .map(|ty| instantiate(ty, subst)) + .collect(), + })), } } diff --git a/language/move-compiler/src/interface_generator.rs b/language/move-compiler/src/interface_generator.rs index bd37bf2967..07ac214740 100644 --- a/language/move-compiler/src/interface_generator.rs +++ b/language/move-compiler/src/interface_generator.rs @@ -369,6 +369,7 @@ fn write_signature_token(ctx: &mut Context, t: &SignatureToken) -> String { format!("&mut {}", write_signature_token(ctx, inner)) } SignatureToken::TypeParameter(idx) => write_type_parameter(*idx), + SignatureToken::Function(_) => "function_ptr".to_string(), } } diff --git a/language/move-ir-compiler/move-ir-to-bytecode/src/compiler.rs b/language/move-ir-compiler/move-ir-to-bytecode/src/compiler.rs index ade4aa0ee4..db2b0b71ea 100644 --- a/language/move-ir-compiler/move-ir-to-bytecode/src/compiler.rs +++ b/language/move-ir-compiler/move-ir-to-bytecode/src/compiler.rs @@ -7,10 +7,10 @@ use anyhow::{bail, format_err, Result}; use move_binary_format::{ file_format::{ Ability, AbilitySet, Bytecode, CodeOffset, CodeUnit, CompiledModule, CompiledScript, - Constant, FieldDefinition, FunctionDefinition, FunctionSignature, ModuleHandle, Signature, - SignatureToken, StructDefinition, StructDefinitionIndex, StructFieldInformation, - StructHandleIndex, StructTypeParameter, TableIndex, TypeParameterIndex, TypeSignature, - Visibility, + Constant, FieldDefinition, FunctionDefinition, FunctionSignature, FunctionType, + ModuleHandle, Signature, SignatureToken, StructDefinition, StructDefinitionIndex, + StructFieldInformation, StructHandleIndex, StructTypeParameter, TableIndex, + TypeParameterIndex, TypeSignature, Visibility, }, file_format_common::VERSION_MAX, }; @@ -664,6 +664,10 @@ fn compile_type( type_parameters, inner_type, )?)), + Type::Function(parameters, return_) => SignatureToken::Function(Box::new(FunctionType { + parameters: compile_types(context, type_parameters, parameters)?, + return_: compile_types(context, type_parameters, return_)?, + })), Type::Reference(is_mutable, inner_type) => { let inner_token = Box::new(compile_type(context, type_parameters, inner_type)?); if *is_mutable { @@ -1321,6 +1325,23 @@ fn compile_expression( compile_expression(context, function_frame, code, e)?; } } + Exp_::GetFunctionPointer { module, name, type_actuals } => { + let ty_arg_tokens = + compile_types(context, function_frame.type_parameters(), &type_actuals)?; + let tokens = Signature(ty_arg_tokens); + let type_actuals_id = context.signature_index(tokens)?; + let fh_idx = context.function_handle(module, name)?.1; + let fcall = if type_actuals.is_empty() { + Bytecode::GetFunctionPointer(fh_idx) + } else { + let fi_idx = + context.function_instantiation_index(fh_idx, type_actuals_id)?; + Bytecode::GetFunctionPointerGeneric(fi_idx) + }; + push_instr!(context.decl_location(), fcall); + // Return value of current function is pushed onto the stack. + function_frame.push()?; + } }) } @@ -1545,6 +1566,24 @@ fn compile_call( // Return value of current function is pushed onto the stack. function_frame.push()?; } + FunctionCall_::CallFunctionPointer(func_ty) => { + let (param_len, return_len) = + if let move_ir_types::ast::Type::Function(parameters, return_) = &func_ty { + (parameters.len(), return_.len()) + } else { + bail!("CallFunctionPointer expect a function type in the argument") + }; + let ty = compile_types(context, function_frame.type_parameters(), &vec![func_ty])?; + let tokens = Signature(ty); + let func_ty_id = context.signature_index(tokens)?; + push_instr!(call.loc, Bytecode::CallFunctionPointer(func_ty_id)); + for _ in 0..param_len { + function_frame.pop()?; + } + for _ in 0..return_len { + function_frame.push()?; + } + } }) } @@ -1562,6 +1601,7 @@ fn compile_constant(_context: &mut Context, ty: Type, value: MoveValue) -> Resul Type::Bool => MoveTypeLayout::Bool, Type::Vector(inner_type) => MoveTypeLayout::Vector(Box::new(type_layout(*inner_type)?)), Type::Reference(_, _) => bail!("References are not supported in constant type layouts"), + Type::Function(_, _) => bail!("Functions are not supported in constant type layouts"), Type::TypeParameter(_) => { bail!("Type parameters are not supported in constant type layouts") } diff --git a/language/move-ir-compiler/move-ir-to-bytecode/src/context.rs b/language/move-ir-compiler/move-ir-to-bytecode/src/context.rs index 8afc1aebf0..b09e3f3162 100644 --- a/language/move-ir-compiler/move-ir-to-bytecode/src/context.rs +++ b/language/move-ir-compiler/move-ir-to-bytecode/src/context.rs @@ -9,9 +9,10 @@ use move_binary_format::{ AbilitySet, AddressIdentifierIndex, CodeOffset, Constant, ConstantPoolIndex, FieldHandle, FieldHandleIndex, FieldInstantiation, FieldInstantiationIndex, FunctionDefinitionIndex, FunctionHandle, FunctionHandleIndex, FunctionInstantiation, FunctionInstantiationIndex, - FunctionSignature, IdentifierIndex, ModuleHandle, ModuleHandleIndex, Signature, - SignatureIndex, SignatureToken, StructDefInstantiation, StructDefInstantiationIndex, - StructDefinitionIndex, StructHandle, StructHandleIndex, StructTypeParameter, TableIndex, + FunctionSignature, FunctionType, IdentifierIndex, ModuleHandle, ModuleHandleIndex, + Signature, SignatureIndex, SignatureToken, StructDefInstantiation, + StructDefInstantiationIndex, StructDefinitionIndex, StructHandle, StructHandleIndex, + StructTypeParameter, TableIndex, }, CompiledModule, }; @@ -808,6 +809,18 @@ impl<'a> Context<'a> { .collect::>()?; SignatureToken::StructInstantiation(correct_sh_idx, correct_inners) } + SignatureToken::Function(func_ty) => SignatureToken::Function(Box::new(FunctionType { + parameters: func_ty + .parameters + .into_iter() + .map(|tok| self.reindex_signature_token(dep, tok)) + .collect::>>()?, + return_: func_ty + .return_ + .into_iter() + .map(|tok| self.reindex_signature_token(dep, tok)) + .collect::>>()?, + })), }) } diff --git a/language/move-ir-compiler/move-ir-to-bytecode/syntax/src/lexer.rs b/language/move-ir-compiler/move-ir-to-bytecode/syntax/src/lexer.rs index 8bca7244ca..33da180233 100644 --- a/language/move-ir-compiler/move-ir-to-bytecode/syntax/src/lexer.rs +++ b/language/move-ir-compiler/move-ir-to-bytecode/syntax/src/lexer.rs @@ -114,6 +114,8 @@ pub enum Tok { LSquare, RSquare, PeriodPeriod, + GetFuncPointer, + CallFunctionPointer, } impl Tok { @@ -279,6 +281,7 @@ impl<'input> Lexer<'input> { "move_from" => (Tok::MoveFrom, len + 1), "move_to" => (Tok::MoveTo, len + 1), "main" => (Tok::Main, len), + "call_function_pointer" => (Tok::CallFunctionPointer, len + 1), _ => { if let Some(stripped) = name.strip_prefix("vec_pack_") { match stripped.parse::() { @@ -299,6 +302,7 @@ impl<'input> Lexer<'input> { "assert" => (Tok::Assert, len + 1), "copy" => (Tok::Copy, len + 1), "move" => (Tok::Move, len + 1), + "get_function_pointer" => (Tok::GetFuncPointer, len + 1), _ => (get_name_token(name), len), }, _ => (get_name_token(name), len), diff --git a/language/move-ir-compiler/move-ir-to-bytecode/syntax/src/syntax.rs b/language/move-ir-compiler/move-ir-to-bytecode/syntax/src/syntax.rs index 1054ca4048..48c5cc91b9 100644 --- a/language/move-ir-compiler/move-ir-to-bytecode/syntax/src/syntax.rs +++ b/language/move-ir-compiler/move-ir-to-bytecode/syntax/src/syntax.rs @@ -476,6 +476,12 @@ fn parse_qualified_function_name( let f = parse_builtin(tokens)?; FunctionCall_::Builtin(f) } + Tok::CallFunctionPointer => { + tokens.advance()?; + let func_type = parse_type(tokens)?; + consume_token(tokens, Tok::Greater)?; + FunctionCall_::CallFunctionPointer(func_type) + } Tok::DotNameValue => { let module_dot_name = parse_dot_name(tokens)?; let type_actuals = parse_type_actuals(tokens)?; @@ -619,7 +625,8 @@ fn parse_call_or_term_(tokens: &mut Lexer) -> Result { + | Tok::ToU256 + | Tok::CallFunctionPointer => { let f = parse_qualified_function_name(tokens)?; let exp = parse_call_or_term(tokens)?; Ok(Exp_::FunctionCall(f, Box::new(exp))) @@ -673,6 +680,21 @@ fn parse_pack_( fn parse_term_(tokens: &mut Lexer) -> Result> { match tokens.peek() { + Tok::GetFuncPointer => { + tokens.advance()?; + let module_dot_name = parse_dot_name(tokens)?; + let type_actuals = parse_type_actuals(tokens)?; + let v: Vec<&str> = module_dot_name.split('.').collect(); + assert!(v.len() == 2); + consume_token(tokens, Tok::RParen)?; + + Ok(Exp_::GetFunctionPointer { + module: ModuleName(Symbol::from(v[0])), + name: FunctionName(Symbol::from(v[1])), + type_actuals, + } + ) + }, Tok::Move => { tokens.advance()?; let v = parse_var(tokens)?; @@ -1078,7 +1100,9 @@ fn parse_statement_(tokens: &mut Lexer) -> Result Ok(Statement_::Exp(Box::new(parse_call(tokens)?))), + | Tok::ToU256 + | Tok::CallFunctionPointer => Ok(Statement_::Exp(Box::new(parse_call(tokens)?))), + Tok::LParen => { tokens.advance()?; let start = tokens.start_loc(); @@ -1286,6 +1310,17 @@ fn parse_type(tokens: &mut Lexer) -> Result let tys = parse_type_actuals(tokens)?; Type::Struct(s, tys) } + Tok::Pipe => { + tokens.advance()?; + let parameters = parse_comma_list(tokens, &[Tok::Pipe], parse_type, true)?; + adjust_token(tokens, &[Tok::Pipe])?; + consume_token(tokens, Tok::Pipe)?; + consume_token(tokens, Tok::LParen)?; + let return_ = parse_comma_list(tokens, &[Tok::RParen], parse_type, true)?; + adjust_token(tokens, &[Tok::RParen])?; + consume_token(tokens, Tok::RParen)?; + Type::Function(parameters, return_) + } Tok::Amp => { tokens.advance()?; Type::Reference(false, Box::new(parse_type(tokens)?)) diff --git a/language/move-ir/types/src/ast.rs b/language/move-ir/types/src/ast.rs index 9368f6cf83..ad5def2345 100644 --- a/language/move-ir/types/src/ast.rs +++ b/language/move-ir/types/src/ast.rs @@ -201,6 +201,7 @@ pub enum Type { Reference(bool, Box), /// A type parameter TypeParameter(TypeVar_), + Function(Vec, Vec), } //************************************************************************************************** @@ -458,6 +459,7 @@ pub enum FunctionCall_ { name: FunctionName, type_actuals: Vec, }, + CallFunctionPointer(Type), } /// The type for a function call and its location pub type FunctionCall = Spanned; @@ -637,6 +639,11 @@ pub enum Exp_ { FunctionCall(FunctionCall, Box), /// (e_1, e_2, e_3, ..., e_j) ExprList(Vec), + GetFunctionPointer { + module: ModuleName, + name: FunctionName, + type_actuals: Vec, + } } /// The type for a `Exp_` and its location @@ -1480,6 +1487,20 @@ impl fmt::Display for Type { write!(f, "&{}{}", if *is_mutable { "mut " } else { "" }, t) } Type::TypeParameter(s) => write!(f, "{}", s), + Type::Function(parameters, return_) => { + let print_tys = |f: &mut fmt::Formatter<'_>, tys: &[Type]| { + if tys.is_empty() { + write!(f, "") + } else { + write!(f, "{}", intersperse(tys, ",")) + } + }; + + write!(f, "|")?; + print_tys(f, parameters)?; + write!(f, "|")?; + print_tys(f, return_) + } } } } @@ -1542,6 +1563,7 @@ impl fmt::Display for FunctionCall_ { name, format_type_actuals(type_actuals) ), + FunctionCall_::CallFunctionPointer(ty) => write!(f, "call<{}>", ty), } } } @@ -1704,6 +1726,9 @@ impl fmt::Display for Exp_ { write!(f, "({})", intersperse(exps, ", ")) } } + Exp_::GetFunctionPointer { module, name, type_actuals } => { + writeln!(f, "get_function_pointer({}.{}{}", module, name, format_type_actuals(type_actuals)) + } } } } diff --git a/language/move-model/src/model.rs b/language/move-model/src/model.rs index ebfb970677..435d58be2f 100644 --- a/language/move-model/src/model.rs +++ b/language/move-model/src/model.rs @@ -2258,6 +2258,7 @@ impl<'env> ModuleEnv<'env> { self.globalize_signatures(args), ) } + SignatureToken::Function(_) => unimplemented!(), } } diff --git a/language/move-prover/bytecode/src/stackless_bytecode_generator.rs b/language/move-prover/bytecode/src/stackless_bytecode_generator.rs index 03cad999ef..1a682f255a 100644 --- a/language/move-prover/bytecode/src/stackless_bytecode_generator.rs +++ b/language/move-prover/bytecode/src/stackless_bytecode_generator.rs @@ -1323,6 +1323,9 @@ impl<'a> StacklessBytecodeGenerator<'a> { None, )) } + MoveBytecode::CallFunctionPointer(_) + | MoveBytecode::GetFunctionPointer(_) + | MoveBytecode::GetFunctionPointerGeneric(_) => unimplemented!(), } } diff --git a/language/move-vm/runtime/src/interpreter.rs b/language/move-vm/runtime/src/interpreter.rs index f51c738636..4f8407f395 100644 --- a/language/move-vm/runtime/src/interpreter.rs +++ b/language/move-vm/runtime/src/interpreter.rs @@ -218,6 +218,75 @@ impl Interpreter { self.check_friend_or_private_call(¤t_frame.function, &func)?; } + // Charge gas + let module_id = func + .module_id() + .ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message("Failed to get native function module id".to_string()) + }) + .map_err(|e| set_err_info!(current_frame, e))?; + gas_meter + .charge_call_generic( + module_id, + func.name(), + ty_args.iter().map(|ty| TypeWithLoader { ty, loader }), + self.operand_stack + .last_n(func.arg_count()) + .map_err(|e| set_err_info!(current_frame, e))?, + (func.local_count() as u64).into(), + ) + .map_err(|e| set_err_info!(current_frame, e))?; + + if func.is_native() { + self.call_native( + &resolver, data_store, gas_meter, extensions, func, ty_args, + )?; + current_frame.pc += 1; // advance past the Call instruction in the caller + continue; + } + let frame = self + .make_call_frame(loader, func, ty_args) + .map_err(|e| self.set_location(e)) + .map_err(|err| self.maybe_core_dump(err, ¤t_frame))?; + self.call_stack.push(current_frame).map_err(|frame| { + let err = PartialVMError::new(StatusCode::CALL_STACK_OVERFLOW); + let err = set_err_info!(frame, err); + self.maybe_core_dump(err, &frame) + })?; + current_frame = frame; + } + ExitCode::CallFunctionPointer => { + // TODO(Gas): We should charge gas as we do type substitution... + let (module_id, func_name, ty_args) = self + .operand_stack + .pop() + .map_err(|e| self.set_location(e))? + .as_function() + .ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message( + "CallFunctionPointer has a non function pointer value on stack" + .to_string(), + ) + }) + .map_err(|e| set_err_info!(current_frame, e))?; + + let func = resolver + .function_from_name(&module_id, &func_name) + .ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message( + "CallFunctionPointer failed to resolve function handle" + .to_string(), + ) + }) + .map_err(|e| set_err_info!(current_frame, e))?; + + if self.paranoid_type_checks { + self.check_friend_or_private_call(¤t_frame.function, &func)?; + } + // Charge gas let module_id = func .module_id() @@ -997,6 +1066,7 @@ enum ExitCode { Return, Call(FunctionHandleIndex), CallGeneric(FunctionInstantiationIndex), + CallFunctionPointer, } fn check_ability(has_ability: bool) -> PartialVMResult<()> { @@ -1045,7 +1115,7 @@ impl Frame { ) -> PartialVMResult<()> { match instruction { // Call instruction will be checked at execute_main. - Bytecode::Call(_) | Bytecode::CallGeneric(_) => (), + Bytecode::Call(_) | Bytecode::CallGeneric(_) | Bytecode::CallFunctionPointer(_) => (), Bytecode::BrFalse(_) | Bytecode::BrTrue(_) => { interpreter.operand_stack.pop_ty()?; } @@ -1138,7 +1208,9 @@ impl Frame { | Bytecode::VecPushBack(_) | Bytecode::VecPopBack(_) | Bytecode::VecUnpack(_, _) - | Bytecode::VecSwap(_) => (), + | Bytecode::VecSwap(_) + | Bytecode::GetFunctionPointer(_) + | Bytecode::GetFunctionPointerGeneric(_) => (), }; Ok(()) } @@ -1159,6 +1231,7 @@ impl Frame { | Bytecode::Ret | Bytecode::Call(_) | Bytecode::CallGeneric(_) + | Bytecode::CallFunctionPointer(_) | Bytecode::Abort => { // Invariants hold because all of the instructions above will force VM to break from the interpreter loop and thus not hit this code path. unreachable!("control flow instruction encountered during type check") @@ -1627,6 +1700,9 @@ impl Frame { .pop_ty()? .check_vec_ref(&ty, true)?; } + Bytecode::GetFunctionPointer(_) | Bytecode::GetFunctionPointerGeneric(_) => { + interpreter.operand_stack.push_ty(Type::Function)?; + } } Ok(()) } @@ -2223,6 +2299,47 @@ impl Frame { gas_meter.charge_vec_swap(make_ty!(ty))?; vec_ref.swap(idx1, idx2, ty)?; } + Bytecode::GetFunctionPointer(fh_idx) => { + let func = resolver.function_from_handle(*fh_idx); + let module_id = func + .module_id() + .ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message( + "Failed to get native function module id".to_string(), + ) + })? + .clone(); + + let func_name = func.identifier().clone(); + + interpreter.operand_stack.push(Value::function( + module_id, + func_name, + vec![], + ))?; + } + Bytecode::GetFunctionPointerGeneric(fi_idx) => { + let func = resolver.function_from_instantiation(*fi_idx); + let ty_args = + resolver.instantiate_generic_function(*fi_idx, self.ty_args())?; + let module_id = func + .module_id() + .ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message( + "Failed to get native function module id".to_string(), + ) + })? + .clone(); + + let func_name = func.identifier().clone(); + + interpreter + .operand_stack + .push(Value::function(module_id, func_name, ty_args))?; + } + Bytecode::CallFunctionPointer(_) => return Ok(ExitCode::CallFunctionPointer), } if interpreter.paranoid_type_checks { Self::post_execution_type_stack_transition( diff --git a/language/move-vm/runtime/src/loader.rs b/language/move-vm/runtime/src/loader.rs index 3b43e2d1d7..117f347061 100644 --- a/language/move-vm/runtime/src/loader.rs +++ b/language/move-vm/runtime/src/loader.rs @@ -407,6 +407,7 @@ impl ModuleCache { let def_idx = resolver(struct_name, &module_id)?; Type::StructInstantiation(def_idx, type_parameters) } + SignatureToken::Function(_func_ty) => Type::Function, }; Ok(res) } @@ -1355,6 +1356,8 @@ impl Loader { | Type::U256 | Type::Address => Ok(AbilitySet::PRIMITIVES), + Type::Function => Ok(AbilitySet::FUNCTION), + // Technically unreachable but, no point in erroring if we don't have to Type::Reference(_) | Type::MutableReference(_) => Ok(AbilitySet::REFERENCES), Type::Signer => Ok(AbilitySet::SIGNER), @@ -1441,6 +1444,17 @@ impl<'a> Resolver<'a> { self.loader.function_at(idx) } + pub(crate) fn function_from_name( + &self, + module_id: &ModuleId, + name: &Identifier, + ) -> Option> { + Some( + self.loader + .function_at(self.loader.get_module(module_id).function_by_name(name)?), + ) + } + pub(crate) fn function_from_instantiation( &self, idx: FunctionInstantiationIndex, @@ -1990,6 +2004,10 @@ impl Module { self.module.clone() } + pub(crate) fn function_by_name(&self, name: &Identifier) -> Option { + self.function_map.get(name).cloned() + } + fn field_offset(&self, idx: FieldHandleIndex) -> usize { self.field_handles[idx.0 as usize].offset } @@ -2347,6 +2365,10 @@ impl Function { self.name.as_str() } + pub(crate) fn identifier(&self) -> &Identifier { + &self.name + } + pub(crate) fn code(&self) -> &[Bytecode] { &self.code } @@ -2559,7 +2581,7 @@ impl Loader { Type::StructInstantiation(gidx, ty_args) => { TypeTag::Struct(Box::new(self.struct_gidx_to_type_tag(*gidx, ty_args)?)) } - Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => { + Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) | Type::Function => { return Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) .with_message(format!("no type tag for {:?}", ty)), @@ -2702,7 +2724,7 @@ impl Loader { self.struct_gidx_to_type_layout(*gidx, ty_args, count, depth)?, ) } - Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => { + Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) | Type::Function => { return Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) .with_message(format!("no type layout for {:?}", ty)), @@ -2797,7 +2819,7 @@ impl Loader { Type::StructInstantiation(gidx, ty_args) => MoveTypeLayout::Struct( self.struct_gidx_to_fully_annotated_layout(*gidx, ty_args, count, depth)?, ), - Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => { + Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) | Type::Function => { return Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) .with_message(format!("no type layout for {:?}", ty)), diff --git a/language/move-vm/test-utils/src/gas_schedule.rs b/language/move-vm/test-utils/src/gas_schedule.rs index 708c2935c0..a8221edf82 100644 --- a/language/move-vm/test-utils/src/gas_schedule.rs +++ b/language/move-vm/test-utils/src/gas_schedule.rs @@ -691,6 +691,9 @@ pub fn zero_cost_instruction_table() -> Vec<(Bytecode, GasCost)> { (CastU16, GasCost::new(0, 0)), (CastU32, GasCost::new(0, 0)), (CastU256, GasCost::new(0, 0)), + (GetFunctionPointer(FunctionHandleIndex::new(0)), GasCost::new(0, 0)), + (GetFunctionPointerGeneric(FunctionInstantiationIndex::new(0)), GasCost::new(0, 0)), + (CallFunctionPointer(SignatureIndex::new(0)), GasCost::new(0, 0)), ] } @@ -824,6 +827,9 @@ pub fn bytecode_instruction_costs() -> Vec<(Bytecode, GasCost)> { (CastU16, GasCost::new(2, 1)), (CastU32, GasCost::new(2, 1)), (CastU256, GasCost::new(2, 1)), + (GetFunctionPointer(FunctionHandleIndex::new(0)), GasCost::new(582, 0)), + (GetFunctionPointerGeneric(FunctionInstantiationIndex::new(0)), GasCost::new(1132, 0)), + (CallFunctionPointer(SignatureIndex::new(0)), GasCost::new(1132, 0)), ] } diff --git a/language/move-vm/transactional-tests/tests/function_pointers/abilities/copy.exp b/language/move-vm/transactional-tests/tests/function_pointers/abilities/copy.exp new file mode 100644 index 0000000000..5d92c423f3 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/abilities/copy.exp @@ -0,0 +1 @@ +processed 2 tasks diff --git a/language/move-vm/transactional-tests/tests/function_pointers/abilities/copy.mvir b/language/move-vm/transactional-tests/tests/function_pointers/abilities/copy.mvir new file mode 100644 index 0000000000..1e320a85a6 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/abilities/copy.mvir @@ -0,0 +1,20 @@ +//# publish +module 0x42.M { + public sum(a: u64, b: u64): u64 { + let c: u64; + label b0: + c = copy(a) + copy(b); + return copy(c); + } +} + +//# run +import 0x42.M; + +main() { + let func: |u64, u64| (u64); + let a: u64; +label b0: + func = get_function_pointer(M.sum); + return; +} diff --git a/language/move-vm/transactional-tests/tests/function_pointers/abilities/drop.exp b/language/move-vm/transactional-tests/tests/function_pointers/abilities/drop.exp new file mode 100644 index 0000000000..5d92c423f3 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/abilities/drop.exp @@ -0,0 +1 @@ +processed 2 tasks diff --git a/language/move-vm/transactional-tests/tests/function_pointers/abilities/drop.mvir b/language/move-vm/transactional-tests/tests/function_pointers/abilities/drop.mvir new file mode 100644 index 0000000000..cdf3cec330 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/abilities/drop.mvir @@ -0,0 +1,23 @@ +//# publish +module 0x42.M { + public sum(a: u64, b: u64): u64 { + let c: u64; + label b0: + c = copy(a) + copy(b); + return copy(c); + } +} + +//# run +import 0x42.M; + + +main() { + let func: |u64, u64| (u64); + let a: u64; +label b0: + func = get_function_pointer(M.sum); + a = call_function_pointer<|u64, u64| (u64)>(0, 1, copy(func)); + assert(move(a) == 1, 0); + return; +} \ No newline at end of file diff --git a/language/move-vm/transactional-tests/tests/function_pointers/abilities/store.exp b/language/move-vm/transactional-tests/tests/function_pointers/abilities/store.exp new file mode 100644 index 0000000000..cbd4038ee2 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/abilities/store.exp @@ -0,0 +1,10 @@ +processed 2 tasks + +task 1 'publish'. lines 11-16: +Error: Unable to publish module '00000000000000000000000000000042::F'. Got VMError: { + major_status: FIELD_MISSING_TYPE_ABILITY, + sub_status: None, + location: 0x42::F, + indices: [(StructDefinition, 0)], + offsets: [], +} diff --git a/language/move-vm/transactional-tests/tests/function_pointers/abilities/store.mvir b/language/move-vm/transactional-tests/tests/function_pointers/abilities/store.mvir new file mode 100644 index 0000000000..f5d149f3b0 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/abilities/store.mvir @@ -0,0 +1,16 @@ +//# publish +module 0x42.M { + public sum(a: u64, b: u64): u64 { + let c: u64; + label b0: + c = copy(a) + copy(b); + return copy(c); + } +} + +//# publish +module 0x42.F { + struct T has store { + f: |u64, u64| (u64), + } +} diff --git a/language/move-vm/transactional-tests/tests/function_pointers/example/coin_interface/coin_interface.exp b/language/move-vm/transactional-tests/tests/function_pointers/example/coin_interface/coin_interface.exp new file mode 100644 index 0000000000..f33e2b18fc --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/example/coin_interface/coin_interface.exp @@ -0,0 +1 @@ +processed 8 tasks diff --git a/language/move-vm/transactional-tests/tests/function_pointers/example/coin_interface/coin_interface.mvir b/language/move-vm/transactional-tests/tests/function_pointers/example/coin_interface/coin_interface.mvir new file mode 100644 index 0000000000..41336c5ca7 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/example/coin_interface/coin_interface.mvir @@ -0,0 +1,237 @@ +//# publish +module 0x42.CoinInterface { + struct T has drop { + value: |&T| (u64), + split: |&mut T, u64| (T), + merge: |&mut T, T| (), + } + + public new_interface(value: |&T| (u64), split: |&mut T, u64| (T), merge: |&mut T, T| ()): Self.T { + label b0: + return T { value: move(value), split: move(split), merge: move(merge)}; + } + + public value_func(self: &Self.T): |&T| (u64) { + label b0: + return *&move(self).T::value; + } + + public split_func(self: &Self.T): |&mut T, u64| (T) { + label b0: + return *&move(self).T::split; + } + + public merge_func(self: &Self.T): |&mut T, T| () { + label b0: + return *&move(self).T::merge; + } + + public value(self: &Self.T, coin: &T): u64 { + let value: |&T| (u64); + let ret: u64; + label b0: + value = *&move(self).T::value; + ret = call_function_pointer<|&T| (u64)>(move(coin), move(value)); + return move(ret); + } + + public split(self: &Self.T, coin: &mut T, amount: u64): T { + let split: |&mut T, u64| (T); + let ret: T; + label b0: + split = *&move(self).T::split; + ret = call_function_pointer<|&mut T, u64| (T)>(move(coin), move(amount), move(split)); + return move(ret); + } +} + +//# publish +module 0x42.BasicCoin1 { + import 0x42.CoinInterface; + + struct Coin has store, drop { value: u64 } + + public zero(): Self.Coin { + label b0: + return Coin { value: 0 }; + } + + public mint(value: u64): Self.Coin { + label b0: + return Coin { value: move(value) }; + } + + public value(c: &Self.Coin): u64 { + label b0: + return *&move(c).Coin::value; + } + + public merge(c: &mut Self.Coin, other: Self.Coin) { + let value: u64; + label b0: + Coin { value } = move(other); + *&mut move(c).Coin::value = (*©(c).Coin::value) + 1; + return; + } + + + public split(c: &mut Self.Coin, value: u64): Self.Coin { + let coin_value: u64; + label b0: + coin_value = *©(c).Coin::value; + assert(copy(coin_value) >= copy(value), 0); + *&mut copy(c).Coin::value = move(coin_value) - copy(value); + return Coin { value: move(value) }; + } + + public coin_interface(): CoinInterface.T> { + let value: |&Self.Coin| (u64); + let split: |&mut Self.Coin, u64| (Self.Coin); + let merge: |&mut Self.Coin, Self.Coin| (); + let interface: CoinInterface.T>; + label b0: + value = get_function_pointer(Self.value); + split = get_function_pointer(Self.split); + merge = get_function_pointer(Self.merge); + interface = CoinInterface.new_interface>(move(value), move(split), move(merge)); + return move(interface); + } +} + + +//# publish +module 0x42.BasicCoin2 { + import 0x42.CoinInterface; + + struct Coin has store, drop { value: u64 } + + public zero(): Self.Coin { + label b0: + return Coin { value: 0 }; + } + + public mint(value: u64): Self.Coin { + label b0: + return Coin { value: move(value) }; + } + + public value(c: &Self.Coin): u64 { + label b0: + return *&move(c).Coin::value; + } + + public merge(c: &mut Self.Coin, other: Self.Coin) { + let value: u64; + label b0: + Coin { value } = move(other); + *&mut move(c).Coin::value = (*©(c).Coin::value) + 1; + return; + } + + + public split(c: &mut Self.Coin, value: u64): Self.Coin { + let coin_value: u64; + label b0: + coin_value = *©(c).Coin::value; + assert(copy(coin_value) >= copy(value), 0); + *&mut copy(c).Coin::value = move(coin_value) - copy(value); + return Coin { value: move(value) }; + } + + public coin_interface(): CoinInterface.T { + let value: |&Self.Coin| (u64); + let split: |&mut Self.Coin, u64| (Self.Coin); + let merge: |&mut Self.Coin, Self.Coin| (); + let interface: CoinInterface.T; + label b0: + value = get_function_pointer(Self.value); + split = get_function_pointer(Self.split); + merge = get_function_pointer(Self.merge); + interface = CoinInterface.new_interface(move(value), move(split), move(merge)); + return move(interface); + } +} +//# publish +module 0x42.CoinType { + struct Foo has drop { + v: bool, + } +} + +//# publish +module 0x42.GenericAdder { + import 0x42.CoinInterface; + public add_coins(interface: &CoinInterface.T, coin1: &T, coin2: &T): u64 { + let v1: u64; + let v2: u64; + label b0: + v1 = CoinInterface.value(copy(interface), move(coin1)); + v2 = CoinInterface.value(move(interface), move(coin2)); + return move(v1) + move(v2); + } +} + +//# run +import 0x42.CoinInterface; +import 0x42.BasicCoin1; +import 0x42.GenericAdder; +import 0x42.CoinType; + +main() { + let interface: CoinInterface.T>; + let coin1: BasicCoin1.Coin; + let coin2: BasicCoin1.Coin; + let v: u64; +label b0: + coin1 = BasicCoin1.mint(10); + coin2 = BasicCoin1.mint(20); + interface = BasicCoin1.coin_interface(); + + v = GenericAdder.add_coins>(&interface, &coin1, &coin2); + assert(move(v) == 30, 0); + return; +} + +//# run +import 0x42.CoinInterface; +import 0x42.BasicCoin2; +import 0x42.GenericAdder; + +main() { + let interface: CoinInterface.T; + let coin1: BasicCoin2.Coin; + let coin2: BasicCoin2.Coin; + let v: u64; +label b0: + coin1 = BasicCoin2.mint(10); + coin2 = BasicCoin2.mint(20); + interface = BasicCoin2.coin_interface(); + + v = GenericAdder.add_coins(&interface, &coin1, &coin2); + assert(move(v) == 30, 0); + return; +} + + + +//# run +import 0x42.CoinInterface; +import 0x42.BasicCoin2; +import 0x42.GenericAdder; + +main() { + let interface: CoinInterface.T; + let coin1: BasicCoin2.Coin; + let coin2: BasicCoin2.Coin; + let v: u64; +label b0: + coin1 = BasicCoin2.mint(10); + interface = BasicCoin2.coin_interface(); + + coin2 = CoinInterface.split(&interface, &mut coin1, 5); + v = BasicCoin2.value(&coin2); + + assert(move(v) == 5, 0); + return; +} + diff --git a/language/move-vm/transactional-tests/tests/function_pointers/example/simple_get_and_call.exp b/language/move-vm/transactional-tests/tests/function_pointers/example/simple_get_and_call.exp new file mode 100644 index 0000000000..5d92c423f3 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/example/simple_get_and_call.exp @@ -0,0 +1 @@ +processed 2 tasks diff --git a/language/move-vm/transactional-tests/tests/function_pointers/example/simple_get_and_call.mvir b/language/move-vm/transactional-tests/tests/function_pointers/example/simple_get_and_call.mvir new file mode 100644 index 0000000000..584bdda427 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/example/simple_get_and_call.mvir @@ -0,0 +1,22 @@ +//# publish +module 0x42.M { + public sum(a: u64, b: u64): u64 { + let c: u64; + label b0: + c = copy(a) + copy(b); + return copy(c); + } +} + +//# run +import 0x42.M; + +main() { + let func: |u64, u64| (u64); + let a: u64; +label b0: + func = get_function_pointer(M.sum); + a = call_function_pointer<|u64, u64| (u64)>(0, 1, move(func)); + assert(move(a) == 1, 0); + return; +} diff --git a/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_no_return.exp b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_no_return.exp new file mode 100644 index 0000000000..5d92c423f3 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_no_return.exp @@ -0,0 +1 @@ +processed 2 tasks diff --git a/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_no_return.mvir b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_no_return.mvir new file mode 100644 index 0000000000..044fa742b4 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_no_return.mvir @@ -0,0 +1,18 @@ +//# publish +module 0x42.M { + public sum(a: u64, b: bool) { + label b0: + return; + } +} + +//# run +import 0x42.M; + +main() { + let func: |u64, bool| (); +label b0: + func = get_function_pointer(M.sum); + call_function_pointer<|u64, bool| ()>(0, true, move(func)); + return; +} diff --git a/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_ptr_mismatch.exp b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_ptr_mismatch.exp new file mode 100644 index 0000000000..9247bcf5c5 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_ptr_mismatch.exp @@ -0,0 +1,10 @@ +processed 2 tasks + +task 1 'run'. lines 9-21: +Error: Script execution failed with VMError: { + major_status: TYPE_MISMATCH, + sub_status: None, + location: script, + indices: [], + offsets: [(FunctionDefinitionIndex(0), 5)], +} diff --git a/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_ptr_mismatch.mvir b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_ptr_mismatch.mvir new file mode 100644 index 0000000000..64fcc7dab7 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/call_ptr_mismatch.mvir @@ -0,0 +1,21 @@ +//# publish +module 0x42.M { + public sum(a: u64, b: bool): bool * u64 { + label b0: + return move(b), move(a); + } +} + +//# run +import 0x42.M; + +main() { + let func: |u64, bool| (bool, u64); + let a: u64; + let b: bool; +label b0: + func = get_function_pointer(M.sum); + b = call_function_pointer<|u64, bool| (bool)>(0, true, move(func)); + + return; +} diff --git a/language/move-vm/transactional-tests/tests/function_pointers/type_checking/get_ptr_mismatch.exp b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/get_ptr_mismatch.exp new file mode 100644 index 0000000000..1061d5fdc2 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/get_ptr_mismatch.exp @@ -0,0 +1,10 @@ +processed 2 tasks + +task 1 'run'. lines 9-21: +Error: Script execution failed with VMError: { + major_status: STLOC_TYPE_MISMATCH_ERROR, + sub_status: None, + location: script, + indices: [], + offsets: [(FunctionDefinitionIndex(0), 1)], +} diff --git a/language/move-vm/transactional-tests/tests/function_pointers/type_checking/get_ptr_mismatch.mvir b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/get_ptr_mismatch.mvir new file mode 100644 index 0000000000..09e66308dc --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/get_ptr_mismatch.mvir @@ -0,0 +1,21 @@ +//# publish +module 0x42.M { + public sum(a: u64, b: bool): bool * u64 { + label b0: + return move(b), move(a); + } +} + +//# run +import 0x42.M; + +main() { + let func: |u64, bool| (bool); + let a: u64; + let b: bool; +label b0: + func = get_function_pointer(M.sum); + b = call_function_pointer<|u64, bool| (bool)>(0, true, move(func)); + + return; +} diff --git a/language/move-vm/transactional-tests/tests/function_pointers/type_checking/simple_call_args.exp b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/simple_call_args.exp new file mode 100644 index 0000000000..cce7e08941 --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/simple_call_args.exp @@ -0,0 +1,19 @@ +processed 4 tasks + +task 2 'run'. lines 25-39: +Error: Script execution failed with VMError: { + major_status: STLOC_TYPE_MISMATCH_ERROR, + sub_status: None, + location: script, + indices: [], + offsets: [(FunctionDefinitionIndex(0), 6)], +} + +task 3 'run'. lines 41-55: +Error: Script execution failed with VMError: { + major_status: CALL_TYPE_MISMATCH_ERROR, + sub_status: None, + location: script, + indices: [], + offsets: [(FunctionDefinitionIndex(0), 5)], +} diff --git a/language/move-vm/transactional-tests/tests/function_pointers/type_checking/simple_call_args.mvir b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/simple_call_args.mvir new file mode 100644 index 0000000000..fbfcf61c0a --- /dev/null +++ b/language/move-vm/transactional-tests/tests/function_pointers/type_checking/simple_call_args.mvir @@ -0,0 +1,55 @@ +//# publish +module 0x42.M { + public sum(a: u64, b: bool): bool * u64 { + label b0: + return move(b), move(a); + } +} + +//# run +import 0x42.M; + +main() { + let func: |u64, bool| (bool, u64); + let a: u64; + let b: bool; +label b0: + func = get_function_pointer(M.sum); + b, a = call_function_pointer<|u64, bool| (bool, u64)>(0, true, move(func)); + + assert(move(a) == 0, 0); + assert(move(b) == true, 0); + return; +} + +//# run +import 0x42.M; + +main() { + let func: |u64, bool| (bool, u64); + let a: u64; + let b: bool; +label b0: + func = get_function_pointer(M.sum); + a, b = call_function_pointer<|u64, bool| (bool, u64)>(0, true, move(func)); + + assert(move(a) == 0, 0); + assert(move(b) == true, 0); + return; +} + +//# run +import 0x42.M; + +main() { + let func: |u64, bool| (bool, u64); + let a: u64; + let b: bool; +label b0: + func = get_function_pointer(M.sum); + b, a = call_function_pointer<|u64, bool| (bool, u64)>(true, 0, move(func)); + + assert(move(a) == 0, 0); + assert(move(b) == true, 0); + return; +} \ No newline at end of file diff --git a/language/move-vm/types/src/loaded_data/runtime_types.rs b/language/move-vm/types/src/loaded_data/runtime_types.rs index a8d1addd43..42a54c053f 100644 --- a/language/move-vm/types/src/loaded_data/runtime_types.rs +++ b/language/move-vm/types/src/loaded_data/runtime_types.rs @@ -50,6 +50,7 @@ pub enum Type { U16, U32, U256, + Function, } impl Type { @@ -88,6 +89,7 @@ impl Type { } Type::StructInstantiation(*def_idx, inst) } + Type::Function => Type::Function, }; Ok(res) } @@ -126,6 +128,8 @@ impl Type { Vector(ty) | Reference(ty) | MutableReference(ty) => { Self::LEGACY_BASE_MEMORY_SIZE + ty.size() } + // TODO: is this sizing appropriate? + Function => Self::LEGACY_BASE_MEMORY_SIZE, Struct(_) => Self::LEGACY_BASE_MEMORY_SIZE, StructInstantiation(_, tys) => tys .iter() @@ -155,7 +159,11 @@ impl Type { ) } // Not allowed/Not meaningful - S::TypeParameter(_) | S::Reference(_) | S::MutableReference(_) | S::Signer => { + S::TypeParameter(_) + | S::Reference(_) + | S::MutableReference(_) + | S::Signer + | S::Function(_) => { return Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) .with_message("Unable to load const type signature".to_string()), diff --git a/language/move-vm/types/src/values/values_impl.rs b/language/move-vm/types/src/values/values_impl.rs index 412c1848bd..f2fa268436 100644 --- a/language/move-vm/types/src/values/values_impl.rs +++ b/language/move-vm/types/src/values/values_impl.rs @@ -14,6 +14,8 @@ use move_core_types::{ account_address::AccountAddress, effects::Op, gas_algebra::AbstractMemorySize, + identifier::Identifier, + language_storage::ModuleId, u256, value::{MoveStructLayout, MoveTypeLayout}, vm_status::{sub_status::NFE_VECTOR_ERROR_BASE, StatusCode}, @@ -53,6 +55,7 @@ enum ValueImpl { ContainerRef(ContainerRef), IndexedRef(IndexedRef), + Function(ModuleId, Identifier, Vec), } /// A container is a collection of values. It is used to represent data structures like a @@ -358,6 +361,9 @@ impl ValueImpl { // When cloning a container, we need to make sure we make a deep // copy of the data instead of a shallow copy of the Rc. Container(c) => Container(c.copy_value()?), + Function(id, func_name, ty_args) => { + Function(id.clone(), func_name.clone(), ty_args.clone()) + } }) } } @@ -474,6 +480,9 @@ impl ValueImpl { (ContainerRef(l), ContainerRef(r)) => l.equals(r)?, (IndexedRef(l), IndexedRef(r)) => l.equals(r)?, + (Function(lid, lname, lty_args), Function(rid, rname, rty_args)) => { + lid == rid && lname == rname && lty_args == rty_args + } (Invalid, _) | (U8(_), _) | (U16(_), _) @@ -485,7 +494,8 @@ impl ValueImpl { | (Address(_), _) | (Container(_), _) | (ContainerRef(_), _) - | (IndexedRef(_), _) => { + | (IndexedRef(_), _) + | (Function(_, _, _), _) => { return Err(PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR) .with_message(format!("cannot compare values: {:?}, {:?}", self, other))) } @@ -951,10 +961,13 @@ impl Locals { idx, }))), - ValueImpl::ContainerRef(_) | ValueImpl::Invalid | ValueImpl::IndexedRef(_) => Err( - PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) - .with_message(format!("cannot borrow local {:?}", &v[idx])), - ), + ValueImpl::ContainerRef(_) + | ValueImpl::Invalid + | ValueImpl::IndexedRef(_) + | ValueImpl::Function(_, _, _) => Err(PartialVMError::new( + StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, + ) + .with_message(format!("cannot borrow local {:?}", &v[idx]))), } } } @@ -1181,6 +1194,17 @@ impl Value { it.into_iter().map(|v| v.0).collect(), ))))) } + + pub fn function(module_id: ModuleId, func_name: Identifier, ty_args: Vec) -> Self { + Self(ValueImpl::Function(module_id, func_name, ty_args)) + } + + pub fn as_function(self) -> Option<(ModuleId, Identifier, Vec)> { + match self.0 { + ValueImpl::Function(id, func_name, tys) => Some((id, func_name, tys)), + _ => None, + } + } } /*************************************************************************************** @@ -1958,7 +1982,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) @@ -1976,7 +2001,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!( @@ -2186,11 +2212,13 @@ impl Vector { .collect::>>()?, ), - Type::Signer | Type::Vector(_) | Type::Struct(_) | Type::StructInstantiation(_, _) => { - Value(ValueImpl::Container(Container::Vec(Rc::new(RefCell::new( - elements.into_iter().map(|v| v.0).collect(), - ))))) - } + Type::Signer + | Type::Vector(_) + | Type::Struct(_) + | 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( @@ -2606,6 +2634,9 @@ impl Display for ValueImpl { Self::ContainerRef(r) => write!(f, "{}", r), Self::IndexedRef(r) => write!(f, "{}", r), + Self::Function(id, fh_idx, ty_args) => { + write!(f, "func({:?}.{:?}<{:?}>)", id, fh_idx, ty_args) + } } } } @@ -2774,6 +2805,9 @@ pub mod debug { ValueImpl::ContainerRef(r) => print_container_ref(buf, r), ValueImpl::IndexedRef(r) => print_indexed_ref(buf, r), + ValueImpl::Function(id, fh_idx, ty_args) => { + debug_write!(buf, "func({}.{}<{:?}>)", id, fh_idx, ty_args) + } } } @@ -3210,7 +3244,9 @@ impl Value { // Not yet supported S::Struct(_) | S::StructInstantiation(_, _) => return None, // Not allowed/Not meaningful - S::TypeParameter(_) | S::Reference(_) | S::MutableReference(_) => return None, + S::TypeParameter(_) | S::Reference(_) | S::MutableReference(_) | S::Function(_) => { + return None + } }) } @@ -3338,6 +3374,7 @@ impl ValueImpl { ContainerRef(r) => r.visit_impl(visitor, depth), IndexedRef(r) => r.visit_impl(visitor, depth), + Function(_, _, _) => (), } } } diff --git a/language/tools/move-bytecode-utils/src/layout.rs b/language/tools/move-bytecode-utils/src/layout.rs index c054adff1e..c063159079 100644 --- a/language/tools/move-bytecode-utils/src/layout.rs +++ b/language/tools/move-bytecode-utils/src/layout.rs @@ -169,7 +169,7 @@ impl<'a, T: GetModule> SerdeLayoutBuilder<'a, T> { } } TypeParameter(i) => input_type_args[*i as usize].clone(), - Reference(_) | MutableReference(_) => unreachable!(), // structs cannot store references + Reference(_) | MutableReference(_) | Function { .. } => unreachable!(), // structs cannot store references }) } @@ -415,7 +415,9 @@ impl TypeLayoutBuilder { U256 => MoveTypeLayout::U256, Address => MoveTypeLayout::Address, Signer => bail!("Type layouts cannot contain signer"), - Reference(_) | MutableReference(_) => bail!("Type layouts cannot contain references"), + Reference(_) | MutableReference(_) | Function { .. } => { + bail!("Type layouts cannot contain references") + } }) } } diff --git a/language/tools/move-disassembler/src/disassembler.rs b/language/tools/move-disassembler/src/disassembler.rs index d2d176fb5b..519b74de61 100644 --- a/language/tools/move-disassembler/src/disassembler.rs +++ b/language/tools/move-disassembler/src/disassembler.rs @@ -531,6 +531,7 @@ impl<'a> Disassembler<'a> { })? .0 .to_string(), + SignatureToken::Function(_) => unimplemented!(), }) } diff --git a/language/tools/move-resource-viewer/src/resolver.rs b/language/tools/move-resource-viewer/src/resolver.rs index 6550abbcb2..1344b2637e 100644 --- a/language/tools/move-resource-viewer/src/resolver.rs +++ b/language/tools/move-resource-viewer/src/resolver.rs @@ -176,6 +176,7 @@ impl<'a, T: MoveResolver + ?Sized> Resolver<'a, T> { SignatureToken::Signer => FatType::Reference(Box::new(FatType::Signer)), _ => return Err(anyhow!("Unexpected Reference")), }, + SignatureToken::Function(_) => return Err(anyhow!("Unexpected Function Type")), }) }