From 9688bde2137edce3a8e561632b7b0fef91667b67 Mon Sep 17 00:00:00 2001 From: Wolfgang Grieskamp Date: Sun, 3 Nov 2024 07:30:41 -0800 Subject: [PATCH] [move-vm][closures] Types and opcodes for closures This PR implements the extensions needed in the file format for representing closures: - The type (SignatureToken) has a new variant `Function(args, result, abilities)` to represent a function type - The opcodes are extendeed by operations `ClosPack`, `ClosPackGeneric`, and `ClosEval` This supports bit masks for specifyinng which arguments of a function are captured when creating a closure. Bytecode verification is extended to support the new types and opcodes. The implementation of consistency, type, and reference safety should be complete. What is missing are: - File format serialization - Interpreter and value representation - Value serialization - Connection of the new types with the various other type representations --- api/types/src/bytecode.rs | 4 + aptos-move/script-composer/src/decompiler.rs | 3 + .../move-binary-format/src/binary_views.rs | 1 + .../move/move-binary-format/src/builders.rs | 16 +- .../move-binary-format/src/check_bounds.rs | 9 +- .../src/check_complexity.rs | 8 +- .../move/move-binary-format/src/constant.rs | 4 + .../move-binary-format/src/deserializer.rs | 20 +- .../move-binary-format/src/file_format.rs | 220 +++++++++++++++++- .../src/file_format_common.rs | 9 +- .../move/move-binary-format/src/normalized.rs | 2 + .../src/proptest_types/functions.rs | 2 +- .../src/proptest_types/types.rs | 1 + .../move/move-binary-format/src/serializer.rs | 21 ++ .../move/move-bytecode-spec/src/lib.rs | 1 + .../invalid-mutations/src/bounds.rs | 2 +- .../invalid-mutations/src/bounds/code_unit.rs | 8 +- .../src/acquires_list_verifier.rs | 5 +- .../src/dependencies.rs | 13 ++ .../src/instantiation_loops.rs | 8 + .../src/instruction_consistency.rs | 39 +++- .../src/locals_safety/mod.rs | 3 + .../src/reference_safety/abstract_state.rs | 23 +- .../src/reference_safety/mod.rs | 69 +++++- .../move-bytecode-verifier/src/signature.rs | 17 ++ .../src/signature_v2.rs | 28 ++- .../src/stack_usage_verifier.rs | 39 +++- .../move-bytecode-verifier/src/struct_defs.rs | 5 + .../move-bytecode-verifier/src/type_safety.rs | 147 +++++++++++- .../move-compiler/src/interface_generator.rs | 16 +- .../move/move-core/types/src/vm_status.rs | 23 +- .../move-ir-to-bytecode/src/context.rs | 3 + .../src/stackless_bytecode_generator.rs | 5 + third_party/move/move-model/src/ty.rs | 4 + .../move/move-vm/runtime/src/interpreter.rs | 8 + .../move-vm/runtime/src/loader/type_loader.rs | 10 +- .../runtime/src/runtime_type_checks.rs | 15 ++ .../move-vm/test-utils/src/gas_schedule.rs | 22 +- .../move-vm/types/src/values/values_impl.rs | 2 +- .../tools/move-bytecode-utils/src/layout.rs | 1 + .../move-disassembler/src/disassembler.rs | 8 + .../tools/move-resource-viewer/src/lib.rs | 3 + 42 files changed, 778 insertions(+), 69 deletions(-) diff --git a/api/types/src/bytecode.rs b/api/types/src/bytecode.rs index 0f9a40e243805c..f6ed8405471a40 100644 --- a/api/types/src/bytecode.rs +++ b/api/types/src/bytecode.rs @@ -105,6 +105,10 @@ pub trait Bytecode { mutable: true, to: Box::new(self.new_move_type(t.borrow())), }, + SignatureToken::Function(..) => { + // TODO + unimplemented!("signature token function to API MoveType") + }, } } diff --git a/aptos-move/script-composer/src/decompiler.rs b/aptos-move/script-composer/src/decompiler.rs index 8d9ee51ea2203d..56806f589ab082 100644 --- a/aptos-move/script-composer/src/decompiler.rs +++ b/aptos-move/script-composer/src/decompiler.rs @@ -208,6 +208,9 @@ impl LocalState { SignatureToken::Vector(s) => { TypeTag::Vector(Box::new(Self::type_tag_from_sig_token(script, s)?)) }, + SignatureToken::Function(..) => { + bail!("function types NYI for script composer") + }, SignatureToken::Struct(s) => { let module_handle = script.module_handle_at(script.struct_handle_at(*s).module); TypeTag::Struct(Box::new(StructTag { diff --git a/third_party/move/move-binary-format/src/binary_views.rs b/third_party/move/move-binary-format/src/binary_views.rs index 840754c3a565f8..74c9325c7e2e9b 100644 --- a/third_party/move/move-binary-format/src/binary_views.rs +++ b/third_party/move/move-binary-format/src/binary_views.rs @@ -343,6 +343,7 @@ impl<'a> BinaryIndexedView<'a> { Vector(ty) => AbilitySet::polymorphic_abilities(AbilitySet::VECTOR, vec![false], vec![ self.abilities(ty, constraints)?, ]), + Function(_, _, abilities) => Ok(*abilities), Struct(idx) => { let sh = self.struct_handle_at(*idx); Ok(sh.abilities) diff --git a/third_party/move/move-binary-format/src/builders.rs b/third_party/move/move-binary-format/src/builders.rs index 17fae53208aede..b78ba64c23707d 100644 --- a/third_party/move/move-binary-format/src/builders.rs +++ b/third_party/move/move-binary-format/src/builders.rs @@ -213,6 +213,12 @@ impl CompiledScriptBuilder { sig: &SignatureToken, ) -> PartialVMResult { use SignatureToken::*; + let import_vec = + |s: &mut Self, v: &[SignatureToken]| -> PartialVMResult> { + v.iter() + .map(|sig| s.import_signature_token(module, sig)) + .collect::>>() + }; Ok(match sig { U8 => U8, U16 => U16, @@ -229,13 +235,15 @@ impl CompiledScriptBuilder { MutableReference(Box::new(self.import_signature_token(module, ty)?)) }, Vector(ty) => Vector(Box::new(self.import_signature_token(module, ty)?)), + Function(args, result, abilities) => Function( + import_vec(self, args)?, + import_vec(self, result)?, + *abilities, + ), Struct(idx) => Struct(self.import_struct(module, *idx)?), StructInstantiation(idx, inst_tys) => StructInstantiation( self.import_struct(module, *idx)?, - inst_tys - .iter() - .map(|sig| self.import_signature_token(module, sig)) - .collect::>>()?, + import_vec(self, inst_tys)?, ), }) } diff --git a/third_party/move/move-binary-format/src/check_bounds.rs b/third_party/move/move-binary-format/src/check_bounds.rs index 91e52ee07fb5e7..1406085ac4c7a8 100644 --- a/third_party/move/move-binary-format/src/check_bounds.rs +++ b/third_party/move/move-binary-format/src/check_bounds.rs @@ -546,12 +546,12 @@ impl<'a> BoundsChecker<'a> { )?; } }, - Call(idx) => self.check_code_unit_bounds_impl( + Call(idx) | PackClosure(idx, _) => self.check_code_unit_bounds_impl( self.view.function_handles(), *idx, bytecode_offset, )?, - CallGeneric(idx) => { + CallGeneric(idx) | PackClosureGeneric(idx, _) => { self.check_code_unit_bounds_impl( self.view.function_instantiations(), *idx, @@ -650,7 +650,8 @@ impl<'a> BoundsChecker<'a> { }, // Instructions that refer to a signature - VecPack(idx, _) + CallClosure(idx) + | VecPack(idx, _) | VecLen(idx) | VecImmBorrow(idx) | VecMutBorrow(idx) @@ -684,7 +685,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()) { diff --git a/third_party/move/move-binary-format/src/check_complexity.rs b/third_party/move/move-binary-format/src/check_complexity.rs index 232d530404cc97..4385547d4d796e 100644 --- a/third_party/move/move-binary-format/src/check_complexity.rs +++ b/third_party/move/move-binary-format/src/check_complexity.rs @@ -68,7 +68,7 @@ impl<'a> BinaryComplexityMeter<'a> { cost = cost.saturating_add(moduel_name.len() as u64 * COST_PER_IDENT_BYTE); }, U8 | U16 | U32 | U64 | U128 | U256 | Signer | Address | Bool | Vector(_) - | TypeParameter(_) | Reference(_) | MutableReference(_) => (), + | Function(..) | TypeParameter(_) | Reference(_) | MutableReference(_) => (), } } @@ -262,7 +262,7 @@ impl<'a> BinaryComplexityMeter<'a> { for instr in &code.code { match instr { - CallGeneric(idx) => { + CallGeneric(idx) | PackClosureGeneric(idx, ..) => { self.meter_function_instantiation(*idx)?; }, PackGeneric(idx) | UnpackGeneric(idx) => { @@ -284,7 +284,8 @@ impl<'a> BinaryComplexityMeter<'a> { ImmBorrowVariantFieldGeneric(idx) | MutBorrowVariantFieldGeneric(idx) => { self.meter_variant_field_instantiation(*idx)?; }, - VecPack(idx, _) + CallClosure(idx) + | VecPack(idx, _) | VecLen(idx) | VecImmBorrow(idx) | VecMutBorrow(idx) @@ -323,6 +324,7 @@ impl<'a> BinaryComplexityMeter<'a> { | PackVariant(_) | UnpackVariant(_) | TestVariant(_) + | PackClosure(..) | ReadRef | WriteRef | FreezeRef diff --git a/third_party/move/move-binary-format/src/constant.rs b/third_party/move/move-binary-format/src/constant.rs index 991bee29624f78..6fa8b21fa11fbd 100644 --- a/third_party/move/move-binary-format/src/constant.rs +++ b/third_party/move/move-binary-format/src/constant.rs @@ -17,6 +17,10 @@ fn sig_to_ty(sig: &SignatureToken) -> Option { SignatureToken::U128 => Some(MoveTypeLayout::U128), SignatureToken::U256 => Some(MoveTypeLayout::U256), SignatureToken::Vector(v) => Some(MoveTypeLayout::Vector(Box::new(sig_to_ty(v.as_ref())?))), + SignatureToken::Function(..) => { + // TODO: do we need representation in MoveTypeLayout? + None + }, SignatureToken::Reference(_) | SignatureToken::MutableReference(_) | SignatureToken::Struct(_) diff --git a/third_party/move/move-binary-format/src/deserializer.rs b/third_party/move/move-binary-format/src/deserializer.rs index 89a78a2562c6ec..1f8712f994ba6a 100644 --- a/third_party/move/move-binary-format/src/deserializer.rs +++ b/third_party/move/move-binary-format/src/deserializer.rs @@ -321,6 +321,10 @@ fn load_struct_variant_inst_index( )?)) } +fn load_closure_mask(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + Ok(ClosureMask::new(read_uleb_internal(cursor, u64::MAX)?)) +} + fn load_constant_pool_index(cursor: &mut VersionedCursor) -> BinaryLoaderResult { Ok(ConstantPoolIndex(read_uleb_internal( cursor, @@ -1745,6 +1749,15 @@ fn load_code(cursor: &mut VersionedCursor, code: &mut Vec) -> BinaryLo Opcodes::TEST_VARIANT_GENERIC => { Bytecode::TestVariantGeneric(load_struct_variant_inst_index(cursor)?) }, + Opcodes::PACK_CLOSURE => Bytecode::PackClosure( + load_function_handle_index(cursor)?, + load_closure_mask(cursor)?, + ), + Opcodes::PACK_CLOSURE_GENERIC => Bytecode::PackClosureGeneric( + load_function_inst_index(cursor)?, + load_closure_mask(cursor)?, + ), + Opcodes::CALL_CLOSURE => Bytecode::CallClosure(load_signature_index(cursor)?), Opcodes::READ_REF => Bytecode::ReadRef, Opcodes::WRITE_REF => Bytecode::WriteRef, Opcodes::ADD => Bytecode::Add, @@ -2011,7 +2024,12 @@ impl Opcodes { 0x55 => Ok(Opcodes::UNPACK_VARIANT_GENERIC), 0x56 => Ok(Opcodes::TEST_VARIANT), 0x57 => Ok(Opcodes::TEST_VARIANT_GENERIC), - _ => Err(PartialVMError::new(StatusCode::UNKNOWN_OPCODE)), + // Since bytecode version 8 + 0x58 => Ok(Opcodes::PACK_CLOSURE), + 0x59 => Ok(Opcodes::PACK_CLOSURE_GENERIC), + 0x5A => Ok(Opcodes::CALL_CLOSURE), + _ => Err(PartialVMError::new(StatusCode::UNKNOWN_OPCODE) + .with_message(format!("code {:X}", value))), } } } diff --git a/third_party/move/move-binary-format/src/file_format.rs b/third_party/move/move-binary-format/src/file_format.rs index 468bdcca9fd326..5cd43f451b224d 100644 --- a/third_party/move/move-binary-format/src/file_format.rs +++ b/third_party/move/move-binary-format/src/file_format.rs @@ -49,6 +49,7 @@ use proptest::{collection::vec, prelude::*, strategy::BoxedStrategy}; use ref_cast::RefCast; use serde::{Deserialize, Serialize}; use std::{ + collections::BTreeMap, fmt::{self, Formatter}, ops::BitOr, }; @@ -1254,6 +1255,8 @@ pub enum SignatureToken { Signer, /// Vector Vector(Box), + /// Function, with n argument types and m result types, and an associated ability set. + Function(Vec, Vec, AbilitySet), /// User defined type Struct(StructHandleIndex), StructInstantiation(StructHandleIndex, Vec), @@ -1296,6 +1299,11 @@ impl<'a> Iterator for SignatureTokenPreorderTraversalIter<'a> { self.stack.extend(inner_toks.iter().rev()) }, + Function(args, result, _) => { + self.stack.extend(args.iter().rev()); + self.stack.extend(result.iter().rev()); + }, + Signer | Bool | Address | U8 | U16 | U32 | U64 | U128 | U256 | Struct(_) | TypeParameter(_) => (), } @@ -1329,6 +1337,13 @@ impl<'a> Iterator for SignatureTokenPreorderTraversalIterWithDepth<'a> { .stack .extend(inner_toks.iter().map(|tok| (tok, depth + 1)).rev()), + Function(args, result, _) => { + self.stack + .extend(args.iter().map(|tok| (tok, depth + 1)).rev()); + self.stack + .extend(result.iter().map(|tok| (tok, depth + 1)).rev()); + }, + Signer | Bool | Address | U8 | U16 | U32 | U64 | U128 | U256 | Struct(_) | TypeParameter(_) => (), } @@ -1389,11 +1404,14 @@ impl std::fmt::Debug for SignatureToken { SignatureToken::Address => write!(f, "Address"), SignatureToken::Signer => write!(f, "Signer"), SignatureToken::Vector(boxed) => write!(f, "Vector({:?})", boxed), + SignatureToken::Function(args, result, abilities) => { + write!(f, "Function({:?}, {:?}, {})", args, result, abilities) + }, + SignatureToken::Reference(boxed) => write!(f, "Reference({:?})", boxed), SignatureToken::Struct(idx) => write!(f, "Struct({:?})", idx), SignatureToken::StructInstantiation(idx, types) => { write!(f, "StructInstantiation({:?}, {:?})", idx, types) }, - SignatureToken::Reference(boxed) => write!(f, "Reference({:?})", boxed), SignatureToken::MutableReference(boxed) => write!(f, "MutableReference({:?})", boxed), SignatureToken::TypeParameter(idx) => write!(f, "TypeParameter({:?})", idx), } @@ -1410,6 +1428,7 @@ impl SignatureToken { | Address | Signer | Vector(_) + | Function(..) | Struct(_) | StructInstantiation(_, _) | Reference(_) @@ -1448,6 +1467,7 @@ impl SignatureToken { Bool | U8 | U16 | U32 | U64 | U128 | U256 | Address => true, Vector(inner) => inner.is_valid_for_constant(), Signer + | Function(..) | Struct(_) | StructInstantiation(_, _) | Reference(_) @@ -1490,6 +1510,9 @@ impl SignatureToken { pub fn instantiate(&self, subst_mapping: &[SignatureToken]) -> SignatureToken { use SignatureToken::*; + let inst_vec = |v: &[SignatureToken]| -> Vec { + v.iter().map(|ty| ty.instantiate(subst_mapping)).collect() + }; match self { Bool => Bool, U8 => U8, @@ -1501,14 +1524,13 @@ impl SignatureToken { Address => Address, Signer => Signer, Vector(ty) => Vector(Box::new(ty.instantiate(subst_mapping))), + Function(args, result, abilities) => { + Function(inst_vec(args), inst_vec(result), *abilities) + }, Struct(idx) => Struct(*idx), - StructInstantiation(idx, struct_type_args) => StructInstantiation( - *idx, - struct_type_args - .iter() - .map(|ty| ty.instantiate(subst_mapping)) - .collect(), - ), + StructInstantiation(idx, struct_type_args) => { + StructInstantiation(*idx, inst_vec(struct_type_args)) + }, Reference(ty) => Reference(Box::new(ty.instantiate(subst_mapping))), MutableReference(ty) => MutableReference(Box::new(ty.instantiate(subst_mapping))), TypeParameter(idx) => subst_mapping[*idx as usize].clone(), @@ -1516,6 +1538,105 @@ impl SignatureToken { } } +/// A `ClosureMask` is a value which determines how to distinguish those function arguments +/// which are captured and which are not when a closure is constructed. For instance, +/// with `_` representing an omitted argument, the mask for `f(a,_,b,_)` would have the argument +/// at index 0 and at index 2 captured. The mask can be used to transform lists of types. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(any(test, feature = "fuzzing"), derive(proptest_derive::Arbitrary))] +#[cfg_attr(any(test, feature = "fuzzing"), proptest(no_params))] +#[cfg_attr( + feature = "fuzzing", + derive(arbitrary::Arbitrary), + derive(dearbitrary::Dearbitrary) +)] +pub struct ClosureMask { + pub mask: u64, +} + +impl fmt::Display for ClosureMask { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:b}", self.mask) + } +} + +impl ClosureMask { + pub fn new(mask: u64) -> Self { + Self { mask } + } + + /// Apply a closure mask to a list of elements, returning only those + /// where position `i` is set in the mask (if `collect_captured` is true) or not + /// set (otherwise). + pub fn extract(&self, tys: &[T], collect_captured: bool) -> Vec { + tys.iter() + .enumerate() + .filter_map(|(pos, x)| { + let set = (1 << pos) & self.mask != 0; + if set && collect_captured || !set && !collect_captured { + Some(x.clone()) + } else { + None + } + }) + .collect() + } + + /// Compose two lists of elements into one based on the given mask such that the + /// following holds: + /// ```ignore + /// mask.compose(mask.extract(v, true), mask.extract(v, false)) == v + /// ``` + /// This returns `None` if the provided lists are inconsistent w.r.t the mask + /// and cannot be composed. This should not happen in verified code, but + /// a caller should decide whether to crash or to error. + pub fn compose(&self, captured: &[T], provided: &[T]) -> Option> { + let mut result = BTreeMap::new(); // expect ordered enumeration + let mut cap_idx = 0; + let mut pro_idx = 0; + for i in 0..64 { + if cap_idx >= captured.len() && pro_idx >= provided.len() { + // all covered + break; + } + if (1 << i) & self.mask != 0 { + if cap_idx >= captured.len() { + // Inconsistency + return None; + } + result.insert(i, captured[cap_idx].clone()); + cap_idx += 1 + } else { + if pro_idx >= provided.len() { + // Inconsistency + return None; + } + result.insert(i, provided[pro_idx].clone()); + pro_idx += 1 + } + } + let map_len = result.len(); + let vec = result.into_values().collect::>(); + if vec.len() != map_len { + // Inconsistency: all indices must be contiguously covered + None + } else { + Some(vec) + } + } + + /// Return the max index of captured arguments + pub fn max_captured(&self) -> usize { + let mut i = 0; + let mut mask = self.mask; + while mask != 0 { + mask >>= 1; + i += 1 + } + i + } +} + /// A `Constant` is a serialized value along with its type. That type will be deserialized by the /// loader/evaluator #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -1896,7 +2017,6 @@ pub enum Bytecode { #[gas_type_creation_tier_1 = "field_tys"] PackVariantGeneric(StructVariantInstantiationIndex), - //TODO: Unpack, Test #[group = "struct"] #[static_operands = "[struct_def_idx]"] #[description = "Destroy an instance of a struct and push the values bound to each field onto the stack."] @@ -2934,6 +3054,83 @@ pub enum Bytecode { "#] VecSwap(SignatureIndex), + #[group = "closure"] + #[description = r#" + `PackClosure(fun, mask)` creates a closure for a given function handle as controlled by + the given `mask`. `mask` is a u64 bitset which describes which of the arguments + of `fun` are captured by the closure. + + If the function `fun` has type `|t1..tn|r`, then the following holds: + + - If `m` are the number of bits set in the mask, then `m <= n`, and the stack is + `[vm..v1] + stack`, and if `i` is the `j`th bit set in the mask, + then `vj` has type `ti`. + - type ti is not a reference. + + Thus the values on the stack must match the types in the function + signature which have the bit to be captured set in the mask. + + The type of the resulting value on the stack is derived from the types `|t1..tn|` + for which the bit is not set, which build the arguments of a function type + with `fun`'s result types. + + The `abilities` of this function type are derived from the inputs as follows. + First, take the intersection of the abilities of all captured arguments + with type `t1..tn`. Then intersect this with the abilities derived from the + function: a function handle has `drop` and `copy`, never has `key`, and only + `store` if the underlying function is public, and therefore cannot change + its signature. + + Notice that an implementation can derive the types of the captured arguments + at runtime from a closure value as long as the closure value stores the function + handle (or a derived form of it) and the mask, where the handle allows to lookup the + function's type at runtime. Then the same procedure as outlined above can be used. + "#] + #[static_operands = "[fun, mask]"] + #[semantics = ""] + #[runtime_check_epilogue = ""] + #[gas_type_creation_tier_0 = "closure_ty"] + PackClosure(FunctionHandleIndex, ClosureMask), + + #[group = "closure"] + #[static_operands = "[fun, mask]"] + #[semantics = ""] + #[runtime_check_epilogue = ""] + #[description = r#" + Same as `PackClosure` but for the instantiation of a generic function. + + Notice that an uninstantiated generic function cannot be used to create a closure. + "#] + #[gas_type_creation_tier_0 = "closure_ty"] + PackClosureGeneric(FunctionInstantiationIndex, ClosureMask), + + #[group = "closure"] + #[description = r#" + `ClosEval(|t1..tn|r has a)` evalutes a closure of the given function type, taking + the captured arguments and mixing in the provided ones on the stack. + + On top of the stack is the closure being evaluated, underneath the arguments: + `[c,vn,..,v1] + stack`. The type of the closure must match the type specified in + the instruction, with abilities `a` a subset of the abilities of the closure value. + A value `vi` on the stack must have type `ti`. + + Notice that the type as part of the closure instruction is redundant for + execution semantics. Since the closure is expected to be on top of the stack, + it can decode the arguments underneath without type information. + However, the type is required to do static bytecode verification. + + The semantics of this instruction can be characterized by the following equation: + + ``` + CloseEval(ClosPack(f, mask, c1..cn), a1..am) = f(mask.compose(c1..cn, a1..am)) + ``` + "#] + #[static_operands = "[]"] + #[semantics = ""] + #[runtime_check_epilogue = ""] + #[gas_type_creation_tier_0 = "closure_ty"] + CallClosure(SignatureIndex), + #[group = "stack_and_local"] #[description = "Push a u16 constant onto the stack."] #[static_operands = "[u16_value]"] @@ -3044,6 +3241,11 @@ impl ::std::fmt::Debug for Bytecode { Bytecode::UnpackGeneric(a) => write!(f, "UnpackGeneric({})", a), Bytecode::UnpackVariant(a) => write!(f, "UnpackVariant({})", a), Bytecode::UnpackVariantGeneric(a) => write!(f, "UnpackVariantGeneric({})", a), + Bytecode::PackClosureGeneric(a, mask) => { + write!(f, "PackClosureGeneric({}, {})", a, mask) + }, + Bytecode::PackClosure(a, mask) => write!(f, "PackClosure({}, {})", a, mask), + Bytecode::CallClosure(a) => write!(f, "CallClosure({})", a), Bytecode::ReadRef => write!(f, "ReadRef"), Bytecode::WriteRef => write!(f, "WriteRef"), Bytecode::FreezeRef => write!(f, "FreezeRef"), diff --git a/third_party/move/move-binary-format/src/file_format_common.rs b/third_party/move/move-binary-format/src/file_format_common.rs index b9790db3d35f85..599a49efad132c 100644 --- a/third_party/move/move-binary-format/src/file_format_common.rs +++ b/third_party/move/move-binary-format/src/file_format_common.rs @@ -299,10 +299,13 @@ pub enum Opcodes { UNPACK_VARIANT_GENERIC = 0x55, TEST_VARIANT = 0x56, TEST_VARIANT_GENERIC = 0x57, + PACK_CLOSURE = 0x58, + PACK_CLOSURE_GENERIC = 0x59, + CALL_CLOSURE = 0x5A, } /// Upper limit on the binary size -pub const BINARY_SIZE_LIMIT: usize = usize::max_value(); +pub const BINARY_SIZE_LIMIT: usize = usize::MAX; /// A wrapper for the binary vector #[derive(Default, Debug)] @@ -789,6 +792,10 @@ pub fn instruction_key(instruction: &Bytecode) -> u8 { UnpackVariantGeneric(_) => Opcodes::UNPACK_VARIANT_GENERIC, TestVariant(_) => Opcodes::TEST_VARIANT, TestVariantGeneric(_) => Opcodes::TEST_VARIANT_GENERIC, + // Since bytecode version 8 + PackClosure(..) => Opcodes::PACK_CLOSURE, + PackClosureGeneric(..) => Opcodes::PACK_CLOSURE_GENERIC, + CallClosure(_) => Opcodes::CALL_CLOSURE, }; opcode as u8 } diff --git a/third_party/move/move-binary-format/src/normalized.rs b/third_party/move/move-binary-format/src/normalized.rs index ef61a63963692e..4fe5b3508d0fa3 100644 --- a/third_party/move/move-binary-format/src/normalized.rs +++ b/third_party/move/move-binary-format/src/normalized.rs @@ -192,6 +192,8 @@ 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(..) => panic!("normalized representation does not support function types"), } } diff --git a/third_party/move/move-binary-format/src/proptest_types/functions.rs b/third_party/move/move-binary-format/src/proptest_types/functions.rs index 806ca57aba9e99..f614ffcfbb8e69 100644 --- a/third_party/move/move-binary-format/src/proptest_types/functions.rs +++ b/third_party/move/move-binary-format/src/proptest_types/functions.rs @@ -1062,7 +1062,7 @@ impl BytecodeGen { use SignatureToken::*; match token { U8 | U16 | U32 | U64 | U128 | U256 | Bool | Address | Signer | Struct(_) - | TypeParameter(_) => true, + | Function(..) | TypeParameter(_) => true, Vector(element_token) => BytecodeGen::check_signature_token(element_token), StructInstantiation(_, type_arguments) => type_arguments .iter() diff --git a/third_party/move/move-binary-format/src/proptest_types/types.rs b/third_party/move/move-binary-format/src/proptest_types/types.rs index 566d45809a7354..161586d5038040 100644 --- a/third_party/move/move-binary-format/src/proptest_types/types.rs +++ b/third_party/move/move-binary-format/src/proptest_types/types.rs @@ -71,6 +71,7 @@ impl StDefnMaterializeState { let inner = self.potential_abilities(ty); inner.intersect(AbilitySet::VECTOR) }, + Function(_, _, a) => *a, Struct(idx) => { let sh = &self.struct_handles[idx.0 as usize]; sh.abilities diff --git a/third_party/move/move-binary-format/src/serializer.rs b/third_party/move/move-binary-format/src/serializer.rs index 78f98741ad6b51..fc412a6f0046a2 100644 --- a/third_party/move/move-binary-format/src/serializer.rs +++ b/third_party/move/move-binary-format/src/serializer.rs @@ -160,6 +160,10 @@ fn serialize_struct_def_inst_index( write_as_uleb128(binary, idx.0, STRUCT_DEF_INST_INDEX_MAX) } +fn serialize_closure_mask(binary: &mut BinaryData, mask: &ClosureMask) -> Result<()> { + write_as_uleb128(binary, mask.mask, u64::MAX) +} + fn seiralize_table_offset(binary: &mut BinaryData, offset: u32) -> Result<()> { write_as_uleb128(binary, offset, TABLE_OFFSET_MAX) } @@ -800,6 +804,9 @@ fn serialize_signature_token_single_node_impl( binary.push(SerializedType::TYPE_PARAMETER as u8)?; serialize_type_parameter_index(binary, *idx)?; }, + SignatureToken::Function(..) => { + unimplemented!("serialization of function types") + }, } Ok(()) } @@ -1092,6 +1099,20 @@ fn serialize_instruction_inner( binary.push(Opcodes::TEST_VARIANT_GENERIC as u8)?; serialize_struct_variant_inst_index(binary, class_idx) }, + Bytecode::PackClosure(idx, mask) => { + binary.push(Opcodes::PACK_CLOSURE as u8)?; + serialize_function_handle_index(binary, idx)?; + serialize_closure_mask(binary, mask) + }, + Bytecode::PackClosureGeneric(idx, mask) => { + binary.push(Opcodes::PACK_CLOSURE_GENERIC as u8)?; + serialize_function_inst_index(binary, idx)?; + serialize_closure_mask(binary, mask) + }, + Bytecode::CallClosure(idx) => { + binary.push(Opcodes::CALL_CLOSURE as u8)?; + serialize_signature_index(binary, idx) + }, Bytecode::ReadRef => binary.push(Opcodes::READ_REF as u8), Bytecode::WriteRef => binary.push(Opcodes::WRITE_REF as u8), Bytecode::Add => binary.push(Opcodes::ADD as u8), diff --git a/third_party/move/move-bytecode-spec/src/lib.rs b/third_party/move/move-bytecode-spec/src/lib.rs index c0e573d29c3047..3d8ab4b7164f41 100644 --- a/third_party/move/move-bytecode-spec/src/lib.rs +++ b/third_party/move/move-bytecode-spec/src/lib.rs @@ -129,6 +129,7 @@ static VALID_GROUPS: Lazy> = Lazy::new(|| { "reference", "arithmetic", "casting", + "closure", "bitwise", "comparison", "boolean", diff --git a/third_party/move/move-bytecode-verifier/invalid-mutations/src/bounds.rs b/third_party/move/move-bytecode-verifier/invalid-mutations/src/bounds.rs index bda7c8361fd480..cb47be2e8e2203 100644 --- a/third_party/move/move-bytecode-verifier/invalid-mutations/src/bounds.rs +++ b/third_party/move/move-bytecode-verifier/invalid-mutations/src/bounds.rs @@ -359,6 +359,6 @@ fn struct_handle(token: &SignatureToken) -> Option { StructInstantiation(sh_idx, _) => Some(*sh_idx), Reference(token) | MutableReference(token) => struct_handle(token), Bool | U8 | U16 | U32 | U64 | U128 | U256 | Address | Signer | Vector(_) - | TypeParameter(_) => None, + | TypeParameter(_) | Function(..) => None, } } diff --git a/third_party/move/move-bytecode-verifier/invalid-mutations/src/bounds/code_unit.rs b/third_party/move/move-bytecode-verifier/invalid-mutations/src/bounds/code_unit.rs index 0146087e0cbe30..1a2da3100def5c 100644 --- a/third_party/move/move-bytecode-verifier/invalid-mutations/src/bounds/code_unit.rs +++ b/third_party/move/move-bytecode-verifier/invalid-mutations/src/bounds/code_unit.rs @@ -498,6 +498,9 @@ impl<'a> ApplyCodeUnitBoundsContext<'a> { // TODO(#13806): implement panic!("Enum types bytecode NYI: {:?}", code[bytecode_idx]) }, + PackClosure(..) | PackClosureGeneric(..) | CallClosure(..) => { + panic!("Closure bytecode NYI: {:?}", code[bytecode_idx]) + }, }; code[bytecode_idx] = new_bytecode; @@ -557,7 +560,10 @@ fn is_interesting(bytecode: &Bytecode) -> bool { | LdFalse | ReadRef | WriteRef | Add | Sub | Mul | Mod | Div | BitOr | BitAnd | Xor | Shl | Shr | Or | And | Not | Eq | Neq | Lt | Gt | Le | Ge | Abort | Nop => false, - PackVariant(_) + PackClosure(..) + | PackClosureGeneric(..) + | CallClosure(..) + | PackVariant(_) | PackVariantGeneric(_) | UnpackVariant(_) | UnpackVariantGeneric(_) diff --git a/third_party/move/move-bytecode-verifier/src/acquires_list_verifier.rs b/third_party/move/move-bytecode-verifier/src/acquires_list_verifier.rs index 6b15a043ea97b5..d0fc627a63e57c 100644 --- a/third_party/move/move-bytecode-verifier/src/acquires_list_verifier.rs +++ b/third_party/move/move-bytecode-verifier/src/acquires_list_verifier.rs @@ -102,7 +102,10 @@ impl<'a> AcquiresVerifier<'a> { self.struct_acquire(si.def, offset) }, - Bytecode::Pop + Bytecode::PackClosure(..) + | Bytecode::PackClosureGeneric(..) + | Bytecode::CallClosure(_) + | Bytecode::Pop | Bytecode::BrTrue(_) | Bytecode::BrFalse(_) | Bytecode::Abort diff --git a/third_party/move/move-bytecode-verifier/src/dependencies.rs b/third_party/move/move-bytecode-verifier/src/dependencies.rs index 9ec148e4b43c9d..8593100e39746c 100644 --- a/third_party/move/move-bytecode-verifier/src/dependencies.rs +++ b/third_party/move/move-bytecode-verifier/src/dependencies.rs @@ -455,6 +455,18 @@ fn compare_types( (SignatureToken::Vector(ty1), SignatureToken::Vector(ty2)) => { compare_types(context, ty1, ty2, def_module) }, + ( + SignatureToken::Function(args1, result1, ab1), + SignatureToken::Function(args2, result2, ab2), + ) => { + compare_cross_module_signatures(context, args1, args2, def_module)?; + compare_cross_module_signatures(context, result1, result2, def_module)?; + if ab1 != ab2 { + Err(PartialVMError::new(StatusCode::TYPE_MISMATCH)) + } else { + Ok(()) + } + }, (SignatureToken::Struct(idx1), SignatureToken::Struct(idx2)) => { compare_structs(context, *idx1, *idx2, def_module) }, @@ -483,6 +495,7 @@ fn compare_types( | (SignatureToken::Address, _) | (SignatureToken::Signer, _) | (SignatureToken::Vector(_), _) + | (SignatureToken::Function(..), _) | (SignatureToken::Struct(_), _) | (SignatureToken::StructInstantiation(_, _), _) | (SignatureToken::Reference(_), _) diff --git a/third_party/move/move-bytecode-verifier/src/instantiation_loops.rs b/third_party/move/move-bytecode-verifier/src/instantiation_loops.rs index 78f7903ff36cac..53896c212ee141 100644 --- a/third_party/move/move-bytecode-verifier/src/instantiation_loops.rs +++ b/third_party/move/move-bytecode-verifier/src/instantiation_loops.rs @@ -148,6 +148,14 @@ impl<'a> InstantiationLoopChecker<'a> { type_params.insert(*idx); }, Vector(ty) => rec(type_params, ty), + Function(args, result, _) => { + for ty in args { + rec(type_params, ty); + } + for ty in result { + rec(type_params, ty); + } + }, Reference(ty) | MutableReference(ty) => rec(type_params, ty), StructInstantiation(_, tys) => { for ty in tys { diff --git a/third_party/move/move-bytecode-verifier/src/instruction_consistency.rs b/third_party/move/move-bytecode-verifier/src/instruction_consistency.rs index 84abbb33060324..f42676c89b026d 100644 --- a/third_party/move/move-bytecode-verifier/src/instruction_consistency.rs +++ b/third_party/move/move-bytecode-verifier/src/instruction_consistency.rs @@ -11,8 +11,8 @@ use move_binary_format::{ binary_views::BinaryIndexedView, errors::{Location, PartialVMError, PartialVMResult, VMResult}, file_format::{ - Bytecode, CodeOffset, CodeUnit, CompiledModule, CompiledScript, FieldHandleIndex, - FunctionDefinitionIndex, FunctionHandleIndex, StructDefinitionIndex, + Bytecode, ClosureMask, CodeOffset, CodeUnit, CompiledModule, CompiledScript, + FieldHandleIndex, FunctionDefinitionIndex, FunctionHandleIndex, StructDefinitionIndex, StructVariantHandleIndex, TableIndex, VariantFieldHandleIndex, }, }; @@ -91,12 +91,22 @@ impl<'a> InstructionConsistency<'a> { )?; }, Call(idx) => { + // Nothing to verify for `_mask`, so merge with Call self.check_function_op(offset, *idx, /* generic */ false)?; }, CallGeneric(idx) => { let func_inst = self.resolver.function_instantiation_at(*idx); self.check_function_op(offset, func_inst.handle, /* generic */ true)?; }, + PackClosure(idx, mask) => { + self.check_function_op(offset, *idx, /* generic */ false)?; + self.check_closure_mask(offset, *idx, *mask)? + }, + PackClosureGeneric(idx, mask) => { + let func_inst = self.resolver.function_instantiation_at(*idx); + self.check_function_op(offset, func_inst.handle, /* generic */ true)?; + self.check_closure_mask(offset, func_inst.handle, *mask)? + }, Pack(idx) | Unpack(idx) => { self.check_struct_op(offset, *idx, /* generic */ false)?; }, @@ -135,11 +145,11 @@ 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(_) + CallClosure(_) | 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 => (), } @@ -227,4 +237,19 @@ impl<'a> InstructionConsistency<'a> { } Ok(()) } + + fn check_closure_mask( + &self, + offset: usize, + func_handle_index: FunctionHandleIndex, + mask: ClosureMask, + ) -> PartialVMResult<()> { + let function_handle = self.resolver.function_handle_at(func_handle_index); + let signature = self.resolver.signature_at(function_handle.parameters); + if mask.max_captured() >= signature.len() { + return Err(PartialVMError::new(StatusCode::INVALID_CLOSURE_MASK) + .at_code_offset(self.current_function(), offset as CodeOffset)); + } + Ok(()) + } } diff --git a/third_party/move/move-bytecode-verifier/src/locals_safety/mod.rs b/third_party/move/move-bytecode-verifier/src/locals_safety/mod.rs index 7d27f91f54e83f..13220a1e2ee50a 100644 --- a/third_party/move/move-bytecode-verifier/src/locals_safety/mod.rs +++ b/third_party/move/move-bytecode-verifier/src/locals_safety/mod.rs @@ -125,6 +125,9 @@ fn execute_inner( | Bytecode::UnpackVariantGeneric(_) | Bytecode::TestVariant(_) | Bytecode::TestVariantGeneric(_) + | Bytecode::PackClosure(..) + | Bytecode::PackClosureGeneric(..) + | Bytecode::CallClosure(_) | Bytecode::ReadRef | Bytecode::WriteRef | Bytecode::CastU8 diff --git a/third_party/move/move-bytecode-verifier/src/reference_safety/abstract_state.rs b/third_party/move/move-bytecode-verifier/src/reference_safety/abstract_state.rs index 962487f832a2cc..1f8fd1a6d04e9c 100644 --- a/third_party/move/move-bytecode-verifier/src/reference_safety/abstract_state.rs +++ b/third_party/move/move-bytecode-verifier/src/reference_safety/abstract_state.rs @@ -500,7 +500,17 @@ impl AbstractState { return Err(self.error(StatusCode::GLOBAL_REFERENCE_ERROR, offset)); } } + // Check arguments and return, and abstract value transition + self.core_call(offset, arguments, &return_.0, meter) + } + fn core_call( + &mut self, + offset: CodeOffset, + arguments: Vec, + result_tys: &[SignatureToken], + meter: &mut impl Meter, + ) -> PartialVMResult> { // Check mutable references can be transfered let mut all_references_to_borrow_from = BTreeSet::new(); let mut mutable_references_to_borrow_from = BTreeSet::new(); @@ -518,8 +528,7 @@ impl AbstractState { // Track borrow relationships of return values on inputs let mut returned_refs = 0; - let return_values = return_ - .0 + let return_values = result_tys .iter() .map(|return_type| match return_type { SignatureToken::MutableReference(_) => { @@ -559,6 +568,16 @@ impl AbstractState { Ok(return_values) } + pub fn clos_eval( + &mut self, + offset: CodeOffset, + arguments: Vec, + result_tys: &[SignatureToken], + meter: &mut impl Meter, + ) -> PartialVMResult> { + self.core_call(offset, arguments, result_tys, meter) + } + pub fn ret(&mut self, offset: CodeOffset, values: Vec) -> PartialVMResult<()> { // release all local variables let mut released = BTreeSet::new(); diff --git a/third_party/move/move-bytecode-verifier/src/reference_safety/mod.rs b/third_party/move/move-bytecode-verifier/src/reference_safety/mod.rs index 2d70d91ae314fa..41e17f5cd66e2e 100644 --- a/third_party/move/move-bytecode-verifier/src/reference_safety/mod.rs +++ b/third_party/move/move-bytecode-verifier/src/reference_safety/mod.rs @@ -22,8 +22,9 @@ use move_binary_format::{ binary_views::{BinaryIndexedView, FunctionView}, errors::{PartialVMError, PartialVMResult}, file_format::{ - Bytecode, CodeOffset, FunctionDefinitionIndex, FunctionHandle, IdentifierIndex, - SignatureIndex, SignatureToken, StructDefinition, StructVariantHandle, VariantIndex, + Bytecode, ClosureMask, CodeOffset, FunctionDefinitionIndex, FunctionHandle, + IdentifierIndex, SignatureIndex, SignatureToken, StructDefinition, StructVariantHandle, + VariantIndex, }, safe_assert, safe_unwrap, views::FieldOrVariantIndex, @@ -100,6 +101,43 @@ fn call( Ok(()) } +fn clos_pack( + verifier: &mut ReferenceSafetyAnalysis, + function_handle: &FunctionHandle, + mask: ClosureMask, +) -> PartialVMResult<()> { + let parameters = verifier.resolver.signature_at(function_handle.parameters); + // Extract the captured arguments and pop them from the stack + let argc = mask.extract(¶meters.0, true).len(); + for _ in 0..argc { + // Currently closures require captured arguments to be values. This is verified + // by type safety. + safe_assert!(safe_unwrap!(verifier.stack.pop()).is_value()) + } + verifier.stack.push(AbstractValue::NonReference); + Ok(()) +} + +fn clos_eval( + verifier: &mut ReferenceSafetyAnalysis, + state: &mut AbstractState, + offset: CodeOffset, + arg_tys: Vec, + result_tys: Vec, + meter: &mut impl Meter, +) -> PartialVMResult<()> { + let arguments = arg_tys + .iter() + .map(|_| verifier.stack.pop().unwrap()) + .rev() + .collect(); + let values = state.clos_eval(offset, arguments, &result_tys, meter)?; + for value in values { + verifier.stack.push(value) + } + Ok(()) +} + fn num_fields(struct_def: &StructDefinition) -> usize { struct_def.field_information.field_count(None) } @@ -185,6 +223,18 @@ fn vec_element_type( } } +fn fun_type( + verifier: &mut ReferenceSafetyAnalysis, + idx: SignatureIndex, +) -> PartialVMResult<(Vec, Vec)> { + match verifier.resolver.signature_at(idx).0.first() { + Some(SignatureToken::Function(args, result, _)) => Ok((args.clone(), result.clone())), + _ => Err(PartialVMError::new( + StatusCode::VERIFIER_INVARIANT_VIOLATION, + )), + } +} + fn execute_inner( verifier: &mut ReferenceSafetyAnalysis, state: &mut AbstractState, @@ -497,6 +547,20 @@ fn execute_inner( unpack_variant(verifier, handle)? }, + Bytecode::PackClosure(idx, mask) => { + let function_handle = verifier.resolver.function_handle_at(*idx); + clos_pack(verifier, function_handle, *mask)? + }, + Bytecode::PackClosureGeneric(idx, mask) => { + let func_inst = verifier.resolver.function_instantiation_at(*idx); + let function_handle = verifier.resolver.function_handle_at(func_inst.handle); + clos_pack(verifier, function_handle, *mask)? + }, + Bytecode::CallClosure(idx) => { + let (arg_tys, result_tys) = fun_type(verifier, *idx)?; + clos_eval(verifier, state, offset, arg_tys, result_tys, meter)? + }, + Bytecode::VecPack(idx, num) => { for _ in 0..*num { safe_assert!(safe_unwrap!(verifier.stack.pop()).is_value()) @@ -559,7 +623,6 @@ fn execute_inner( }; Ok(()) } - impl<'a> TransferFunctions for ReferenceSafetyAnalysis<'a> { type State = AbstractState; diff --git a/third_party/move/move-bytecode-verifier/src/signature.rs b/third_party/move/move-bytecode-verifier/src/signature.rs index fdd845ffc871ad..c1cf6358517e41 100644 --- a/third_party/move/move-bytecode-verifier/src/signature.rs +++ b/third_party/move/move-bytecode-verifier/src/signature.rs @@ -259,6 +259,12 @@ impl<'a> SignatureChecker<'a> { self.check_signature_tokens(type_arguments) }, + // Closure operations not supported by legacy signature checker + PackClosure(..) | PackClosureGeneric(..) | CallClosure(_) => { + return Err(PartialVMError::new(StatusCode::UNEXPECTED_VERIFIER_ERROR) + .with_message("closure operations not supported".to_owned())) + }, + // List out the other options explicitly so there's a compile error if a new // bytecode gets added. Pop @@ -363,6 +369,11 @@ impl<'a> SignatureChecker<'a> { } }, + SignatureToken::Function(..) => { + return Err(PartialVMError::new(StatusCode::UNEXPECTED_VERIFIER_ERROR) + .with_message("function types not supported".to_string())); + }, + SignatureToken::Struct(_) | SignatureToken::Reference(_) | SignatureToken::MutableReference(_) @@ -415,6 +426,8 @@ impl<'a> SignatureChecker<'a> { Err(PartialVMError::new(StatusCode::INVALID_SIGNATURE_TOKEN) .with_message("reference not allowed".to_string())) }, + Function(..) => Err(PartialVMError::new(StatusCode::UNEXPECTED_VERIFIER_ERROR) + .with_message("function types not supported".to_string())), Vector(ty) => self.check_signature_token(ty), StructInstantiation(_, type_arguments) => self.check_signature_tokens(type_arguments), } @@ -465,6 +478,10 @@ impl<'a> SignatureChecker<'a> { type_parameters, ) }, + SignatureToken::Function(..) => { + Err(PartialVMError::new(StatusCode::UNEXPECTED_VERIFIER_ERROR) + .with_message("function types not supported".to_string())) + }, SignatureToken::Reference(_) | SignatureToken::MutableReference(_) | SignatureToken::Vector(_) diff --git a/third_party/move/move-bytecode-verifier/src/signature_v2.rs b/third_party/move/move-bytecode-verifier/src/signature_v2.rs index 764a850c726795..af1e78e6745baf 100644 --- a/third_party/move/move-bytecode-verifier/src/signature_v2.rs +++ b/third_party/move/move-bytecode-verifier/src/signature_v2.rs @@ -173,6 +173,18 @@ fn check_ty( param_constraints, )?; }, + Function(args, result, abilities) => { + assert_abilities(*abilities, required_abilities)?; + for ty in args.iter().chain(result.iter()) { + check_ty( + struct_handles, + ty, + false, + required_abilities.requires(), + param_constraints, + )?; + } + }, Struct(sh_idx) => { let handle = &struct_handles[sh_idx.0 as usize]; assert_abilities(handle.abilities, required_abilities)?; @@ -259,6 +271,11 @@ fn check_phantom_params( match ty { Vector(ty) => check_phantom_params(struct_handles, context, false, ty)?, + Function(args, result, _) => { + for ty in args.iter().chain(result) { + check_phantom_params(struct_handles, context, false, ty)? + } + }, StructInstantiation(idx, type_arguments) => { let sh = &struct_handles[idx.0 as usize]; for (i, ty) in type_arguments.iter().enumerate() { @@ -822,7 +839,7 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { }) }; match instr { - CallGeneric(idx) => { + CallGeneric(idx) | PackClosureGeneric(idx, _) => { if let btree_map::Entry::Vacant(entry) = checked_func_insts.entry(*idx) { let constraints = self.verify_function_instantiation_contextless(*idx)?; map_err(constraints.check_in_context(&ability_context))?; @@ -881,6 +898,14 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { entry.insert(()); } }, + CallClosure(idx) => { + let sign = self.resolver.signature_at(*idx); + if sign.len() != 1 || !matches!(&sign.0[0], SignatureToken::Function(..)) { + return map_err(Err(PartialVMError::new( + StatusCode::CLOSURE_EVAL_REQUIRES_FUNCTION, + ))); + } + }, VecPack(idx, _) | VecLen(idx) | VecImmBorrow(idx) @@ -936,6 +961,7 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { | LdTrue | LdFalse | Call(_) + | PackClosure(..) | Pack(_) | Unpack(_) | TestVariant(_) diff --git a/third_party/move/move-bytecode-verifier/src/stack_usage_verifier.rs b/third_party/move/move-bytecode-verifier/src/stack_usage_verifier.rs index 7e43a64fe36d63..90055fdcc649e5 100644 --- a/third_party/move/move-bytecode-verifier/src/stack_usage_verifier.rs +++ b/third_party/move/move-bytecode-verifier/src/stack_usage_verifier.rs @@ -14,7 +14,7 @@ use move_binary_format::{ binary_views::{BinaryIndexedView, FunctionView}, control_flow_graph::{BlockId, ControlFlowGraph}, errors::{PartialVMError, PartialVMResult}, - file_format::{Bytecode, CodeUnit, FunctionDefinitionIndex, Signature}, + file_format::{Bytecode, CodeUnit, FunctionDefinitionIndex, Signature, SignatureToken}, }; use move_core_types::vm_status::StatusCode; @@ -228,6 +228,43 @@ impl<'a> StackUsageVerifier<'a> { (arg_count, return_count) }, + // ClosEval pops the number of arguments and pushes the results of the given function + // type + Bytecode::CallClosure(idx) => { + if let Some(SignatureToken::Function(args, result, _)) = + self.resolver.signature_at(*idx).0.first() + { + ((1 + args.len()) as u64, result.len() as u64) + } else { + // We don't know what it will pop/push, but the signature checker + // ensures we never reach this + (0, 0) + } + }, + + // ClosPack pops the captured arguments and returns 1 value + Bytecode::PackClosure(idx, mask) => { + let function_handle = self.resolver.function_handle_at(*idx); + let arg_count = mask + .extract( + &self.resolver.signature_at(function_handle.parameters).0, + true, + ) + .len() as u64; + (arg_count, 1) + }, + Bytecode::PackClosureGeneric(idx, mask) => { + let func_inst = self.resolver.function_instantiation_at(*idx); + let function_handle = self.resolver.function_handle_at(func_inst.handle); + let arg_count = mask + .extract( + &self.resolver.signature_at(function_handle.parameters).0, + true, + ) + .len() as u64; + (arg_count, 1) + }, + // Pack performs `num_fields` pops and one push Bytecode::Pack(idx) => { let struct_definition = self.resolver.struct_def_at(*idx)?; diff --git a/third_party/move/move-bytecode-verifier/src/struct_defs.rs b/third_party/move/move-bytecode-verifier/src/struct_defs.rs index 6080b0cbb88fb3..d78c9e7f58f7d5 100644 --- a/third_party/move/move-bytecode-verifier/src/struct_defs.rs +++ b/third_party/move/move-bytecode-verifier/src/struct_defs.rs @@ -123,6 +123,11 @@ impl<'a> StructDefGraphBuilder<'a> { ) }, T::Vector(inner) => self.add_signature_token(neighbors, cur_idx, inner)?, + T::Function(args, result, _) => { + for t in args.iter().chain(result) { + self.add_signature_token(neighbors, cur_idx, t)? + } + }, T::Struct(sh_idx) => { if let Some(struct_def_idx) = self.handle_to_def.get(sh_idx) { neighbors diff --git a/third_party/move/move-bytecode-verifier/src/type_safety.rs b/third_party/move/move-bytecode-verifier/src/type_safety.rs index e50efc0cf23b03..cf278862b7c317 100644 --- a/third_party/move/move-bytecode-verifier/src/type_safety.rs +++ b/third_party/move/move-bytecode-verifier/src/type_safety.rs @@ -11,11 +11,12 @@ use move_binary_format::{ control_flow_graph::ControlFlowGraph, errors::{PartialVMError, PartialVMResult}, file_format::{ - AbilitySet, Bytecode, CodeOffset, FunctionDefinitionIndex, FunctionHandle, LocalIndex, - Signature, SignatureToken, SignatureToken as ST, StructDefinition, StructDefinitionIndex, - StructFieldInformation, StructHandleIndex, VariantIndex, + Ability, AbilitySet, Bytecode, ClosureMask, CodeOffset, FunctionDefinitionIndex, + FunctionHandle, FunctionHandleIndex, LocalIndex, Signature, SignatureToken, + SignatureToken as ST, StructDefinition, StructDefinitionIndex, StructFieldInformation, + StructHandleIndex, VariantIndex, Visibility, }, - safe_unwrap, + safe_assert, safe_unwrap, views::FieldOrVariantIndex, }; use move_core_types::vm_status::StatusCode; @@ -300,6 +301,115 @@ fn call( Ok(()) } +fn clos_eval( + verifier: &mut TypeSafetyChecker, + meter: &mut impl Meter, + offset: CodeOffset, + expected_ty: &SignatureToken, +) -> PartialVMResult<()> { + let SignatureToken::Function(param_tys, ret_tys, abilities) = expected_ty else { + // The signature checker has ensured this is a function + safe_assert!(false); + unreachable!() + }; + // On top of the stack is the closure, pop it. + let closure_ty = safe_unwrap!(verifier.stack.pop()); + // Verify that the closure type matches the expected type + if &closure_ty != expected_ty { + return Err(verifier + .error(StatusCode::CALL_TYPE_MISMATCH_ERROR, offset) + .with_message("closure type mismatch".to_owned())); + } + // Verify that the abilities match + let SignatureToken::Function(_, _, closure_abilities) = closure_ty else { + // Ensured above, but never panic + safe_assert!(false); + unreachable!() + }; + if !abilities.is_subset(closure_abilities) { + return Err(verifier + .error(StatusCode::CALL_TYPE_MISMATCH_ERROR, offset) + .with_message("closure ability mismatch".to_owned())); + } + // Pop and verify arguments + for param_ty in param_tys.iter().rev() { + let arg_ty = safe_unwrap!(verifier.stack.pop()); + if &arg_ty != param_ty { + return Err(verifier.error(StatusCode::CALL_TYPE_MISMATCH_ERROR, offset)); + } + } + for ret_ty in ret_tys { + verifier.push(meter, ret_ty.clone())? + } + Ok(()) +} + +fn clos_pack( + verifier: &mut TypeSafetyChecker, + meter: &mut impl Meter, + offset: CodeOffset, + func_handle_idx: FunctionHandleIndex, + type_actuals: &Signature, + mask: ClosureMask, +) -> PartialVMResult<()> { + let func_handle = verifier.resolver.function_handle_at(func_handle_idx); + // Check the captured arguments on the stack + let param_sign = verifier.resolver.signature_at(func_handle.parameters); + let captured_param_tys = mask.extract(¶m_sign.0, true); + let mut abilities = AbilitySet::ALL; + for ty in captured_param_tys.iter().rev() { + abilities = abilities.intersect(verifier.abilities(ty)?); + let arg = safe_unwrap!(verifier.stack.pop()); + if (type_actuals.is_empty() && &arg != ty) + || (!type_actuals.is_empty() && arg != instantiate(ty, type_actuals)) + { + return Err(verifier + .error(StatusCode::PACK_TYPE_MISMATCH_ERROR, offset) + .with_message("captured argument type mismatch".to_owned())); + } + // A captured argument must not be a reference + if ty.is_reference() { + return Err(verifier + .error(StatusCode::PACK_TYPE_MISMATCH_ERROR, offset) + .with_message("captured argument must not be a reference".to_owned())); + } + } + + // In order to determine whether this closure can be storable, we need to figure whether + // this function is public. + // !!!TODO!!! + // We currently cannot determine for an imported function if it is public or friend. A + // standalone CompiledModule does not give this information. This means that we cannot + // construct storable closures from imported functions for now, which is an + // undesired restriction, so this should be fixed by extending the FunctionHandle data, + // and adding visibility there. + let mut is_storable = false; + for fun_def in verifier.resolver.function_defs().unwrap_or(&[]) { + if fun_def.function == func_handle_idx { + // Function defined in this module, so we can check visibility. + if fun_def.visibility == Visibility::Public { + is_storable = true; + } + break; + } + } + if !is_storable { + abilities.remove(Ability::Store); + } + abilities.remove(Ability::Key); + + // Construct the resulting function type + let not_captured_param_tys = mask.extract(¶m_sign.0, false); + let ret_sign = verifier.resolver.signature_at(func_handle.return_); + verifier.push( + meter, + instantiate( + &SignatureToken::Function(not_captured_param_tys, ret_sign.0.to_vec(), abilities), + type_actuals, + ), + ) +} + fn type_fields_signature( verifier: &mut TypeSafetyChecker, _meter: &mut impl Meter, // TODO: metering @@ -725,6 +835,21 @@ fn verify_instr( call(verifier, meter, offset, func_handle, type_args)? }, + Bytecode::PackClosure(idx, mask) => { + clos_pack(verifier, meter, offset, *idx, &Signature(vec![]), *mask)? + }, + Bytecode::PackClosureGeneric(idx, mask) => { + let func_inst = verifier.resolver.function_instantiation_at(*idx); + let type_args = &verifier.resolver.signature_at(func_inst.type_parameters); + verifier.charge_tys(meter, &type_args.0)?; + clos_pack(verifier, meter, offset, func_inst.handle, type_args, *mask)? + }, + Bytecode::CallClosure(idx) => { + // The signature checker has verified this is a function type. + let expected_ty = safe_unwrap!(verifier.resolver.signature_at(*idx).0.first()); + clos_eval(verifier, meter, offset, expected_ty)? + }, + Bytecode::Pack(idx) => { let struct_definition = verifier.resolver.struct_def_at(*idx)?; pack( @@ -1139,6 +1264,9 @@ fn instantiate(token: &SignatureToken, subst: &Signature) -> SignatureToken { return token.clone(); } + let inst_vec = |v: &[SignatureToken]| -> Vec { + v.iter().map(|ty| instantiate(ty, subst)).collect() + }; match token { Bool => Bool, U8 => U8, @@ -1150,14 +1278,11 @@ fn instantiate(token: &SignatureToken, subst: &Signature) -> SignatureToken { Address => Address, Signer => Signer, Vector(ty) => Vector(Box::new(instantiate(ty, subst))), + Function(args, result, abilities) => Function(inst_vec(args), inst_vec(result), *abilities), Struct(idx) => Struct(*idx), - StructInstantiation(idx, struct_type_args) => StructInstantiation( - *idx, - struct_type_args - .iter() - .map(|ty| instantiate(ty, subst)) - .collect(), - ), + StructInstantiation(idx, struct_type_args) => { + StructInstantiation(*idx, inst_vec(struct_type_args)) + }, Reference(ty) => Reference(Box::new(instantiate(ty, subst))), MutableReference(ty) => MutableReference(Box::new(instantiate(ty, subst))), TypeParameter(idx) => { diff --git a/third_party/move/move-compiler/src/interface_generator.rs b/third_party/move/move-compiler/src/interface_generator.rs index 4e0e3fd118b074..8a78305aecc9dc 100644 --- a/third_party/move/move-compiler/src/interface_generator.rs +++ b/third_party/move/move-compiler/src/interface_generator.rs @@ -348,6 +348,12 @@ fn write_return_type(ctx: &mut Context, tys: &[SignatureToken]) -> String { } fn write_signature_token(ctx: &mut Context, t: &SignatureToken) -> String { + let tok_list = |c: &mut Context, v: &[SignatureToken]| { + v.iter() + .map(|ty| write_signature_token(c, ty)) + .collect::>() + .join(", ") + }; match t { SignatureToken::Bool => "bool".to_string(), SignatureToken::U8 => "u8".to_string(), @@ -359,15 +365,13 @@ fn write_signature_token(ctx: &mut Context, t: &SignatureToken) -> String { SignatureToken::Address => "address".to_string(), SignatureToken::Signer => "signer".to_string(), SignatureToken::Vector(inner) => format!("vector<{}>", write_signature_token(ctx, inner)), + SignatureToken::Function(args, result, _) => { + format!("|{}|{}", tok_list(ctx, args), tok_list(ctx, result)) + }, SignatureToken::Struct(idx) => write_struct_handle_type(ctx, *idx), SignatureToken::StructInstantiation(idx, types) => { let n = write_struct_handle_type(ctx, *idx); - let tys = types - .iter() - .map(|ty| write_signature_token(ctx, ty)) - .collect::>() - .join(", "); - format!("{}<{}>", n, tys) + format!("{}<{}>", n, tok_list(ctx, types)) }, SignatureToken::Reference(inner) => format!("&{}", write_signature_token(ctx, inner)), SignatureToken::MutableReference(inner) => { diff --git a/third_party/move/move-core/types/src/vm_status.rs b/third_party/move/move-core/types/src/vm_status.rs index 0db1e408c3a3ba..a33744afd0009a 100644 --- a/third_party/move/move-core/types/src/vm_status.rs +++ b/third_party/move/move-core/types/src/vm_status.rs @@ -734,12 +734,16 @@ pub enum StatusCode { ZERO_VARIANTS_ERROR = 1130, // A feature is not enabled. FEATURE_NOT_ENABLED = 1131, + // Closure mask invalid + INVALID_CLOSURE_MASK = 1132, + // Closure eval type is not a function + CLOSURE_EVAL_REQUIRES_FUNCTION = 1133, // Reserved error code for future use - RESERVED_VERIFICATION_ERROR_2 = 1132, - RESERVED_VERIFICATION_ERROR_3 = 1133, - RESERVED_VERIFICATION_ERROR_4 = 1134, - RESERVED_VERIFICATION_ERROR_5 = 1135, + RESERVED_VERIFICATION_ERROR_2 = 1134, + RESERVED_VERIFICATION_ERROR_3 = 1135, + RESERVED_VERIFICATION_ERROR_4 = 1136, + RESERVED_VERIFICATION_ERROR_5 = 1137, // These are errors that the VM might raise if a violation of internal // invariants takes place. @@ -858,11 +862,14 @@ pub enum StatusCode { // Struct variant not matching. This error appears on an attempt to unpack or borrow a // field from a value which is not of the expected variant. STRUCT_VARIANT_MISMATCH = 4038, + // An unimplemented feature in the VM. + UNIMPLEMENTED_FEATURE = 4039, + // Reserved error code for future use. Always keep this buffer of well-defined new codes. - RESERVED_RUNTIME_ERROR_1 = 4039, - RESERVED_RUNTIME_ERROR_2 = 4040, - RESERVED_RUNTIME_ERROR_3 = 4041, - RESERVED_RUNTIME_ERROR_4 = 4042, + RESERVED_RUNTIME_ERROR_1 = 4040, + RESERVED_RUNTIME_ERROR_2 = 4041, + RESERVED_RUNTIME_ERROR_3 = 4042, + RESERVED_RUNTIME_ERROR_4 = 4043, // A reserved status to represent an unknown vm status. // this is std::u64::MAX, but we can't pattern match on that, so put the hardcoded value in diff --git a/third_party/move/move-ir-compiler/move-ir-to-bytecode/src/context.rs b/third_party/move/move-ir-compiler/move-ir-to-bytecode/src/context.rs index dd996eed4eac6f..c29d9235040855 100644 --- a/third_party/move/move-ir-compiler/move-ir-to-bytecode/src/context.rs +++ b/third_party/move/move-ir-compiler/move-ir-to-bytecode/src/context.rs @@ -768,6 +768,9 @@ impl<'a> Context<'a> { let correct_inner = self.reindex_signature_token(dep, *inner)?; SignatureToken::Vector(Box::new(correct_inner)) }, + SignatureToken::Function(..) => { + unimplemented!("function types not supported by MoveIR") + }, SignatureToken::Reference(inner) => { let correct_inner = self.reindex_signature_token(dep, *inner)?; SignatureToken::Reference(Box::new(correct_inner)) diff --git a/third_party/move/move-model/bytecode/src/stackless_bytecode_generator.rs b/third_party/move/move-model/bytecode/src/stackless_bytecode_generator.rs index d0b4e19044fcfc..18d59dcdf3eeac 100644 --- a/third_party/move/move-model/bytecode/src/stackless_bytecode_generator.rs +++ b/third_party/move/move-model/bytecode/src/stackless_bytecode_generator.rs @@ -293,6 +293,11 @@ impl<'a> StacklessBytecodeGenerator<'a> { }; match bytecode { + MoveBytecode::PackClosure(..) + | MoveBytecode::PackClosureGeneric(..) + | MoveBytecode::CallClosure(..) => { + unimplemented!("stackless bytecode generation for closure opcodes") + }, MoveBytecode::Pop => { let temp_index = self.temp_stack.pop().unwrap(); self.code diff --git a/third_party/move/move-model/src/ty.rs b/third_party/move/move-model/src/ty.rs index 1d2c5c74d106a7..6da025ea9c482e 100644 --- a/third_party/move/move-model/src/ty.rs +++ b/third_party/move/move-model/src/ty.rs @@ -1394,6 +1394,10 @@ impl Type { .collect(), ) }, + SignatureToken::Function(..) => { + // TODO: implement function conversion + unimplemented!("signature token to model type") + }, } } diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs index 5950c9a08e5f69..33802b688be337 100644 --- a/third_party/move/move-vm/runtime/src/interpreter.rs +++ b/third_party/move/move-vm/runtime/src/interpreter.rs @@ -1537,6 +1537,14 @@ impl Frame { )?; match instruction { + // TODO: implement closures + Bytecode::PackClosure(..) + | Bytecode::PackClosureGeneric(..) + | Bytecode::CallClosure(..) => { + return Err(PartialVMError::new(StatusCode::UNIMPLEMENTED_FEATURE) + .with_message("closure opcodes in interpreter".to_owned())) + }, + Bytecode::Pop => { let popped_val = interpreter.operand_stack.pop()?; gas_meter.charge_pop(popped_val)?; diff --git a/third_party/move/move-vm/runtime/src/loader/type_loader.rs b/third_party/move/move-vm/runtime/src/loader/type_loader.rs index b9da4e7348c507..1aa31dcd377ea7 100644 --- a/third_party/move/move-vm/runtime/src/loader/type_loader.rs +++ b/third_party/move/move-vm/runtime/src/loader/type_loader.rs @@ -2,8 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use move_binary_format::{ - binary_views::BinaryIndexedView, errors::PartialVMResult, file_format::SignatureToken, + binary_views::BinaryIndexedView, + errors::{PartialVMError, PartialVMResult}, + file_format::SignatureToken, }; +use move_core_types::vm_status::StatusCode; use move_vm_types::loaded_data::runtime_types::{AbilityInfo, StructNameIndex, Type}; use triomphe::Arc as TriompheArc; @@ -28,6 +31,11 @@ pub fn intern_type( let inner_type = intern_type(module, inner_tok, struct_name_table)?; Type::Vector(TriompheArc::new(inner_type)) }, + SignatureToken::Function(..) => { + // TODO: implement closures + return Err(PartialVMError::new(StatusCode::UNIMPLEMENTED_FEATURE) + .with_message("function types in the type loader".to_owned())); + }, SignatureToken::Reference(inner_tok) => { let inner_type = intern_type(module, inner_tok, struct_name_table)?; Type::Reference(Box::new(inner_type)) diff --git a/third_party/move/move-vm/runtime/src/runtime_type_checks.rs b/third_party/move/move-vm/runtime/src/runtime_type_checks.rs index 30590fbfbf41c1..567dd877755f61 100644 --- a/third_party/move/move-vm/runtime/src/runtime_type_checks.rs +++ b/third_party/move/move-vm/runtime/src/runtime_type_checks.rs @@ -120,6 +120,13 @@ impl RuntimeTypeCheck for FullRuntimeTypeCheck { instruction: &Bytecode, ) -> PartialVMResult<()> { match instruction { + // TODO: implement closures + Bytecode::PackClosure(..) + | Bytecode::PackClosureGeneric(..) + | Bytecode::CallClosure(..) => { + return Err(PartialVMError::new(StatusCode::UNIMPLEMENTED_FEATURE) + .with_message("closure opcodes in interpreter".to_owned())) + }, // Call instruction will be checked at execute_main. Bytecode::Call(_) | Bytecode::CallGeneric(_) => (), Bytecode::BrFalse(_) | Bytecode::BrTrue(_) => { @@ -247,6 +254,14 @@ impl RuntimeTypeCheck for FullRuntimeTypeCheck { let ty_builder = resolver.loader().ty_builder(); match instruction { + // TODO: implement closures + Bytecode::PackClosure(..) + | Bytecode::PackClosureGeneric(..) + | Bytecode::CallClosure(..) => { + return Err(PartialVMError::new(StatusCode::UNIMPLEMENTED_FEATURE) + .with_message("closure opcodes in interpreter".to_owned())) + }, + Bytecode::BrTrue(_) | Bytecode::BrFalse(_) => (), Bytecode::Branch(_) | Bytecode::Ret diff --git a/third_party/move/move-vm/test-utils/src/gas_schedule.rs b/third_party/move/move-vm/test-utils/src/gas_schedule.rs index dc22263926f538..67a25f88cc7b92 100644 --- a/third_party/move/move-vm/test-utils/src/gas_schedule.rs +++ b/third_party/move/move-vm/test-utils/src/gas_schedule.rs @@ -10,8 +10,8 @@ use move_binary_format::{ errors::{PartialVMError, PartialVMResult}, file_format::{ - Bytecode, CodeOffset, ConstantPoolIndex, FieldHandleIndex, FieldInstantiationIndex, - FunctionHandleIndex, FunctionInstantiationIndex, SignatureIndex, + Bytecode, ClosureMask, CodeOffset, ConstantPoolIndex, FieldHandleIndex, + FieldInstantiationIndex, FunctionHandleIndex, FunctionInstantiationIndex, SignatureIndex, StructDefInstantiationIndex, StructDefinitionIndex, StructVariantHandleIndex, StructVariantInstantiationIndex, VariantFieldHandleIndex, VariantFieldInstantiationIndex, }, @@ -688,6 +688,15 @@ pub fn zero_cost_instruction_table() -> Vec<(Bytecode, GasCost)> { TestVariantGeneric(StructVariantInstantiationIndex::new(0)), GasCost::new(0, 0), ), + ( + PackClosure(FunctionHandleIndex::new(0), ClosureMask::new(0)), + GasCost::new(0, 0), + ), + ( + PackClosureGeneric(FunctionInstantiationIndex::new(0), ClosureMask::new(0)), + GasCost::new(0, 0), + ), + (CallClosure(SignatureIndex::new(0)), GasCost::new(0, 0)), (Nop, GasCost::new(0, 0)), (VecPack(SignatureIndex::new(0), 0), GasCost::new(0, 0)), (VecLen(SignatureIndex::new(0)), GasCost::new(0, 0)), @@ -861,6 +870,15 @@ pub fn bytecode_instruction_costs() -> Vec<(Bytecode, GasCost)> { TestVariantGeneric(StructVariantInstantiationIndex::new(0)), GasCost::new(2, 1), ), + ( + PackClosure(FunctionHandleIndex::new(0), ClosureMask::new(0)), + GasCost::new(2, 1), + ), + ( + PackClosureGeneric(FunctionInstantiationIndex::new(0), ClosureMask::new(0)), + GasCost::new(2, 1), + ), + (CallClosure(SignatureIndex::new(0)), GasCost::new(1132, 1)), (Nop, GasCost::new(1, 1)), (VecPack(SignatureIndex::new(0), 0), GasCost::new(84, 1)), (VecLen(SignatureIndex::new(0)), GasCost::new(98, 1)), 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 fae84d1694a857..df677ccf59d017 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 @@ -4159,7 +4159,7 @@ impl Value { S::Signer => return None, S::Vector(inner) => L::Vector(Box::new(Self::constant_sig_token_to_layout(inner)?)), // Not yet supported - S::Struct(_) | S::StructInstantiation(_, _) => return None, + S::Struct(_) | S::StructInstantiation(_, _) | S::Function(..) => return None, // Not allowed/Not meaningful S::TypeParameter(_) | S::Reference(_) | S::MutableReference(_) => return None, }) 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 ccbda4d4276afd..46863644cdc55b 100644 --- a/third_party/move/tools/move-bytecode-utils/src/layout.rs +++ b/third_party/move/tools/move-bytecode-utils/src/layout.rs @@ -386,6 +386,7 @@ impl TypeLayoutBuilder { ) -> anyhow::Result { use SignatureToken::*; Ok(match s { + Function(..) => bail!("function types NYI for MoveTypeLayout"), Vector(t) => MoveTypeLayout::Vector(Box::new(Self::build_from_signature_token( m, t, diff --git a/third_party/move/tools/move-disassembler/src/disassembler.rs b/third_party/move/tools/move-disassembler/src/disassembler.rs index 3dedaa3f942b18..eee4338aa9459e 100644 --- a/third_party/move/tools/move-disassembler/src/disassembler.rs +++ b/third_party/move/tools/move-disassembler/src/disassembler.rs @@ -570,6 +570,9 @@ impl<'a> Disassembler<'a> { type_param_context: &[SourceName], ) -> Result { Ok(match sig_tok { + // TODO: function types + SignatureToken::Function(..) => unimplemented!("disassembling function sig tokens"), + SignatureToken::Bool => "bool".to_string(), SignatureToken::U8 => "u8".to_string(), SignatureToken::U16 => "u16".to_string(), @@ -641,6 +644,11 @@ impl<'a> Disassembler<'a> { default_location: &Loc, ) -> Result { match instruction { + Bytecode::PackClosure(..) + | Bytecode::PackClosureGeneric(..) + | Bytecode::CallClosure(..) => { + bail!("closure opcodes not implemented") + }, Bytecode::LdConst(idx) => { let constant = self.source_mapper.bytecode.constant_at(*idx); Ok(format!( 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 4f06ccc18ce228..ba6d7f3eca5151 100644 --- a/third_party/move/tools/move-resource-viewer/src/lib.rs +++ b/third_party/move/tools/move-resource-viewer/src/lib.rs @@ -375,6 +375,9 @@ impl MoveValueAnnotator { SignatureToken::Vector(ty) => { FatType::Vector(Box::new(self.resolve_signature(module, ty, limit)?)) }, + SignatureToken::Function(..) => { + bail!("function types NYI by fat types") + }, SignatureToken::Struct(idx) => { FatType::Struct(Box::new(self.resolve_struct_handle(module, *idx, limit)?)) },