From 6c84279403b75712eb7383bb466beb90df261952 Mon Sep 17 00:00:00 2001 From: Wolfgang Grieskamp Date: Mon, 24 Jun 2024 23:45:07 -0700 Subject: [PATCH] [compiler-v2] Enum types in the VM In #13725 support for enums was added to the stackless bytecode IR. This PR extends the Move VM's representation of bytecode ('file format') by struct variants. It also implements - representation in file format - serialization/deserialization of file format - bytecode verifier - code generation in compiler v2 - intepreter and paranoid mode The runtime semantics (intepreter, runtime types, compatibility checking), as well as certain other features (as marked by #13806 in the code), are not yet implemented by this PR. On Move bytecode level, there are `5 * 2` new instructions (each instruction has a dual generic version): ``` TestVariant(StructVariantHandleIndex) and TestVariantGeneric(StructVariantInstantiationIndex) PackVariant(StructVariantHandleIndex) and PackVariantGeneric(StructVariantInstantiationIndex) UnpackVariant(StructVariantHandleIndex) and UnpackVariantGeneric(StructVariantInstantiationIndex) ImmBorrowVariantField(VariantFieldHandleIndex) and ImmBorrowVariantFieldGeneric(VariantFieldInstantiationIndex) MutBorrowVariantField(VariantFieldHandleIndex) and MutBorrowVariantFieldGeneric(VariantFieldInstantiationIndex) ``` For the indices used in those instructions, 4 new tables have been added to the file format holding the associated data. There is a lot of boilerplate code to support the new instructions and tables. Some refactoring of existing code has been done to avoid too much copy and paste, specifically in the serializers and in the bytecode verifier. Apart of passing existing tests, there is a new test in move-compiler-v2/tests/file-format-generator/struct_variants.move which shows the disassembeled output of various match expressions. There are also new e2e transactional tests in move-compiler-v2/transactional-tests/tests/enun. To add negative tests for the bytecode verifier and serializers, we first need a better way to build code with injected faults. See also #14074 and #13812. --- api/types/src/bytecode.rs | 4 + aptos-move/aptos-gas-meter/src/meter.rs | 7 + .../src/gas_schedule/instr.rs | 19 +- aptos-move/aptos-gas-schedule/src/ver.rs | 10 +- .../src/tests/access_path_test.rs | 4 + aptos-move/framework/src/module_metadata.rs | 2 +- .../framework/src/natives/string_utils.rs | 25 + .../move/evm/move-to-yul/src/functions.rs | 2 +- .../serializer_tests.proptest-regressions | 1 + .../move/move-binary-format/src/access.rs | 43 ++ .../move-binary-format/src/binary_views.rs | 80 +- .../move-binary-format/src/check_bounds.rs | 217 +++++- .../src/check_complexity.rs | 133 +++- .../move-binary-format/src/compatibility.rs | 1 + .../move-binary-format/src/deserializer.rs | 721 ++++++++++-------- .../move-binary-format/src/file_format.rs | 371 ++++++++- .../src/file_format_common.rs | 41 +- .../move/move-binary-format/src/lib.rs | 17 + .../move/move-binary-format/src/normalized.rs | 5 + .../move-binary-format/src/proptest_types.rs | 18 +- .../src/proptest_types/functions.rs | 456 +++++++---- .../src/proptest_types/types.rs | 60 +- .../move/move-binary-format/src/serializer.rs | 605 ++++++++------- .../src/unit_tests/compatibility_tests.rs | 4 + .../move/move-binary-format/src/views.rs | 31 +- .../move/move-bytecode-spec/src/lib.rs | 1 + .../src/unit_tests/dependencies_tests.rs | 8 + .../src/unit_tests/generic_ops_tests.rs | 4 + .../src/unit_tests/limit_tests.rs | 4 + .../src/unit_tests/signature_tests.rs | 8 + .../invalid-mutations/src/bounds/code_unit.rs | 27 + .../invalid-mutations/src/signature.rs | 39 +- .../src/ability_field_requirements.rs | 53 +- .../src/acquires_list_verifier.rs | 10 + .../src/check_duplication.rs | 63 +- .../src/instruction_consistency.rs | 119 +-- .../move/move-bytecode-verifier/src/limits.rs | 33 +- .../src/locals_safety/mod.rs | 10 + .../src/reference_safety/abstract_state.rs | 20 +- .../src/reference_safety/mod.rs | 156 +++- .../regression_tests/reference_analysis.rs | 12 + .../move-bytecode-verifier/src/signature.rs | 179 ++++- .../src/signature_v2.rs | 429 ++++++++--- .../src/stack_usage_verifier.rs | 82 +- .../move-bytecode-verifier/src/type_safety.rs | 297 ++++++-- .../move-bytecode-verifier/src/verifier.rs | 4 + .../bytecode_ops_abilities_bad.exp | 6 +- .../src/bytecode_generator.rs | 57 +- .../function_generator.rs | 150 +++- .../src/file_format_generator/mod.rs | 2 + .../file_format_generator/module_generator.rs | 205 ++++- .../pipeline/reference_safety_processor.rs | 19 +- .../matching_ability_err.exp | 2 +- .../tests/bytecode-generator/matching_ok.exp | 126 +-- .../tests/bytecode-generator/matching_ok.move | 10 + .../file-format-generator/struct_variants.exp | 505 ++++++++++++ .../struct_variants.move | 114 +++ .../struct_variants.opt.exp | 492 ++++++++++++ .../enum/enum_field_select.move | 43 ++ .../enum/enum_field_select.no-optimize.exp | 19 + ...enum_field_select.optimize-no-simplify.exp | 19 + .../enum/enum_field_select.optimize.exp | 19 + .../no-v1-comparison/enum/enum_matching.exp | 43 ++ .../no-v1-comparison/enum/enum_matching.move | 201 +++++ .../transactional-tests/tests/tests.rs | 2 + .../move-compiler/src/interface_generator.rs | 5 + third_party/move/move-core/types/src/value.rs | 83 +- .../move/move-core/types/src/vm_status.rs | 27 +- .../src/source_map.rs | 54 +- .../move-ir-to-bytecode/src/compiler.rs | 12 +- .../bytecode/src/borrow_analysis.rs | 6 +- .../bytecode/src/stackless_bytecode.rs | 31 +- .../src/stackless_bytecode_generator.rs | 12 + .../move-model/src/builder/module_builder.rs | 44 +- third_party/move/move-model/src/lib.rs | 5 + third_party/move/move-model/src/model.rs | 64 +- .../boogie-backend/src/bytecode_translator.rs | 9 +- .../bytecode-pipeline/src/mono_analysis.rs | 2 +- .../src/tests/instantiation_tests.rs | 5 + .../src/tests/vm_arguments_tests.rs | 4 + .../move/move-vm/runtime/src/interpreter.rs | 466 +++++++++-- .../move/move-vm/runtime/src/loader/mod.rs | 256 +++++-- .../move-vm/runtime/src/loader/modules.rs | 208 ++++- .../move-vm/test-utils/src/gas_schedule.rs | 83 +- third_party/move/move-vm/types/src/gas.rs | 30 + .../types/src/loaded_data/runtime_types.rs | 83 +- .../move-vm/types/src/values/values_impl.rs | 70 ++ third_party/move/scripts/move_pr.sh | 1 + .../test-generation/src/bytecode_generator.rs | 8 + .../test-generation/src/summaries.rs | 13 + .../test-generation/src/transitions.rs | 4 + .../tests/struct_instructions.rs | 4 + .../tools/move-bytecode-utils/src/layout.rs | 3 + .../move-disassembler/src/disassembler.rs | 471 ++++++++---- .../tools/move-resource-viewer/src/lib.rs | 6 + types/src/vm/configs.rs | 1 + 96 files changed, 6846 insertions(+), 1693 deletions(-) create mode 100644 third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.exp create mode 100644 third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.move create mode 100644 third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.opt.exp create mode 100644 third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.move create mode 100644 third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.no-optimize.exp create mode 100644 third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.optimize-no-simplify.exp create mode 100644 third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.optimize.exp create mode 100644 third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_matching.exp create mode 100644 third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_matching.move diff --git a/api/types/src/bytecode.rs b/api/types/src/bytecode.rs index 83e6ab0b8d89f..1c6a6d49fc625 100644 --- a/api/types/src/bytecode.rs +++ b/api/types/src/bytecode.rs @@ -109,6 +109,10 @@ pub trait Bytecode { .map(|f| self.new_move_struct_field(f)) .collect(), ), + StructFieldInformation::DeclaredVariants(..) => { + // TODO(#13806): implement for enums. Currently we pretend they don't have fields + (false, vec![]) + }, }; let name = self.identifier_at(handle.name).to_owned(); let abilities = handle diff --git a/aptos-move/aptos-gas-meter/src/meter.rs b/aptos-move/aptos-gas-meter/src/meter.rs index 1bbcf85a26191..f17c266e09fef 100644 --- a/aptos-move/aptos-gas-meter/src/meter.rs +++ b/aptos-move/aptos-gas-meter/src/meter.rs @@ -101,6 +101,13 @@ where MutBorrowField => MUT_BORROW_FIELD, ImmBorrowFieldGeneric => IMM_BORROW_FIELD_GENERIC, MutBorrowFieldGeneric => MUT_BORROW_FIELD_GENERIC, + ImmBorrowVariantField => IMM_BORROW_VARIANT_FIELD, + MutBorrowVariantField => MUT_BORROW_VARIANT_FIELD, + ImmBorrowVariantFieldGeneric => IMM_BORROW_VARIANT_FIELD_GENERIC, + MutBorrowVariantFieldGeneric => MUT_BORROW_VARIANT_FIELD_GENERIC, + TestVariant => TEST_VARIANT, + TestVariantGeneric => TEST_VARIANT_GENERIC, + FreezeRef => FREEZE_REF, CastU8 => CAST_U8, diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/instr.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/instr.rs index e965f8d918b55..26557162fb157 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/instr.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/instr.rs @@ -3,7 +3,7 @@ //! This module defines the gas parameters for all Move instructions. -use crate::gas_schedule::VMGasParameters; +use crate::{gas_feature_versions::RELEASE_V1_18, gas_schedule::VMGasParameters}; use aptos_gas_algebra::{ InternalGas, InternalGasPerAbstractValueUnit, InternalGasPerArg, InternalGasPerByte, InternalGasPerTypeNode, @@ -43,8 +43,23 @@ crate::gas_schedule::macros::define_gas_parameters!( [mut_borrow_loc: InternalGas, "mut_borrow_loc", 220], [imm_borrow_field: InternalGas, "imm_borrow_field", 735], [mut_borrow_field: InternalGas, "mut_borrow_field", 735], - [imm_borrow_field_generic: InternalGas, "imm_borrow_field_generic", 735], + [imm_borrow_field_generic: InternalGas, "imm_borrow_field_generic" , 735], [mut_borrow_field_generic: InternalGas, "mut_borrow_field_generic", 735], + [imm_borrow_variant_field: InternalGas, + { RELEASE_V1_18.. => "imm_borrow_variant_field" }, 835], + [mut_borrow_variant_field: InternalGas, + { RELEASE_V1_18.. => "mut_borrow_variant_field" }, 835], + [imm_borrow_variant_field_generic: InternalGas, + { RELEASE_V1_18 => "imm_borrow_variant_field_generic" }, 835], + [mut_borrow_variant_field_generic: InternalGas, + { RELEASE_V1_18 => "mut_borrow_variant_field_generic" }, 835], + + // variant testing + [test_variant: InternalGas, + { RELEASE_V1_18 => "test_variant" }, 535], + [test_variant_generic: InternalGas, + { RELEASE_V1_18 => "test_variant_generic" }, 535], + // locals [copy_loc_base: InternalGas, "copy_loc.base", 294], [copy_loc_per_abs_val_unit: InternalGasPerAbstractValueUnit, "copy_loc.per_abs_val_unit", 14], diff --git a/aptos-move/aptos-gas-schedule/src/ver.rs b/aptos-move/aptos-gas-schedule/src/ver.rs index 975ffe7663169..eb65a55acd64c 100644 --- a/aptos-move/aptos-gas-schedule/src/ver.rs +++ b/aptos-move/aptos-gas-schedule/src/ver.rs @@ -66,7 +66,7 @@ /// global operations. /// - V1 /// - TBA -pub const LATEST_GAS_FEATURE_VERSION: u64 = gas_feature_versions::RELEASE_V1_16; +pub const LATEST_GAS_FEATURE_VERSION: u64 = gas_feature_versions::RELEASE_V1_18; pub mod gas_feature_versions { pub const RELEASE_V1_8: u64 = 11; @@ -79,8 +79,8 @@ pub mod gas_feature_versions { pub const RELEASE_V1_14: u64 = 19; pub const RELEASE_V1_15: u64 = 20; pub const RELEASE_V1_16: u64 = 21; - pub const RELEASE_V1_17: u64 = 22; - pub const RELEASE_V1_18: u64 = 23; - pub const RELEASE_V1_19: u64 = 24; - pub const RELEASE_V1_20: u64 = 25; + pub const RELEASE_V1_18: u64 = 22; + pub const RELEASE_V1_19: u64 = 23; + pub const RELEASE_V1_20: u64 = 24; + pub const RELEASE_V1_21: u64 = 25; } diff --git a/aptos-move/e2e-move-tests/src/tests/access_path_test.rs b/aptos-move/e2e-move-tests/src/tests/access_path_test.rs index b4917624d5a61..40fd2e9f5d08c 100644 --- a/aptos-move/e2e-move-tests/src/tests/access_path_test.rs +++ b/aptos-move/e2e-move-tests/src/tests/access_path_test.rs @@ -90,6 +90,10 @@ fn access_path_panic() { ], }), }], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; let mut module_bytes = vec![]; diff --git a/aptos-move/framework/src/module_metadata.rs b/aptos-move/framework/src/module_metadata.rs index 4b969d1b96eae..217e50e354a68 100644 --- a/aptos-move/framework/src/module_metadata.rs +++ b/aptos-move/framework/src/module_metadata.rs @@ -431,7 +431,7 @@ pub fn is_valid_resource_group( if let Ok(ident_struct) = Identifier::new(struct_) { if let Some((struct_handle, struct_def)) = structs.get(ident_struct.as_ident_str()) { let num_fields = match &struct_def.field_information { - StructFieldInformation::Native => 0, + StructFieldInformation::Native | StructFieldInformation::DeclaredVariants(_) => 0, StructFieldInformation::Declared(fields) => fields.len(), }; if struct_handle.abilities == AbilitySet::EMPTY diff --git a/aptos-move/framework/src/natives/string_utils.rs b/aptos-move/framework/src/natives/string_utils.rs index b724fe2c476d8..79548ee7d610a 100644 --- a/aptos-move/framework/src/natives/string_utils.rs +++ b/aptos-move/framework/src/natives/string_utils.rs @@ -308,6 +308,31 @@ fn native_format_impl( )?; out.push('}'); }, + MoveTypeLayout::Struct(MoveStructLayout::RuntimeVariants(variants)) => { + let strct = val.value_as::()?; + let mut elems = strct.unpack()?.collect::>(); + if elems.is_empty() { + return Err(SafeNativeError::Abort { + abort_code: EARGS_MISMATCH, + }); + } + let tag = elems.pop().unwrap().value_as::()? as usize; + if tag >= variants.len() { + return Err(SafeNativeError::Abort { + abort_code: EINVALID_FORMAT, + }); + } + out.push_str(&format!("#{}{{", tag)); + format_vector( + context, + variants[tag].iter(), + elems, + depth, + !context.single_line, + out, + )?; + out.push('}'); + }, // This is unreachable because we check layout at the start. Still, return // an error to be safe. diff --git a/third_party/move/evm/move-to-yul/src/functions.rs b/third_party/move/evm/move-to-yul/src/functions.rs index 66cc5021f4f9a..7e24f6757b828 100644 --- a/third_party/move/evm/move-to-yul/src/functions.rs +++ b/third_party/move/evm/move-to-yul/src/functions.rs @@ -551,7 +551,7 @@ impl<'a> FunctionGenerator<'a> { TestVariant(..) | PackVariant(..) | UnpackVariant(..) - | BorrowFieldVariant(..) => { + | BorrowVariantField(..) => { unimplemented!("variants") }, diff --git a/third_party/move/move-binary-format/serializer-tests/tests/serializer_tests.proptest-regressions b/third_party/move/move-binary-format/serializer-tests/tests/serializer_tests.proptest-regressions index 988ff09ce66d8..c6765cae79845 100644 --- a/third_party/move/move-binary-format/serializer-tests/tests/serializer_tests.proptest-regressions +++ b/third_party/move/move-binary-format/serializer-tests/tests/serializer_tests.proptest-regressions @@ -7,3 +7,4 @@ cc 3d00be1cbcb9f344e7bce080015d7a755f6e69acd37dbde62e449732af226fe4 # shrinks to module = CompiledModule: { module_handles: [ ModuleHandle { address: AddressPoolIndex(0), name: IdentifierIndex(0) },] struct_handles: [ StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(0), is_resource: false }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(0), is_resource: false },] function_handles: [ FunctionHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(0), signature: FunctionSignatureIndex(0) }, FunctionHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(0), signature: FunctionSignatureIndex(1) },] struct_defs: [ StructDefinition { struct_handle: 1, field_count: 0, fields: 0 },] field_defs: [] function_defs: [ FunctionDefinition { function: 1, flags: 0x0, code: CodeUnit { max_stack_size: 0, locals: 0 code: [] } },] type_signatures: [ TypeSignature(Unit), TypeSignature(Unit),] function_signatures: [ FunctionSignature { return_type: Unit, arg_types: [] }, FunctionSignature { return_type: Unit, arg_types: [] },] locals_signatures: [ LocalsSignature([]),] string_pool: [ "",] address_pool: [ Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),] } cc 7b1bb969b87bfcdbb0f635eb46212f8437d21bcd1ba754de84d66bb552e6aec2 # shrinks to module = CompiledModule { module_handles: [], struct_handles: [], function_handles: [], type_signatures: [], function_signatures: [], locals_signatures: [], string_pool: [], byte_array_pool: [], address_pool: [0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000], struct_defs: [], field_defs: [], function_defs: [] } cc 4118fc247fb7d48382876de931d47a8999a6e42658bbecc93afff9245ade141b # shrinks to module = CompiledModule { module_handles: [], struct_handles: [], function_handles: [], type_signatures: [], function_signatures: [], locals_signatures: [], string_pool: [], byte_array_pool: [], address_pool: [], struct_defs: [], field_defs: [FieldDefinition { struct_: StructHandleIndex(0), name: IdentifierIndex(0), signature: TypeSignatureIndex(0) }], function_defs: [] } +cc 42cd5d033842c708fc3a704a573a77455676f33b4cb987516cee12efeaa33655 # shrinks to module = CompiledModule { version: 7, self_module_handle_idx: ModuleHandleIndex(0), module_handles: [ModuleHandle { address: AddressIdentifierIndex(0), name: IdentifierIndex(0) }], struct_handles: [StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(7), abilities: [Copy, Store, ], type_parameters: [StructTypeParameter { constraints: [Copy, ], is_phantom: true }] }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(16), abilities: [Copy, Drop, Store, ], type_parameters: [StructTypeParameter { constraints: [Drop, ], is_phantom: false }, StructTypeParameter { constraints: [], is_phantom: true }] }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(10), abilities: [Copy, ], type_parameters: [] }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(15), abilities: [], type_parameters: [StructTypeParameter { constraints: [], is_phantom: true }, StructTypeParameter { constraints: [Copy, Store, ], is_phantom: false }] }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(12), abilities: [Copy, ], type_parameters: [] }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(0), abilities: [Drop, ], type_parameters: [StructTypeParameter { constraints: [Copy, Drop, Store, ], is_phantom: true }] }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(5), abilities: [Copy, ], type_parameters: [StructTypeParameter { constraints: [Copy, Store, ], is_phantom: false }, StructTypeParameter { constraints: [], is_phantom: true }] }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(13), abilities: [Drop, ], type_parameters: [StructTypeParameter { constraints: [Copy, Drop, ], is_phantom: true }] }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(3), abilities: [Drop, ], type_parameters: [StructTypeParameter { constraints: [Drop, Store, ], is_phantom: false }, StructTypeParameter { constraints: [Copy, Drop, Store, ], is_phantom: false }] }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(4), abilities: [Drop, ], type_parameters: [StructTypeParameter { constraints: [Copy, Drop, Store, ], is_phantom: true }] }, StructHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(6), abilities: [Copy, ], type_parameters: [StructTypeParameter { constraints: [], is_phantom: false }, StructTypeParameter { constraints: [Drop, Store, ], is_phantom: true }] }], function_handles: [FunctionHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(9), parameters: SignatureIndex(2), return_: SignatureIndex(3), type_parameters: [], access_specifiers: None }, FunctionHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(16), parameters: SignatureIndex(5), return_: SignatureIndex(6), type_parameters: [], access_specifiers: None }, FunctionHandle { module: ModuleHandleIndex(0), name: IdentifierIndex(4), parameters: SignatureIndex(9), return_: SignatureIndex(10), type_parameters: [], access_specifiers: None }], field_handles: [], friend_decls: [ModuleHandle { address: AddressIdentifierIndex(0), name: IdentifierIndex(0) }, ModuleHandle { address: AddressIdentifierIndex(0), name: IdentifierIndex(2) }, ModuleHandle { address: AddressIdentifierIndex(0), name: IdentifierIndex(4) }, ModuleHandle { address: AddressIdentifierIndex(0), name: IdentifierIndex(5) }, ModuleHandle { address: AddressIdentifierIndex(0), name: IdentifierIndex(10) }, ModuleHandle { address: AddressIdentifierIndex(0), name: IdentifierIndex(12) }, ModuleHandle { address: AddressIdentifierIndex(0), name: IdentifierIndex(13) }, ModuleHandle { address: AddressIdentifierIndex(0), name: IdentifierIndex(14) }], struct_def_instantiations: [StructDefInstantiation { def: StructDefinitionIndex(10), type_parameters: SignatureIndex(4) }, StructDefInstantiation { def: StructDefinitionIndex(5), type_parameters: SignatureIndex(7) }, StructDefInstantiation { def: StructDefinitionIndex(8), type_parameters: SignatureIndex(4) }], function_instantiations: [], field_instantiations: [], signatures: [Signature([U32, Bool]), Signature([U16, Address, U32]), Signature([]), Signature([Reference(Bool)]), Signature([U64, U64]), Signature([StructInstantiation(StructHandleIndex(7), [U64]), Reference(U32)]), Signature([U16, Reference(U256)]), Signature([U64]), Signature([StructInstantiation(StructHandleIndex(3), [U64, U64])]), Signature([StructInstantiation(StructHandleIndex(8), [U64, U64]), Vector(Signer), U8]), Signature([U128, MutableReference(Address)]), Signature([Bool])], identifiers: [Identifier("A"), Identifier("A0"), Identifier("AA"), Identifier("AB"), Identifier("AC"), Identifier("AD"), Identifier("AE"), Identifier("A_"), Identifier("Aa"), Identifier("a"), Identifier("a0"), Identifier("a1"), Identifier("a2"), Identifier("a3"), Identifier("aA"), Identifier("a_"), Identifier("aa")], address_identifiers: [0000000000000000000000000000000000000000000000000000000000000000], constant_pool: [], metadata: [], struct_defs: [StructDefinition { struct_handle: StructHandleIndex(0), field_information: Native }, StructDefinition { struct_handle: StructHandleIndex(1), field_information: Native }, StructDefinition { struct_handle: StructHandleIndex(2), field_information: Native }, StructDefinition { struct_handle: StructHandleIndex(3), field_information: Native }, StructDefinition { struct_handle: StructHandleIndex(4), field_information: Native }, StructDefinition { struct_handle: StructHandleIndex(5), field_information: Declared([FieldDefinition { name: IdentifierIndex(16), signature: TypeSignature(U64) }, FieldDefinition { name: IdentifierIndex(7), signature: TypeSignature(Address) }, FieldDefinition { name: IdentifierIndex(12), signature: TypeSignature(U64) }]) }, StructDefinition { struct_handle: StructHandleIndex(6), field_information: DeclaredVariants([VariantDefinition { name: IdentifierIndex(2), fields: [FieldDefinition { name: IdentifierIndex(5), signature: TypeSignature(U256) }] }]) }, StructDefinition { struct_handle: StructHandleIndex(7), field_information: Native }, StructDefinition { struct_handle: StructHandleIndex(8), field_information: DeclaredVariants([VariantDefinition { name: IdentifierIndex(14), fields: [FieldDefinition { name: IdentifierIndex(11), signature: TypeSignature(StructInstantiation(StructHandleIndex(1), [Signer, U64])) }] }, VariantDefinition { name: IdentifierIndex(10), fields: [] }]) }, StructDefinition { struct_handle: StructHandleIndex(9), field_information: DeclaredVariants([VariantDefinition { name: IdentifierIndex(14), fields: [FieldDefinition { name: IdentifierIndex(13), signature: TypeSignature(Bool) }, FieldDefinition { name: IdentifierIndex(9), signature: TypeSignature(U16) }, FieldDefinition { name: IdentifierIndex(14), signature: TypeSignature(Address) }, FieldDefinition { name: IdentifierIndex(7), signature: TypeSignature(Signer) }] }]) }, StructDefinition { struct_handle: StructHandleIndex(10), field_information: Native }], function_defs: [FunctionDefinition { function: FunctionHandleIndex(0), visibility: Private, is_entry: true, acquires_global_resources: [StructDefinitionIndex(5)], code: Some(CodeUnit { locals: SignatureIndex(2), code: [MutBorrowGlobalGeneric(StructDefInstantiationIndex(0)), Branch(0)] }) }, FunctionDefinition { function: FunctionHandleIndex(1), visibility: Private, is_entry: false, acquires_global_resources: [StructDefinitionIndex(10)], code: Some(CodeUnit { locals: SignatureIndex(8), code: [Call(0), Xor, ImmBorrowVariantFieldGeneric(VariantFieldInstantiationIndex(0)), ExistsGeneric(StructDefInstantiationIndex(1)), ImmBorrowGlobal(StructDefinitionIndex(4)), MoveFromGeneric(StructDefInstantiationIndex(2)), BrFalse(1), ImmBorrowLoc(0), MutBorrowGlobalGeneric(StructDefInstantiationIndex(1)), ImmBorrowLoc(0), ExistsGeneric(StructDefInstantiationIndex(0)), ImmBorrowLoc(0), Exists(StructDefinitionIndex(2)), MutBorrowLoc(0), MoveToGeneric(StructDefInstantiationIndex(1))] }) }, FunctionDefinition { function: FunctionHandleIndex(2), visibility: Friend, is_entry: true, acquires_global_resources: [], code: Some(CodeUnit { locals: SignatureIndex(11), code: [StLoc(0), ImmBorrowVariantFieldGeneric(VariantFieldInstantiationIndex(1))] }) }], struct_variant_handles: [], struct_variant_instantiations: [], variant_field_handles: [VariantFieldHandle { owner: StructDefinitionIndex(9), variants: [0], field: 3 }, VariantFieldHandle { owner: StructDefinitionIndex(6), variants: [0], field: 0 }], variant_field_instantiations: [VariantFieldInstantiation { handle: VariantFieldHandleIndex(0), type_parameters: SignatureIndex(7) }, VariantFieldInstantiation { handle: VariantFieldHandleIndex(1), type_parameters: SignatureIndex(4) }] } diff --git a/third_party/move/move-binary-format/src/access.rs b/third_party/move/move-binary-format/src/access.rs index 904619fad022f..c0c3e1114de8e 100644 --- a/third_party/move/move-binary-format/src/access.rs +++ b/third_party/move/move-binary-format/src/access.rs @@ -66,6 +66,19 @@ pub trait ModuleAccess: Sync { handle } + fn variant_field_handle_at(&self, idx: VariantFieldHandleIndex) -> &VariantFieldHandle { + let handle = &self.as_module().variant_field_handles[idx.into_index()]; + debug_assert!(handle.struct_index.into_index() < self.as_module().struct_defs.len()); // invariant + + handle + } + + fn struct_variant_handle_at(&self, idx: StructVariantHandleIndex) -> &StructVariantHandle { + let handle = &self.as_module().struct_variant_handles[idx.into_index()]; + debug_assert!(handle.struct_index.into_index() < self.as_module().struct_defs.len()); // invariant + handle + } + fn struct_instantiation_at(&self, idx: StructDefInstantiationIndex) -> &StructDefInstantiation { &self.as_module().struct_def_instantiations[idx.into_index()] } @@ -78,6 +91,20 @@ pub trait ModuleAccess: Sync { &self.as_module().field_instantiations[idx.into_index()] } + fn variant_field_instantiation_at( + &self, + idx: VariantFieldInstantiationIndex, + ) -> &VariantFieldInstantiation { + &self.as_module().variant_field_instantiations[idx.into_index()] + } + + fn struct_variant_instantiation_at( + &self, + idx: StructVariantInstantiationIndex, + ) -> &StructVariantInstantiation { + &self.as_module().struct_variant_instantiations[idx.into_index()] + } + fn signature_at(&self, idx: SignatureIndex) -> &Signature { &self.as_module().signatures[idx.into_index()] } @@ -124,6 +151,14 @@ pub trait ModuleAccess: Sync { &self.as_module().field_handles } + fn variant_field_handles(&self) -> &[VariantFieldHandle] { + &self.as_module().variant_field_handles + } + + fn struct_variant_handles(&self) -> &[StructVariantHandle] { + &self.as_module().struct_variant_handles + } + fn struct_instantiations(&self) -> &[StructDefInstantiation] { &self.as_module().struct_def_instantiations } @@ -136,6 +171,14 @@ pub trait ModuleAccess: Sync { &self.as_module().field_instantiations } + fn variant_field_instantiations(&self) -> &[VariantFieldInstantiation] { + &self.as_module().variant_field_instantiations + } + + fn struct_variant_instantiations(&self) -> &[StructVariantInstantiation] { + &self.as_module().struct_variant_instantiations + } + fn signatures(&self) -> &[Signature] { &self.as_module().signatures } 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 58367db2aa2d0..840754c3a565f 100644 --- a/third_party/move/move-binary-format/src/binary_views.rs +++ b/third_party/move/move-binary-format/src/binary_views.rs @@ -13,7 +13,9 @@ use crate::{ FunctionInstantiation, FunctionInstantiationIndex, IdentifierIndex, ModuleHandle, ModuleHandleIndex, Signature, SignatureIndex, SignatureToken, StructDefInstantiation, StructDefInstantiationIndex, StructDefinition, StructDefinitionIndex, StructHandle, - StructHandleIndex, + StructHandleIndex, StructVariantHandle, StructVariantHandleIndex, + StructVariantInstantiation, StructVariantInstantiationIndex, VariantFieldHandle, + VariantFieldHandleIndex, VariantFieldInstantiation, VariantFieldInstantiationIndex, }, CompiledModule, }; @@ -157,6 +159,13 @@ impl<'a> BinaryIndexedView<'a> { } } + pub fn variant_field_handles(&self) -> Option<&[VariantFieldHandle]> { + match self { + BinaryIndexedView::Module(module) => Some(&module.variant_field_handles), + BinaryIndexedView::Script(_) => None, + } + } + pub fn field_handle_at(&self, idx: FieldHandleIndex) -> PartialVMResult<&FieldHandle> { match self { BinaryIndexedView::Module(module) => Ok(module.field_handle_at(idx)), @@ -166,6 +175,42 @@ impl<'a> BinaryIndexedView<'a> { } } + pub fn variant_field_handle_at( + &self, + idx: VariantFieldHandleIndex, + ) -> PartialVMResult<&VariantFieldHandle> { + match self { + BinaryIndexedView::Module(module) => Ok(module.variant_field_handle_at(idx)), + BinaryIndexedView::Script(_) => { + Err(PartialVMError::new(StatusCode::INVALID_OPERATION_IN_SCRIPT)) + }, + } + } + + pub fn struct_variant_handle_at( + &self, + idx: StructVariantHandleIndex, + ) -> PartialVMResult<&StructVariantHandle> { + match self { + BinaryIndexedView::Module(module) => Ok(module.struct_variant_handle_at(idx)), + BinaryIndexedView::Script(_) => { + Err(PartialVMError::new(StatusCode::INVALID_OPERATION_IN_SCRIPT)) + }, + } + } + + pub fn struct_variant_instantiation_at( + &self, + idx: StructVariantInstantiationIndex, + ) -> PartialVMResult<&StructVariantInstantiation> { + match self { + BinaryIndexedView::Module(module) => Ok(module.struct_variant_instantiation_at(idx)), + BinaryIndexedView::Script(_) => { + Err(PartialVMError::new(StatusCode::INVALID_OPERATION_IN_SCRIPT)) + }, + } + } + pub fn friend_decls(&self) -> Option<&[ModuleHandle]> { match self { BinaryIndexedView::Module(module) => Some(module.friend_decls()), @@ -199,6 +244,13 @@ impl<'a> BinaryIndexedView<'a> { } } + pub fn variant_field_instantiations(&self) -> Option<&[VariantFieldInstantiation]> { + match self { + BinaryIndexedView::Module(module) => Some(&module.variant_field_instantiations), + BinaryIndexedView::Script(_) => None, + } + } + pub fn field_instantiation_at( &self, idx: FieldInstantiationIndex, @@ -211,6 +263,18 @@ impl<'a> BinaryIndexedView<'a> { } } + pub fn variant_field_instantiation_at( + &self, + idx: VariantFieldInstantiationIndex, + ) -> PartialVMResult<&VariantFieldInstantiation> { + match self { + BinaryIndexedView::Module(module) => Ok(module.variant_field_instantiation_at(idx)), + BinaryIndexedView::Script(_) => { + Err(PartialVMError::new(StatusCode::INVALID_OPERATION_IN_SCRIPT)) + }, + } + } + pub fn struct_defs(&self) -> Option<&[StructDefinition]> { match self { BinaryIndexedView::Module(module) => Some(module.struct_defs()), @@ -227,6 +291,20 @@ impl<'a> BinaryIndexedView<'a> { } } + pub fn struct_variant_handles(&self) -> Option<&[StructVariantHandle]> { + match self { + BinaryIndexedView::Module(module) => Some(&module.struct_variant_handles), + BinaryIndexedView::Script(_) => None, + } + } + + pub fn struct_variant_instantiations(&self) -> Option<&[StructVariantInstantiation]> { + match self { + BinaryIndexedView::Module(module) => Some(&module.struct_variant_instantiations), + BinaryIndexedView::Script(_) => None, + } + } + pub fn function_defs(&self) -> Option<&[FunctionDefinition]> { match self { BinaryIndexedView::Module(module) => Some(module.function_defs()), 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 c877872fe3ffe..a3629a450af90 100644 --- a/third_party/move/move-binary-format/src/check_bounds.rs +++ b/third_party/move/move-binary-format/src/check_bounds.rs @@ -10,10 +10,12 @@ use crate::{ }, file_format::{ AbilitySet, Bytecode, CodeOffset, CodeUnit, CompiledModule, CompiledScript, Constant, - FieldHandle, FieldInstantiation, FunctionDefinition, FunctionDefinitionIndex, - FunctionHandle, FunctionInstantiation, LocalIndex, ModuleHandle, Signature, SignatureIndex, - SignatureToken, StructDefInstantiation, StructDefinition, StructFieldInformation, - StructHandle, TableIndex, TypeParameterIndex, + FieldDefinition, FieldHandle, FieldInstantiation, FunctionDefinition, + FunctionDefinitionIndex, FunctionHandle, FunctionInstantiation, LocalIndex, ModuleHandle, + Signature, SignatureIndex, SignatureToken, StructDefInstantiation, StructDefinition, + StructFieldInformation, StructHandle, StructVariantHandle, StructVariantInstantiation, + TableIndex, TypeParameterIndex, VariantFieldHandle, VariantFieldInstantiation, + VariantIndex, }, internals::ModuleIndex, IndexKind, @@ -95,7 +97,38 @@ impl<'a> BoundsChecker<'a> { self.check_function_instantiations()?; self.check_field_instantiations()?; self.check_struct_defs()?; - self.check_function_defs() + self.check_function_defs()?; + // Since bytecode version 7 + self.check_table( + self.view.variant_field_handles(), + Self::check_variant_field_handle, + )?; + self.check_table( + self.view.struct_variant_handles(), + Self::check_struct_variant_handle, + )?; + self.check_table( + self.view.variant_field_instantiations(), + Self::check_variant_field_instantiation, + )?; + self.check_table( + self.view.struct_variant_instantiations(), + Self::check_struct_variant_instantiation, + )?; + Ok(()) + } + + fn check_table( + &self, + table: Option<&[T]>, + checker: impl Fn(&Self, &T) -> PartialVMResult<()>, + ) -> PartialVMResult<()> { + if let Some(table) = table { + for elem in table { + checker(self, elem)? + } + } + Ok(()) } fn check_signatures(&self) -> PartialVMResult<()> { @@ -169,8 +202,10 @@ impl<'a> BoundsChecker<'a> { } fn check_struct_defs(&self) -> PartialVMResult<()> { - for struct_def in self.view.struct_defs().into_iter().flatten() { - self.check_struct_def(struct_def)? + for (struct_def_idx, struct_def) in + self.view.struct_defs().into_iter().flatten().enumerate() + { + self.check_struct_def(struct_def, struct_def_idx)? } Ok(()) } @@ -222,10 +257,7 @@ impl<'a> BoundsChecker<'a> { .struct_defs() .and_then(|d| d.get(field_handle.owner.into_index())) { - let fields_count = match &struct_def.field_information { - StructFieldInformation::Native => 0, - StructFieldInformation::Declared(fields) => fields.len(), - }; + let fields_count = struct_def.field_information.field_count(None); if field_handle.field as usize >= fields_count { return Err(bounds_error( StatusCode::INDEX_OUT_OF_BOUNDS, @@ -238,6 +270,47 @@ impl<'a> BoundsChecker<'a> { Ok(()) } + fn check_variant_field_handle(&self, field_handle: &VariantFieldHandle) -> PartialVMResult<()> { + check_bounds_impl_opt(&self.view.struct_defs(), field_handle.struct_index)?; + let struct_def = self.view.struct_def_at(field_handle.struct_index)?; + for variant in &field_handle.variants { + Self::check_variant_index(struct_def, *variant)?; + let field_count = struct_def.field_information.field_count(Some(*variant)); + if field_handle.field as usize >= field_count { + return Err(bounds_error( + StatusCode::INDEX_OUT_OF_BOUNDS, + IndexKind::MemberCount, + field_handle.field, + field_count, + )); + } + } + Ok(()) + } + + fn check_struct_variant_handle(&self, handle: &StructVariantHandle) -> PartialVMResult<()> { + check_bounds_impl_opt(&self.view.struct_defs(), handle.struct_index)?; + let struct_def = self.view.struct_def_at(handle.struct_index)?; + Self::check_variant_index(struct_def, handle.variant) + } + + fn check_variant_index( + struct_def: &StructDefinition, + variant_index: VariantIndex, + ) -> PartialVMResult<()> { + let count = struct_def.field_information.variant_count(); + if (variant_index as usize) >= count { + Err(bounds_error( + StatusCode::INDEX_OUT_OF_BOUNDS, + IndexKind::MemberCount, + variant_index, + count, + )) + } else { + Ok(()) + } + } + fn check_struct_instantiation( &self, struct_instantiation: &StructDefInstantiation, @@ -265,6 +338,22 @@ impl<'a> BoundsChecker<'a> { check_bounds_impl(self.view.signatures(), field_instantiation.type_parameters) } + fn check_variant_field_instantiation( + &self, + inst: &VariantFieldInstantiation, + ) -> PartialVMResult<()> { + check_bounds_impl_opt(&self.view.variant_field_handles(), inst.handle)?; + check_bounds_impl(self.view.signatures(), inst.type_parameters) + } + + fn check_struct_variant_instantiation( + &self, + inst: &StructVariantInstantiation, + ) -> PartialVMResult<()> { + check_bounds_impl_opt(&self.view.struct_variant_handles(), inst.handle)?; + check_bounds_impl(self.view.signatures(), inst.type_parameters) + } + fn check_signature(&self, signature: &Signature) -> PartialVMResult<()> { for ty in &signature.0 { self.check_type(ty)? @@ -276,25 +365,54 @@ impl<'a> BoundsChecker<'a> { self.check_type(&constant.type_) } - fn check_struct_def(&self, struct_def: &StructDefinition) -> PartialVMResult<()> { + fn check_struct_def( + &self, + struct_def: &StructDefinition, + struct_def_idx: usize, + ) -> PartialVMResult<()> { check_bounds_impl(self.view.struct_handles(), struct_def.struct_handle)?; - // check signature (type) and type parameter for the field type - if let StructFieldInformation::Declared(fields) = &struct_def.field_information { - let type_param_count = self - .view - .struct_handles() - .get(struct_def.struct_handle.into_index()) - .map_or(0, |sh| sh.type_parameters.len()); - // field signatures are inlined - for field in fields { - check_bounds_impl(self.view.identifiers(), field.name)?; - self.check_type(&field.signature.0)?; - self.check_type_parameters_in_ty(&field.signature.0, type_param_count)?; - } + // check signature (type) and type parameter for the field types + let type_param_count = self + .view + .struct_handles() + .get(struct_def.struct_handle.into_index()) + .map_or(0, |sh| sh.type_parameters.len()); + match &struct_def.field_information { + StructFieldInformation::Declared(fields) => { + // field signatures are inlined + for field in fields { + self.check_field_def(type_param_count, field)?; + } + }, + StructFieldInformation::DeclaredVariants(variants) => { + for field in variants.iter().flat_map(|v| v.fields.iter()) { + self.check_field_def(type_param_count, field)?; + } + if variants.is_empty() { + // Empty variants are not allowed + return Err(verification_error( + StatusCode::ZERO_VARIANTS_ERROR, + IndexKind::StructDefinition, + struct_def_idx as TableIndex, + )); + } + }, + StructFieldInformation::Native => {}, } Ok(()) } + fn check_field_def( + &self, + type_param_count: usize, + field: &FieldDefinition, + ) -> Result<(), PartialVMError> { + check_bounds_impl(self.view.identifiers(), field.name)?; + self.check_type(&field.signature.0)?; + self.check_type_parameters_in_ty(&field.signature.0, type_param_count)?; + Ok(()) + } + fn check_function_def( &mut self, function_def_idx: usize, @@ -379,13 +497,18 @@ impl<'a> BoundsChecker<'a> { *idx, bytecode_offset, )?, + MutBorrowVariantField(idx) | ImmBorrowVariantField(idx) => self + .check_code_unit_bounds_impl_opt( + &self.view.variant_field_handles(), + *idx, + bytecode_offset, + )?, MutBorrowFieldGeneric(idx) | ImmBorrowFieldGeneric(idx) => { self.check_code_unit_bounds_impl_opt( &self.view.field_instantiations(), *idx, bytecode_offset, )?; - // check type parameters in borrow are bound to the function type parameters if let Some(field_inst) = self .view .field_instantiations() @@ -397,6 +520,23 @@ impl<'a> BoundsChecker<'a> { )?; } }, + MutBorrowVariantFieldGeneric(idx) | ImmBorrowVariantFieldGeneric(idx) => { + self.check_code_unit_bounds_impl_opt( + &self.view.variant_field_instantiations(), + *idx, + bytecode_offset, + )?; + if let Some(field_inst) = self + .view + .variant_field_instantiations() + .and_then(|f| f.get(idx.into_index())) + { + self.check_type_parameters_in_signature( + field_inst.type_parameters, + type_param_count, + )?; + } + }, Call(idx) => self.check_code_unit_bounds_impl( self.view.function_handles(), *idx, @@ -408,7 +548,6 @@ impl<'a> BoundsChecker<'a> { *idx, bytecode_offset, )?; - // check type parameters in call are bound to the function type parameters if let Some(func_inst) = self.view.function_instantiations().get(idx.into_index()) { @@ -425,6 +564,12 @@ impl<'a> BoundsChecker<'a> { *idx, bytecode_offset, )?, + PackVariant(idx) | UnpackVariant(idx) | TestVariant(idx) => self + .check_code_unit_bounds_impl_opt( + &self.view.struct_variant_handles(), + *idx, + bytecode_offset, + )?, PackGeneric(idx) | UnpackGeneric(idx) | ExistsGeneric(idx) @@ -449,6 +594,24 @@ impl<'a> BoundsChecker<'a> { )?; } }, + PackVariantGeneric(idx) | UnpackVariantGeneric(idx) | TestVariantGeneric(idx) => { + self.check_code_unit_bounds_impl_opt( + &self.view.struct_variant_instantiations(), + *idx, + bytecode_offset, + )?; + // check type parameters + if let Some(struct_variant_inst) = self + .view + .struct_variant_instantiations() + .and_then(|s| s.get(idx.into_index())) + { + self.check_type_parameters_in_signature( + struct_variant_inst.type_parameters, + type_param_count, + )?; + } + }, // Instructions that refer to this code block. BrTrue(offset) | BrFalse(offset) | Branch(offset) => { let offset = *offset as usize; 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 b291a2612c0a8..cd06a8f8554a4 100644 --- a/third_party/move/move-binary-format/src/check_complexity.rs +++ b/third_party/move/move-binary-format/src/check_complexity.rs @@ -7,7 +7,8 @@ use crate::{ file_format::{ Bytecode, CodeUnit, CompiledModule, CompiledScript, FieldInstantiationIndex, FunctionInstantiationIndex, IdentifierIndex, ModuleHandleIndex, SignatureIndex, - SignatureToken, StructDefInstantiationIndex, StructFieldInformation, TableIndex, + SignatureToken, StructDefInstantiationIndex, StructFieldInformation, + StructVariantInstantiationIndex, TableIndex, VariantFieldInstantiationIndex, }, }; use move_core_types::vm_status::StatusCode; @@ -134,6 +135,22 @@ impl<'a> BinaryComplexityMeter<'a> { self.meter_signature(struct_inst.type_parameters) } + fn meter_struct_variant_instantiation( + &self, + struct_inst_idx: StructVariantInstantiationIndex, + ) -> PartialVMResult<()> { + let struct_variant_insts = + self.resolver + .struct_variant_instantiations() + .ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message( + "Can't get struct variant instantiations -- not a module.".to_string(), + ) + })?; + let struct_variant_inst = safe_get_table(struct_variant_insts, struct_inst_idx.0)?; + self.meter_signature(struct_variant_inst.type_parameters) + } + fn meter_struct_def_instantiations(&self) -> PartialVMResult<()> { let struct_insts = self.resolver.struct_instantiations().ok_or_else(|| { PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) @@ -159,6 +176,23 @@ impl<'a> BinaryComplexityMeter<'a> { self.meter_signature(field_inst.type_parameters) } + fn meter_variant_field_instantiation( + &self, + variant_field_inst_idx: VariantFieldInstantiationIndex, + ) -> PartialVMResult<()> { + let variant_field_insts = + self.resolver + .variant_field_instantiations() + .ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message( + "Can't get variant field instantiations -- not a module.".to_string(), + ) + })?; + let field_inst = safe_get_table(variant_field_insts, variant_field_inst_idx.0)?; + + self.meter_signature(field_inst.type_parameters) + } + fn meter_field_instantiations(&self) -> PartialVMResult<()> { let field_insts = self.resolver.field_instantiations().ok_or_else(|| { PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) @@ -201,13 +235,20 @@ impl<'a> BinaryComplexityMeter<'a> { })?; for sdef in struct_defs { - let fields = match &sdef.field_information { + match &sdef.field_information { StructFieldInformation::Native => continue, - StructFieldInformation::Declared(fields) => fields, - }; - - for field in fields { - self.charge(field.signature.0.num_nodes() as u64)?; + StructFieldInformation::Declared(fields) => { + for field in fields { + self.charge(field.signature.0.num_nodes() as u64)?; + } + }, + StructFieldInformation::DeclaredVariants(variants) => { + for variant in variants { + for field in &variant.fields { + self.charge(field.signature.0.num_nodes() as u64)?; + } + } + }, } } Ok(()) @@ -226,6 +267,9 @@ impl<'a> BinaryComplexityMeter<'a> { PackGeneric(idx) | UnpackGeneric(idx) => { self.meter_struct_instantiation(*idx)?; }, + PackVariantGeneric(idx) | UnpackVariantGeneric(idx) | TestVariantGeneric(idx) => { + self.meter_struct_variant_instantiation(*idx)?; + }, ExistsGeneric(idx) | MoveFromGeneric(idx) | MoveToGeneric(idx) @@ -236,6 +280,9 @@ impl<'a> BinaryComplexityMeter<'a> { ImmBorrowFieldGeneric(idx) | MutBorrowFieldGeneric(idx) => { self.meter_field_instantiation(*idx)?; }, + ImmBorrowVariantFieldGeneric(idx) | MutBorrowVariantFieldGeneric(idx) => { + self.meter_variant_field_instantiation(*idx)?; + }, VecPack(idx, _) | VecLen(idx) | VecImmBorrow(idx) @@ -249,14 +296,70 @@ impl<'a> BinaryComplexityMeter<'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 => (), + Pop + | Ret + | Branch(_) + | BrTrue(_) + | BrFalse(_) + | LdU8(_) + | LdU16(_) + | LdU32(_) + | LdU64(_) + | LdU128(_) + | LdU256(_) + | LdConst(_) + | CastU8 + | CastU16 + | CastU32 + | CastU64 + | CastU128 + | CastU256 + | LdTrue + | LdFalse + | Call(_) + | Pack(_) + | Unpack(_) + | PackVariant(_) + | UnpackVariant(_) + | TestVariant(_) + | 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(_) + | MutBorrowVariantField(_) + | ImmBorrowVariantField(_) + | MutBorrowGlobal(_) + | ImmBorrowGlobal(_) + | Exists(_) + | MoveTo(_) + | MoveFrom(_) + | Abort + | Nop => (), } } Ok(()) diff --git a/third_party/move/move-binary-format/src/compatibility.rs b/third_party/move/move-binary-format/src/compatibility.rs index 0f3abccc0c000..8345aabf54737 100644 --- a/third_party/move/move-binary-format/src/compatibility.rs +++ b/third_party/move/move-binary-format/src/compatibility.rs @@ -105,6 +105,7 @@ impl Compatibility { struct_and_pub_function_linking = false; } if new_struct.fields != old_struct.fields { + // TODO(#13806): implement struct variants // Fields changed. Code in this module will fail at runtime if it tries to // read a previously published struct value // TODO: this is a stricter definition than required. We could in principle diff --git a/third_party/move/move-binary-format/src/deserializer.rs b/third_party/move/move-binary-format/src/deserializer.rs index 0f3f012b0a4e5..cc9d3bc549371 100644 --- a/third_party/move/move-binary-format/src/deserializer.rs +++ b/third_party/move/move-binary-format/src/deserializer.rs @@ -285,6 +285,42 @@ fn load_struct_def_inst_index( )?)) } +fn load_variant_field_handle_index( + cursor: &mut VersionedCursor, +) -> BinaryLoaderResult { + Ok(VariantFieldHandleIndex(read_uleb_internal( + cursor, + TABLE_INDEX_MAX, + )?)) +} + +fn load_variant_field_inst_index( + cursor: &mut VersionedCursor, +) -> BinaryLoaderResult { + Ok(VariantFieldInstantiationIndex(read_uleb_internal( + cursor, + TABLE_INDEX_MAX, + )?)) +} + +fn load_struct_variant_handle_index( + cursor: &mut VersionedCursor, +) -> BinaryLoaderResult { + Ok(StructVariantHandleIndex(read_uleb_internal( + cursor, + TABLE_INDEX_MAX, + )?)) +} + +fn load_struct_variant_inst_index( + cursor: &mut VersionedCursor, +) -> BinaryLoaderResult { + Ok(StructVariantInstantiationIndex(read_uleb_internal( + cursor, + TABLE_INDEX_MAX, + )?)) +} + fn load_constant_pool_index(cursor: &mut VersionedCursor) -> BinaryLoaderResult { Ok(ConstantPoolIndex(read_uleb_internal( cursor, @@ -308,6 +344,10 @@ fn load_field_count(cursor: &mut VersionedCursor) -> BinaryLoaderResult { read_uleb_internal(cursor, FIELD_COUNT_MAX) } +fn load_variant_count(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + read_uleb_internal(cursor, VARIANT_COUNT_MAX) +} + fn load_type_parameter_count(cursor: &mut VersionedCursor) -> BinaryLoaderResult { read_uleb_internal(cursor, TYPE_PARAMETER_COUNT_MAX) } @@ -344,6 +384,10 @@ fn load_field_offset(cursor: &mut VersionedCursor) -> BinaryLoaderResult { read_uleb_internal(cursor, FIELD_OFFSET_MAX) } +fn load_variant_offset(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + read_uleb_internal(cursor, VARIANT_OFFSET_MAX) +} + fn load_table_count(cursor: &mut VersionedCursor) -> BinaryLoaderResult { read_uleb_internal(cursor, TABLE_COUNT_MAX) } @@ -501,6 +545,23 @@ fn check_tables(tables: &mut Vec, binary_len: usize) -> BinaryLoaderResul Ok(current_offset) } +impl Table { + /// Generic function to deserialize a table into a vector of given type. + fn load( + &self, + binary: &VersionedBinary, + result: &mut Vec, + deserializer: impl Fn(&mut VersionedCursor) -> BinaryLoaderResult, + ) -> BinaryLoaderResult<()> { + let start = self.offset as usize; + let end = start + self.count as usize; + let mut cursor = binary.new_cursor(start, end); + while cursor.position() < self.count as u64 { + result.push(deserializer(&mut cursor)?) + } + Ok(()) + } +} // // Trait to read common tables from CompiledScript or CompiledModule // @@ -624,22 +685,28 @@ fn build_common_tables( for table in tables { match table.kind { TableType::MODULE_HANDLES => { - load_module_handles(binary, table, common.get_module_handles())?; + table.load(binary, common.get_module_handles(), load_module_handle)?; }, TableType::STRUCT_HANDLES => { - load_struct_handles(binary, table, common.get_struct_handles())?; + table.load(binary, common.get_struct_handles(), load_struct_handle)?; }, TableType::FUNCTION_HANDLES => { - load_function_handles(binary, table, common.get_function_handles())?; + table.load(binary, common.get_function_handles(), |cursor| { + load_function_handle(binary.version(), cursor) + })?; }, TableType::FUNCTION_INST => { - load_function_instantiations(binary, table, common.get_function_instantiations())?; + table.load( + binary, + common.get_function_instantiations(), + load_function_instantiation, + )?; }, TableType::SIGNATURES => { - load_signatures(binary, table, common.get_signatures())?; + table.load(binary, common.get_signatures(), load_signature)?; }, TableType::CONSTANT_POOL => { - load_constant_pool(binary, table, common.get_constant_pool())?; + table.load(binary, common.get_constant_pool(), load_constant)?; }, TableType::METADATA => { if binary.version() < VERSION_5 { @@ -650,18 +717,22 @@ fn build_common_tables( )), ); } - load_metadata(binary, table, common.get_metadata())?; + table.load(binary, common.get_metadata(), load_metadata_entry)?; }, TableType::IDENTIFIERS => { - load_identifiers(binary, table, common.get_identifiers())?; + table.load(binary, common.get_identifiers(), load_identifier)?; }, TableType::ADDRESS_IDENTIFIERS => { - load_address_identifiers(binary, table, common.get_address_identifiers())?; + table.load( + binary, + common.get_address_identifiers(), + load_address_identifier, + )?; }, TableType::FUNCTION_DEFS | TableType::STRUCT_DEFS | TableType::STRUCT_DEF_INST - | TableType::FIELD_HANDLE + | TableType::FIELD_HANDLES | TableType::FIELD_INST => continue, TableType::FRIEND_DECLS => { // friend declarations do not exist before VERSION_2 @@ -672,6 +743,19 @@ fn build_common_tables( } continue; }, + TableType::VARIANT_FIELD_HANDLES + | TableType::VARIANT_FIELD_INST + | TableType::STRUCT_VARIANT_HANDLES + | TableType::STRUCT_VARIANT_INST => { + if binary.version() < VERSION_7 { + return Err( + PartialVMError::new(StatusCode::MALFORMED).with_message(format!( + "Struct variants not applicable in bytecode version {}", + binary.version() + )), + ); + } + }, } } Ok(()) @@ -686,23 +770,60 @@ fn build_module_tables( for table in tables { match table.kind { TableType::STRUCT_DEFS => { - load_struct_defs(binary, table, &mut module.struct_defs)?; + table.load(binary, &mut module.struct_defs, load_struct_def)?; }, TableType::STRUCT_DEF_INST => { - load_struct_instantiations(binary, table, &mut module.struct_def_instantiations)?; + table.load( + binary, + &mut module.struct_def_instantiations, + load_struct_instantiation, + )?; }, TableType::FUNCTION_DEFS => { - load_function_defs(binary, table, &mut module.function_defs)?; + table.load(binary, &mut module.function_defs, load_function_def)?; }, - TableType::FIELD_HANDLE => { - load_field_handles(binary, table, &mut module.field_handles)?; + TableType::FIELD_HANDLES => { + table.load(binary, &mut module.field_handles, load_field_handle)?; }, TableType::FIELD_INST => { - load_field_instantiations(binary, table, &mut module.field_instantiations)?; + table.load( + binary, + &mut module.field_instantiations, + load_field_instantiation, + )?; }, TableType::FRIEND_DECLS => { - load_module_handles(binary, table, &mut module.friend_decls)?; + table.load(binary, &mut module.friend_decls, load_module_handle)?; + }, + TableType::VARIANT_FIELD_HANDLES => { + table.load( + binary, + &mut module.variant_field_handles, + load_variant_field_handle, + )?; }, + TableType::VARIANT_FIELD_INST => { + table.load( + binary, + &mut module.variant_field_instantiations, + load_variant_field_instantiation, + )?; + }, + TableType::STRUCT_VARIANT_HANDLES => { + table.load( + binary, + &mut module.struct_variant_handles, + load_struct_variant_handle, + )?; + }, + TableType::STRUCT_VARIANT_INST => { + table.load( + binary, + &mut module.struct_variant_instantiations, + load_struct_variant_instantiation, + )?; + }, + // The remaining are handled via common tables TableType::MODULE_HANDLES | TableType::STRUCT_HANDLES | TableType::FUNCTION_HANDLES @@ -742,8 +863,12 @@ fn build_script_tables( | TableType::STRUCT_DEF_INST | TableType::FUNCTION_DEFS | TableType::FIELD_INST - | TableType::FIELD_HANDLE - | TableType::FRIEND_DECLS => { + | TableType::FIELD_HANDLES + | TableType::FRIEND_DECLS + | TableType::VARIANT_FIELD_HANDLES + | TableType::VARIANT_FIELD_INST + | TableType::STRUCT_VARIANT_HANDLES + | TableType::STRUCT_VARIANT_INST => { return Err(PartialVMError::new(StatusCode::MALFORMED) .with_message("Bad table in Script".to_string())); }, @@ -752,213 +877,109 @@ fn build_script_tables( Ok(()) } -/// Builds the `ModuleHandle` table. -fn load_module_handles( - binary: &VersionedBinary, - table: &Table, - module_handles: &mut Vec, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - while cursor.position() < table.count as u64 { - let address = load_address_identifier_index(&mut cursor)?; - let name = load_identifier_index(&mut cursor)?; - module_handles.push(ModuleHandle { address, name }); - } - Ok(()) +fn load_module_handle(cursor: &mut VersionedCursor) -> Result { + let address = load_address_identifier_index(cursor)?; + let name = load_identifier_index(cursor)?; + Ok(ModuleHandle { address, name }) } -/// Builds the `StructHandle` table. -fn load_struct_handles( - binary: &VersionedBinary, - table: &Table, - struct_handles: &mut Vec, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - while cursor.position() < table.count as u64 { - let module = load_module_handle_index(&mut cursor)?; - let name = load_identifier_index(&mut cursor)?; - let abilities = load_ability_set(&mut cursor, AbilitySetPosition::StructHandle)?; - let type_parameters = load_struct_type_parameters(&mut cursor)?; - struct_handles.push(StructHandle { - module, - name, - abilities, - type_parameters, - }); - } - Ok(()) +fn load_struct_handle(cursor: &mut VersionedCursor) -> Result { + let module = load_module_handle_index(cursor)?; + let name = load_identifier_index(cursor)?; + let abilities = load_ability_set(cursor, AbilitySetPosition::StructHandle)?; + let type_parameters = load_struct_type_parameters(cursor)?; + Ok(StructHandle { + module, + name, + abilities, + type_parameters, + }) } -/// Builds the `FunctionHandle` table. -fn load_function_handles( - binary: &VersionedBinary, - table: &Table, - function_handles: &mut Vec, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - while cursor.position() < table.count as u64 { - let module = load_module_handle_index(&mut cursor)?; - let name = load_identifier_index(&mut cursor)?; - let parameters = load_signature_index(&mut cursor)?; - let return_ = load_signature_index(&mut cursor)?; - let type_parameters = - load_ability_sets(&mut cursor, AbilitySetPosition::FunctionTypeParameters)?; - - let accesses = if binary.version() >= VERSION_7 { - load_access_specifiers(&mut cursor)? - } else { - None - }; +fn load_function_handle( + version: u32, + cursor: &mut VersionedCursor, +) -> Result { + let module = load_module_handle_index(cursor)?; + let name = load_identifier_index(cursor)?; + let parameters = load_signature_index(cursor)?; + let return_ = load_signature_index(cursor)?; + let type_parameters = load_ability_sets(cursor, AbilitySetPosition::FunctionTypeParameters)?; - function_handles.push(FunctionHandle { - module, - name, - parameters, - return_, - type_parameters, - access_specifiers: accesses, - }); - } - Ok(()) -} + let accesses = if version >= VERSION_7 { + load_access_specifiers(cursor)? + } else { + None + }; -/// Builds the `StructInstantiation` table. -fn load_struct_instantiations( - binary: &VersionedBinary, - table: &Table, - struct_insts: &mut Vec, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - - while cursor.position() < table.count as u64 { - let def = load_struct_def_index(&mut cursor)?; - let type_parameters = load_signature_index(&mut cursor)?; - struct_insts.push(StructDefInstantiation { - def, - type_parameters, - }); - } - Ok(()) + Ok(FunctionHandle { + module, + name, + parameters, + return_, + type_parameters, + access_specifiers: accesses, + }) } -/// Builds the `FunctionInstantiation` table. -fn load_function_instantiations( - binary: &VersionedBinary, - table: &Table, - func_insts: &mut Vec, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - while cursor.position() < table.count as u64 { - let handle = load_function_handle_index(&mut cursor)?; - let type_parameters = load_signature_index(&mut cursor)?; - func_insts.push(FunctionInstantiation { - handle, - type_parameters, - }); - } - Ok(()) +fn load_struct_instantiation( + cursor: &mut VersionedCursor, +) -> Result { + let def = load_struct_def_index(cursor)?; + let type_parameters = load_signature_index(cursor)?; + Ok(StructDefInstantiation { + def, + type_parameters, + }) } -/// Builds the `IdentifierPool`. -fn load_identifiers( - binary: &VersionedBinary, - table: &Table, - identifiers: &mut IdentifierPool, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - while cursor.position() < u64::from(table.count) { - let size = load_identifier_size(&mut cursor)?; - let mut buffer: Vec = vec![0u8; size]; - if let Ok(count) = cursor.read(&mut buffer) { - if count != size { - return Err(PartialVMError::new(StatusCode::MALFORMED) - .with_message("Bad Identifier pool size".to_string())); - } - let s = Identifier::from_utf8(buffer).map_err(|_| { - PartialVMError::new(StatusCode::MALFORMED) - .with_message("Invalid Identifier".to_string()) - })?; - identifiers.push(s); - } - } - Ok(()) +fn load_function_instantiation( + cursor: &mut VersionedCursor, +) -> Result { + let handle = load_function_handle_index(cursor)?; + let type_parameters = load_signature_index(cursor)?; + Ok(FunctionInstantiation { + handle, + type_parameters, + }) } -/// Builds the `AddressIdentifierPool`. -fn load_address_identifiers( - binary: &VersionedBinary, - table: &Table, - addresses: &mut AddressIdentifierPool, -) -> BinaryLoaderResult<()> { - let mut start = table.offset as usize; - if table.count as usize % AccountAddress::LENGTH != 0 { - return Err(PartialVMError::new(StatusCode::MALFORMED) - .with_message("Bad Address Identifier pool size".to_string())); - } - for _i in 0..table.count as usize / AccountAddress::LENGTH { - let end_addr = start + AccountAddress::LENGTH; - let address = binary.slice(start, end_addr).try_into(); - if address.is_err() { - return Err(PartialVMError::new(StatusCode::MALFORMED) - .with_message("Invalid Address format".to_string())); - } - start = end_addr; - - addresses.push(address.unwrap()); +fn load_identifier(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + let size = load_identifier_size(cursor)?; + let mut buffer: Vec = vec![0u8; size]; + if !cursor.read(&mut buffer).map(|count| count == size).unwrap() { + Err(PartialVMError::new(StatusCode::MALFORMED) + .with_message("Bad Identifier pool size".to_string()))? } - Ok(()) + Identifier::from_utf8(buffer).map_err(|_| { + PartialVMError::new(StatusCode::MALFORMED).with_message("Invalid Identifier".to_string()) + }) } -/// Builds the `ConstantPool`. -fn load_constant_pool( - binary: &VersionedBinary, - table: &Table, - constants: &mut ConstantPool, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - while cursor.position() < u64::from(table.count) { - constants.push(load_constant(&mut cursor)?) +fn load_address_identifier(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + let mut buffer: Vec = vec![0u8; AccountAddress::LENGTH]; + if !cursor + .read(&mut buffer) + .map(|count| count == AccountAddress::LENGTH) + .unwrap() + { + Err(PartialVMError::new(StatusCode::MALFORMED) + .with_message("Bad Address pool size".to_string()))? } - Ok(()) + buffer.try_into().map_err(|_| { + PartialVMError::new(StatusCode::MALFORMED) + .with_message("Invalid Address format".to_string()) + }) } -/// Build a single `Constant` +/// Build a `Constant` fn load_constant(cursor: &mut VersionedCursor) -> BinaryLoaderResult { let type_ = load_signature_token(cursor)?; let data = load_byte_blob(cursor, load_constant_size)?; Ok(Constant { type_, data }) } -/// Builds a metadata vector. -fn load_metadata( - binary: &VersionedBinary, - table: &Table, - metadata: &mut Vec, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - while cursor.position() < u64::from(table.count) { - metadata.push(load_metadata_entry(&mut cursor)?) - } - Ok(()) -} - -/// Build a single metadata entry. +/// Build a metadata entry. fn load_metadata_entry(cursor: &mut VersionedCursor) -> BinaryLoaderResult { let key = load_byte_blob(cursor, load_metadata_key_size)?; let value = load_byte_blob(cursor, load_metadata_value_size)?; @@ -983,19 +1004,8 @@ fn load_byte_blob( Ok(data) } -/// Builds the `SignaturePool`. -fn load_signatures( - binary: &VersionedBinary, - table: &Table, - signatures: &mut SignaturePool, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - while cursor.position() < u64::from(table.count) { - signatures.push(Signature(load_signature_tokens(&mut cursor)?)); - } - Ok(()) +fn load_signature(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + Ok(Signature(load_signature_tokens(cursor)?)) } fn load_signature_tokens(cursor: &mut VersionedCursor) -> BinaryLoaderResult> { @@ -1352,37 +1362,39 @@ fn load_struct_type_parameter( }) } -/// Builds the `StructDefinition` table. -fn load_struct_defs( - binary: &VersionedBinary, - table: &Table, - struct_defs: &mut Vec, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - while cursor.position() < u64::from(table.count) { - let struct_handle = load_struct_handle_index(&mut cursor)?; - let field_information_flag = match cursor.read_u8() { - Ok(byte) => SerializedNativeStructFlag::from_u8(byte)?, - Err(_) => { - return Err(PartialVMError::new(StatusCode::MALFORMED) - .with_message("Invalid field info in struct".to_string())); - }, - }; - let field_information = match field_information_flag { - SerializedNativeStructFlag::NATIVE => StructFieldInformation::Native, - SerializedNativeStructFlag::DECLARED => { - let fields = load_field_defs(&mut cursor)?; - StructFieldInformation::Declared(fields) - }, - }; - struct_defs.push(StructDefinition { - struct_handle, - field_information, - }); - } - Ok(()) +fn load_struct_def(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + let struct_handle = load_struct_handle_index(cursor)?; + let field_information_flag = match cursor.read_u8() { + Ok(byte) => SerializedNativeStructFlag::from_u8(byte)?, + Err(_) => { + return Err(PartialVMError::new(StatusCode::MALFORMED) + .with_message("Invalid field info in struct".to_string())); + }, + }; + let field_information = match field_information_flag { + SerializedNativeStructFlag::NATIVE => StructFieldInformation::Native, + SerializedNativeStructFlag::DECLARED => { + let fields = load_field_defs(cursor)?; + StructFieldInformation::Declared(fields) + }, + SerializedNativeStructFlag::DECLARED_VARIANTS => { + if cursor.version() >= VERSION_7 { + let variants = load_variants(cursor)?; + StructFieldInformation::DeclaredVariants(variants) + } else { + return Err( + PartialVMError::new(StatusCode::MALFORMED).with_message(format!( + "Struct variants not supported in version {}", + cursor.version() + )), + ); + } + }, + }; + Ok(StructDefinition { + struct_handle, + field_information, + }) } fn load_field_defs(cursor: &mut VersionedCursor) -> BinaryLoaderResult> { @@ -1403,64 +1415,90 @@ fn load_field_def(cursor: &mut VersionedCursor) -> BinaryLoaderResult, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - while cursor.position() < u64::from(table.count) { - let func_def = load_function_def(&mut cursor)?; - func_defs.push(func_def); +fn load_variants(cursor: &mut VersionedCursor) -> BinaryLoaderResult> { + let mut variants = Vec::new(); + let variant_count = load_variant_count(cursor)?; + for _ in 0..variant_count { + variants.push(load_variant(cursor)?); } - Ok(()) + Ok(variants) } -fn load_field_handles( - binary: &VersionedBinary, - table: &Table, - field_handles: &mut Vec, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - loop { - if cursor.position() == u64::from(table.count) { - break; - } - let struct_idx = load_struct_def_index(&mut cursor)?; - let offset = load_field_offset(&mut cursor)?; - field_handles.push(FieldHandle { - owner: struct_idx, - field: offset, - }); - } - Ok(()) +#[inline(always)] +fn load_variant(cursor: &mut VersionedCursor) -> BinaryLoaderResult { + let name = load_identifier_index(cursor)?; + let fields = load_field_defs(cursor)?; + Ok(VariantDefinition { name, fields }) } -fn load_field_instantiations( - binary: &VersionedBinary, - table: &Table, - field_insts: &mut Vec, -) -> BinaryLoaderResult<()> { - let start = table.offset as usize; - let end = start + table.count as usize; - let mut cursor = binary.new_cursor(start, end); - loop { - if cursor.position() == u64::from(table.count) { - break; - } - let handle = load_field_handle_index(&mut cursor)?; - let type_parameters = load_signature_index(&mut cursor)?; - field_insts.push(FieldInstantiation { - handle, - type_parameters, - }); - } - Ok(()) +fn load_field_handle(cursor: &mut VersionedCursor) -> Result { + let struct_idx = load_struct_def_index(cursor)?; + let offset = load_field_offset(cursor)?; + Ok(FieldHandle { + owner: struct_idx, + field: offset, + }) +} + +fn load_field_instantiation( + cursor: &mut VersionedCursor, +) -> Result { + let handle = load_field_handle_index(cursor)?; + let type_parameters = load_signature_index(cursor)?; + Ok(FieldInstantiation { + handle, + type_parameters, + }) +} + +fn load_variant_field_handle( + cursor: &mut VersionedCursor, +) -> Result { + let owner = load_struct_def_index(cursor)?; + let offset = load_field_offset(cursor)?; + let variant_count = load_variant_count(cursor)?; + let mut variants = vec![]; + for _ in 0..variant_count { + variants.push(load_variant_offset(cursor)?) + } + Ok(VariantFieldHandle { + struct_index: owner, + variants, + field: offset, + }) +} + +fn load_variant_field_instantiation( + cursor: &mut VersionedCursor, +) -> Result { + let handle = load_variant_field_handle_index(cursor)?; + let type_parameters = load_signature_index(cursor)?; + Ok(VariantFieldInstantiation { + handle, + type_parameters, + }) +} + +fn load_struct_variant_handle( + cursor: &mut VersionedCursor, +) -> Result { + let struct_index = load_struct_def_index(cursor)?; + let variant = load_variant_offset(cursor)?; + Ok(StructVariantHandle { + struct_index, + variant, + }) +} + +fn load_struct_variant_instantiation( + cursor: &mut VersionedCursor, +) -> Result { + let handle = load_struct_variant_handle_index(cursor)?; + let type_parameters = load_signature_index(cursor)?; + Ok(StructVariantInstantiation { + handle, + type_parameters, + }) } /// Deserializes a `FunctionDefinition`. @@ -1581,15 +1619,32 @@ fn load_code(cursor: &mut VersionedCursor, code: &mut Vec) -> BinaryLo | Opcodes::VEC_PUSH_BACK | Opcodes::VEC_POP_BACK | Opcodes::VEC_UNPACK - | Opcodes::VEC_SWAP => { - if cursor.version() < VERSION_4 { - return Err( - PartialVMError::new(StatusCode::MALFORMED).with_message(format!( - "Vector operations not available before bytecode version {}", - VERSION_4 - )), - ); - } + | Opcodes::VEC_SWAP + if cursor.version() < VERSION_4 => + { + return Err( + PartialVMError::new(StatusCode::MALFORMED).with_message(format!( + "Vector operations not available before bytecode version {}", + VERSION_4 + )), + ); + }, + Opcodes::TEST_VARIANT + | Opcodes::TEST_VARIANT_GENERIC + | Opcodes::PACK_VARIANT + | Opcodes::PACK_VARIANT_GENERIC + | Opcodes::IMM_BORROW_VARIANT_FIELD + | Opcodes::IMM_BORROW_VARIANT_FIELD_GENERIC + | Opcodes::MUT_BORROW_VARIANT_FIELD + | Opcodes::MUT_BORROW_VARIANT_FIELD_GENERIC + if cursor.version() < VERSION_7 => + { + return Err( + PartialVMError::new(StatusCode::MALFORMED).with_message(format!( + "Struct variant operations not available before bytecode version {}", + VERSION_7 + )), + ); }, _ => {}, }; @@ -1654,12 +1709,42 @@ fn load_code(cursor: &mut VersionedCursor, code: &mut Vec) -> BinaryLo Opcodes::IMM_BORROW_FIELD_GENERIC => { Bytecode::ImmBorrowFieldGeneric(load_field_inst_index(cursor)?) }, + Opcodes::MUT_BORROW_VARIANT_FIELD => { + Bytecode::MutBorrowVariantField(load_variant_field_handle_index(cursor)?) + }, + Opcodes::MUT_BORROW_VARIANT_FIELD_GENERIC => { + Bytecode::MutBorrowVariantFieldGeneric(load_variant_field_inst_index(cursor)?) + }, + Opcodes::IMM_BORROW_VARIANT_FIELD => { + Bytecode::ImmBorrowVariantField(load_variant_field_handle_index(cursor)?) + }, + Opcodes::IMM_BORROW_VARIANT_FIELD_GENERIC => { + Bytecode::ImmBorrowVariantFieldGeneric(load_variant_field_inst_index(cursor)?) + }, Opcodes::CALL => Bytecode::Call(load_function_handle_index(cursor)?), Opcodes::CALL_GENERIC => Bytecode::CallGeneric(load_function_inst_index(cursor)?), Opcodes::PACK => Bytecode::Pack(load_struct_def_index(cursor)?), Opcodes::PACK_GENERIC => Bytecode::PackGeneric(load_struct_def_inst_index(cursor)?), Opcodes::UNPACK => Bytecode::Unpack(load_struct_def_index(cursor)?), Opcodes::UNPACK_GENERIC => Bytecode::UnpackGeneric(load_struct_def_inst_index(cursor)?), + Opcodes::PACK_VARIANT => { + Bytecode::PackVariant(load_struct_variant_handle_index(cursor)?) + }, + Opcodes::UNPACK_VARIANT => { + Bytecode::UnpackVariant(load_struct_variant_handle_index(cursor)?) + }, + Opcodes::PACK_VARIANT_GENERIC => { + Bytecode::PackVariantGeneric(load_struct_variant_inst_index(cursor)?) + }, + Opcodes::UNPACK_VARIANT_GENERIC => { + Bytecode::UnpackVariantGeneric(load_struct_variant_inst_index(cursor)?) + }, + Opcodes::TEST_VARIANT => { + Bytecode::TestVariant(load_struct_variant_handle_index(cursor)?) + }, + Opcodes::TEST_VARIANT_GENERIC => { + Bytecode::TestVariantGeneric(load_struct_variant_inst_index(cursor)?) + }, Opcodes::READ_REF => Bytecode::ReadRef, Opcodes::WRITE_REF => Bytecode::WriteRef, Opcodes::ADD => Bytecode::Add, @@ -1749,10 +1834,14 @@ impl TableType { 0xA => Ok(TableType::STRUCT_DEFS), 0xB => Ok(TableType::STRUCT_DEF_INST), 0xC => Ok(TableType::FUNCTION_DEFS), - 0xD => Ok(TableType::FIELD_HANDLE), + 0xD => Ok(TableType::FIELD_HANDLES), 0xE => Ok(TableType::FIELD_INST), 0xF => Ok(TableType::FRIEND_DECLS), 0x10 => Ok(TableType::METADATA), + 0x11 => Ok(TableType::VARIANT_FIELD_HANDLES), + 0x12 => Ok(TableType::VARIANT_FIELD_INST), + 0x13 => Ok(TableType::STRUCT_VARIANT_HANDLES), + 0x14 => Ok(TableType::STRUCT_VARIANT_INST), _ => Err(PartialVMError::new(StatusCode::UNKNOWN_TABLE_TYPE)), } } @@ -1825,6 +1914,7 @@ impl SerializedNativeStructFlag { match value { 0x1 => Ok(SerializedNativeStructFlag::NATIVE), 0x2 => Ok(SerializedNativeStructFlag::DECLARED), + 0x3 => Ok(SerializedNativeStructFlag::DECLARED_VARIANTS), _ => Err(PartialVMError::new(StatusCode::UNKNOWN_NATIVE_STRUCT_FLAG)), } } @@ -1910,6 +2000,17 @@ impl Opcodes { 0x4B => Ok(Opcodes::CAST_U16), 0x4C => Ok(Opcodes::CAST_U32), 0x4D => Ok(Opcodes::CAST_U256), + // Since bytecode version 7 + 0x4E => Ok(Opcodes::IMM_BORROW_VARIANT_FIELD), + 0x4F => Ok(Opcodes::MUT_BORROW_VARIANT_FIELD), + 0x50 => Ok(Opcodes::IMM_BORROW_VARIANT_FIELD_GENERIC), + 0x51 => Ok(Opcodes::MUT_BORROW_VARIANT_FIELD_GENERIC), + 0x52 => Ok(Opcodes::PACK_VARIANT), + 0x53 => Ok(Opcodes::PACK_VARIANT_GENERIC), + 0x54 => Ok(Opcodes::UNPACK_VARIANT), + 0x55 => Ok(Opcodes::UNPACK_VARIANT_GENERIC), + 0x56 => Ok(Opcodes::TEST_VARIANT), + 0x57 => Ok(Opcodes::TEST_VARIANT_GENERIC), _ => Err(PartialVMError::new(StatusCode::UNKNOWN_OPCODE)), } } @@ -1927,7 +2028,7 @@ impl SerializedBool { } impl SerializedOption { - /// Returns a boolean to indice NONE or SOME (NONE = false) + /// Returns a boolean to indicate NONE or SOME (NONE = false) fn from_u8(value: u8) -> BinaryLoaderResult { match value { 0x1 => Ok(false), 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 62a0831ecc022..6feb954f8191e 100644 --- a/third_party/move/move-binary-format/src/file_format.rs +++ b/third_party/move/move-binary-format/src/file_format.rs @@ -163,12 +163,36 @@ define_index! { doc: "Index into the `FunctionDefinition` table.", } +// Since bytecode version 7 +define_index! { + name: StructVariantHandleIndex, + kind: StructVariantHandle, + doc: "Index into the `StructVariantHandle` table.", +} +define_index! { + name: StructVariantInstantiationIndex, + kind: StructVariantInstantiation, + doc: "Index into the `StructVariantInstantiation` table.", +} +define_index! { + name: VariantFieldHandleIndex, + kind: VariantFieldHandle, + doc: "Index into the `VariantFieldHandle` table.", +} +define_index! { + name: VariantFieldInstantiationIndex, + kind: VariantFieldInstantiation, + doc: "Index into the `VariantFieldInstantiation` table.", +} + /// Index of a local variable in a function. /// /// Bytecodes that operate on locals carry indexes to the locals of a function. pub type LocalIndex = u8; /// Max number of fields in a `StructDefinition`. pub type MemberCount = u16; +/// Max number of variants in a `StructDefinition`, as well as index for variants. +pub type VariantIndex = u16; /// Index into the code stream for a jump. The offset is relative to the beginning of /// the instruction stream. pub type CodeOffset = u16; @@ -319,6 +343,31 @@ pub struct FieldHandle { pub field: MemberCount, } +/// A variant field access info +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[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))] +pub struct VariantFieldHandle { + /// The structure which defines the variant. + pub struct_index: StructDefinitionIndex, + /// The sequence of variants which share the field at the given + /// field offset. + pub variants: Vec, + /// The field offset. + pub field: MemberCount, +} + +/// A struct variant access info +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[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))] +pub struct StructVariantHandle { + pub struct_index: StructDefinitionIndex, + pub variant: VariantIndex, +} + // DEFINITIONS: // Definitions are the module code. So the set of types and functions in the module. @@ -330,6 +379,62 @@ pub struct FieldHandle { pub enum StructFieldInformation { Native, Declared(Vec), + DeclaredVariants(Vec), +} + +impl StructFieldInformation { + /// Returns the fields described by this field information. If no variant is + /// provided, this returns all fields of a struct. Otherwise, the fields of the + /// variant are returned. + pub fn fields(&self, variant: Option) -> Vec<&FieldDefinition> { + use StructFieldInformation::*; + match self { + Native => vec![], + Declared(fields) => fields.iter().collect(), + DeclaredVariants(variants) => { + if let Some(variant) = variant.filter(|v| (*v as usize) < variants.len()) { + variants[variant as usize].fields.iter().collect() + } else { + vec![] + } + }, + } + } + + /// Returns the number of fields. This is an optimized version of + /// `self.fields(variant).len()` + pub fn field_count(&self, variant: Option) -> usize { + use StructFieldInformation::*; + match self { + Native => 0, + Declared(fields) => fields.len(), + DeclaredVariants(variants) => { + if let Some(variant) = variant.filter(|v| (*v as usize) < variants.len()) { + variants[variant as usize].fields.len() + } else { + 0 + } + }, + } + } + + /// Returns the variant definitions. For non-variant types, an empty + /// slice is returned. + pub fn variants(&self) -> &[VariantDefinition] { + use StructFieldInformation::*; + match self { + Native | Declared(_) => &[], + DeclaredVariants(variants) => variants, + } + } + + /// Returns the number of variants (zero for struct or native) + pub fn variant_count(&self) -> usize { + match self { + StructFieldInformation::Native | StructFieldInformation::Declared(_) => 0, + StructFieldInformation::DeclaredVariants(variants) => variants.len(), + } + } } // @@ -350,6 +455,16 @@ pub struct StructDefInstantiation { pub type_parameters: SignatureIndex, } +/// A complete or partial instantiation of a generic struct variant +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[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))] +pub struct StructVariantInstantiation { + pub handle: StructVariantHandleIndex, + pub type_parameters: SignatureIndex, +} + /// A complete or partial instantiation of a function #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(any(test, feature = "fuzzing"), derive(proptest_derive::Arbitrary))] @@ -375,6 +490,16 @@ pub struct FieldInstantiation { pub type_parameters: SignatureIndex, } +/// A complete or partial instantiation of a variant field. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[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))] +pub struct VariantFieldInstantiation { + pub handle: VariantFieldHandleIndex, + pub type_parameters: SignatureIndex, +} + /// A `StructDefinition` is a type definition. It either indicates it is native or defines all the /// user-specified fields declared on the type. #[derive(Clone, Debug, Eq, PartialEq)] @@ -391,23 +516,6 @@ pub struct StructDefinition { pub field_information: StructFieldInformation, } -impl StructDefinition { - pub fn declared_field_count(&self) -> PartialVMResult { - match &self.field_information { - StructFieldInformation::Native => Err(PartialVMError::new(StatusCode::LINKER_ERROR) - .with_message("Looking for field in native structure. Native structures have no accessible fields.".to_string())), - StructFieldInformation::Declared(fields) => Ok(fields.len() as u16), - } - } - - pub fn field(&self, offset: usize) -> Option<&FieldDefinition> { - match &self.field_information { - StructFieldInformation::Native => None, - StructFieldInformation::Declared(fields) => fields.get(offset), - } - } -} - /// A `FieldDefinition` is the definition of a field: its name and the field type. #[derive(Clone, Debug, Eq, PartialEq)] #[cfg_attr(any(test, feature = "fuzzing"), derive(proptest_derive::Arbitrary))] @@ -420,6 +528,15 @@ pub struct FieldDefinition { pub signature: TypeSignature, } +#[derive(Clone, Debug, Eq, PartialEq)] +#[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))] +pub struct VariantDefinition { + pub name: IdentifierIndex, + pub fields: Vec, +} + /// `Visibility` restricts the accessibility of the associated entity. /// - For function visibility, it restricts who may call into the associated function. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] @@ -1613,6 +1730,7 @@ pub enum Bytecode { "#] #[gas_type_creation_tier_1 = "local_tys"] Call(FunctionHandleIndex), + #[group = "control_flow"] #[static_operands = "[func_inst_idx]"] #[description = "Generic version of `Call`."] @@ -1651,6 +1769,37 @@ pub enum Bytecode { #[gas_type_creation_tier_1 = "field_tys"] PackGeneric(StructDefInstantiationIndex), + #[group = "variant"] + #[static_operands = "[struct_variant_handle_idx]"] + #[description = r#" + Create an instance of the struct variant specified by the handle and push it on the stack. + The values of the fields of the variant, in the order they are determined by the + declaration, must be pushed on the stack. All fields must be provided. + "#] + #[semantics = r#" + stack >> field_n-1 + ... + stack >> field_0 + stack << struct/variant { field_0, ..., field_n-1 } + "#] + #[runtime_check_epilogue = r#" + ty_stack >> tys + assert tys == field_tys + check field abilities + ty_stack << struct_ty + "#] + PackVariant(StructVariantHandleIndex), + + #[group = "variant"] + #[static_operands = "[struct_variant_inst_idx]"] + #[description = "Generic version of `PackVariant`."] + #[semantics = "See `PackVariant`."] + #[runtime_check_epilogue = "See `PackVariant`."] + #[gas_type_creation_tier_0 = "struct_ty"] + #[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."] @@ -1675,6 +1824,63 @@ pub enum Bytecode { #[gas_type_creation_tier_1 = "field_tys"] UnpackGeneric(StructDefInstantiationIndex), + #[group = "variant"] + #[static_operands = "[struct_variant_handle_idx]"] + #[description = r#" + If the value on the stack is of the specified variant, destroy it and push the + values bound to each field onto the stack. + + Aborts if the value is not of the specified variant. + "#] + #[semantics = r#" + if struct_ref is variant_field.variant + stack >> struct/variant { field_0, .., field_n-1 } + stack << field_0 + ... + stack << field_n-1 + else + error + "#] + #[runtime_check_epilogue = r#" + ty_stack >> ty + assert ty == struct_ty + ty_stack << field_tys + "#] + #[gas_type_creation_tier_0 = "struct_ty"] + #[gas_type_creation_tier_1 = "field_tys"] + UnpackVariant(StructVariantHandleIndex), + + #[group = "struct"] + #[static_operands = "[struct_variant_inst_idx]"] + #[description = "Generic version of `UnpackVariant`."] + #[semantics = "See `UnpackVariant`."] + #[runtime_check_epilogue = "See `UnpackVariant`."] + #[gas_type_creation_tier_0 = "struct_ty"] + #[gas_type_creation_tier_1 = "field_tys"] + UnpackVariantGeneric(StructVariantInstantiationIndex), + + #[group = "variant"] + #[static_operands = "[struct_variant_handle_idx]"] + #[description = r#" + Tests whether the reference value on the stack is of the specified variant. + "#] + #[semantics = r#" + stack >> struct_ref + stack << struct_if is variant + "#] + #[runtime_check_epilogue = r#" + ty_stack >> ty + assert ty == &struct_ty + ty_stack << bool + "#] + TestVariant(StructVariantHandleIndex), + + #[group = "variant"] + #[description = "Generic version of `TestVariant`."] + #[semantics = "See `TestVariant`."] + #[runtime_check_epilogue = "See `TestVariant`."] + TestVariantGeneric(StructVariantInstantiationIndex), + #[group = "reference"] #[description = r#" Consume the reference at the top of the stack, read the value referenced, and push the value onto the stack. @@ -1762,6 +1968,29 @@ pub enum Bytecode { "#] MutBorrowField(FieldHandleIndex), + #[group = "variant"] + #[static_operands = "[variant_field_handle_idx]"] + #[description = r#" + Consume the reference to a struct at the top of the stack, + and provided that the struct is of the given variant, load a mutable reference to + the field of the variant. + + Aborts execution if the operand is not of the given variant. + "#] + #[semantics = r#" + stack >> struct_ref + if struct_ref is variant + stack << &mut (*struct_ref).field(variant_field.field_index) + else + error + "#] + #[runtime_check_epilogue = r#" + ty_stack >> ty + assert ty == &mut struct_ty + ty_stack << &mut field_ty + "#] + MutBorrowVariantField(VariantFieldHandleIndex), + #[group = "struct"] #[static_operands = "[field_inst_idx]"] #[description = r#" @@ -1781,6 +2010,29 @@ pub enum Bytecode { #[gas_type_creation_tier_1 = "field_ty"] MutBorrowFieldGeneric(FieldInstantiationIndex), + #[group = "variant"] + #[static_operands = "[variant_field_inst_idx]"] + #[description = r#" + Consume the reference to a generic struct at the top of the stack, + and provided that the struct is of the given variant, load a mutable reference to + the field of the variant. + + Aborts execution if the operand is not of the given variant. + "#] + #[semantics = r#" + stack >> struct_ref + if struct_ref is variant_field + stack << &mut (*struct_ref).field(field_index) + else + error + "#] + #[runtime_check_epilogue = r#" + ty_stack >> ty + assert ty == &mut struct_ty + ty_stack << &mut field_ty + "#] + MutBorrowVariantFieldGeneric(VariantFieldInstantiationIndex), + #[group = "struct"] #[static_operands = "[field_handle_idx]"] #[description = r#" @@ -1798,11 +2050,35 @@ pub enum Bytecode { "#] ImmBorrowField(FieldHandleIndex), + #[group = "variant"] + #[static_operands = "[variant_field_inst_idx]"] + #[description = r#" + Consume the reference to a struct at the top of the stack, + and provided that the struct is of the given variant, load an + immutable reference to the field of the variant. + + Aborts execution if the operand is not of the given variant. + "#] + #[semantics = r#" + stack >> struct_ref + if struct_ref is variant + stack << &(*struct_ref).field(field_index) + else + error + "#] + #[runtime_check_epilogue = r#" + ty_stack >> ty + assert ty == &mut struct_ty + ty_stack << &mut field_ty + "#] + ImmBorrowVariantField(VariantFieldHandleIndex), + #[group = "struct"] #[static_operands = "[field_inst_idx]"] #[description = r#" Consume the reference to a generic struct at the top of the stack, - and load an immutable reference to the field identified by the field handle index. + and load an immutable reference to the field identified by the + field handle index. "#] #[semantics = r#" stack >> struct_ref @@ -1817,6 +2093,29 @@ pub enum Bytecode { #[gas_type_creation_tier_1 = "field_ty"] ImmBorrowFieldGeneric(FieldInstantiationIndex), + #[group = "variant"] + #[static_operands = "[variant_field_inst_idx]"] + #[description = r#" + Consume the reference to a generic struct at the top of the stack, + and provided that the struct is of the given variant, load an immutable + reference to the field of the variant. + + Aborts execution if the operand is not of the given variant. + "#] + #[semantics = r#" + stack >> struct_ref + if struct_ref is variant_field.variant + stack << &(*struct_ref).field(variant_field.field_index) + else + error + "#] + #[runtime_check_epilogue = r#" + ty_stack >> ty + assert ty == &mut struct_ty + ty_stack << &mut field_ty + "#] + ImmBorrowVariantFieldGeneric(VariantFieldInstantiationIndex), + #[group = "global"] #[static_operands = "[struct_def_idx]"] #[description = r#" @@ -2640,8 +2939,14 @@ impl ::std::fmt::Debug for Bytecode { Bytecode::CallGeneric(a) => write!(f, "CallGeneric({})", a), Bytecode::Pack(a) => write!(f, "Pack({})", a), Bytecode::PackGeneric(a) => write!(f, "PackGeneric({})", a), + Bytecode::PackVariant(a) => write!(f, "PackVariant({})", a), + Bytecode::TestVariant(a) => write!(f, "TestVariant({})", a), + Bytecode::PackVariantGeneric(a) => write!(f, "PackVariantGeneric({})", a), + Bytecode::TestVariantGeneric(a) => write!(f, "TestVariantGeneric({})", a), Bytecode::Unpack(a) => write!(f, "Unpack({})", a), Bytecode::UnpackGeneric(a) => write!(f, "UnpackGeneric({})", a), + Bytecode::UnpackVariant(a) => write!(f, "UnpackVariant({})", a), + Bytecode::UnpackVariantGeneric(a) => write!(f, "UnpackVariantGeneric({})", a), Bytecode::ReadRef => write!(f, "ReadRef"), Bytecode::WriteRef => write!(f, "WriteRef"), Bytecode::FreezeRef => write!(f, "FreezeRef"), @@ -2649,8 +2954,16 @@ impl ::std::fmt::Debug for Bytecode { Bytecode::ImmBorrowLoc(a) => write!(f, "ImmBorrowLoc({})", a), Bytecode::MutBorrowField(a) => write!(f, "MutBorrowField({:?})", a), Bytecode::MutBorrowFieldGeneric(a) => write!(f, "MutBorrowFieldGeneric({:?})", a), + Bytecode::MutBorrowVariantField(a) => write!(f, "MutBorrowVariantField({:?})", a), + Bytecode::MutBorrowVariantFieldGeneric(a) => { + write!(f, "MutBorrowVariantFieldGeneric({:?})", a) + }, Bytecode::ImmBorrowField(a) => write!(f, "ImmBorrowField({:?})", a), Bytecode::ImmBorrowFieldGeneric(a) => write!(f, "ImmBorrowFieldGeneric({:?})", a), + Bytecode::ImmBorrowVariantField(a) => write!(f, "ImmBorrowVariantField({:?})", a), + Bytecode::ImmBorrowVariantFieldGeneric(a) => { + write!(f, "ImmBorrowVariantFieldGeneric({:?})", a) + }, Bytecode::MutBorrowGlobal(a) => write!(f, "MutBorrowGlobal({:?})", a), Bytecode::MutBorrowGlobalGeneric(a) => write!(f, "MutBorrowGlobalGeneric({:?})", a), Bytecode::ImmBorrowGlobal(a) => write!(f, "ImmBorrowGlobal({:?})", a), @@ -2853,6 +3166,12 @@ pub struct CompiledModule { pub struct_defs: Vec, /// Function defined in this module. pub function_defs: Vec, + + /// Since bytecode version 7: variant related handle tables + pub struct_variant_handles: Vec, + pub struct_variant_instantiations: Vec, + pub variant_field_handles: Vec, + pub variant_field_instantiations: Vec, } // Need a custom implementation of Arbitrary because as of proptest-derive 0.1.1, the derivation @@ -2963,6 +3282,10 @@ impl Arbitrary for CompiledModule { metadata: vec![], struct_defs, function_defs, + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], } }, ) @@ -2978,6 +3301,7 @@ impl CompiledModule { IndexKind::LocalPool | IndexKind::CodeDefinition | IndexKind::FieldDefinition + | IndexKind::VariantDefinition | IndexKind::TypeParameter | IndexKind::MemberCount )); @@ -2996,10 +3320,17 @@ impl CompiledModule { IndexKind::Identifier => self.identifiers.len(), IndexKind::AddressIdentifier => self.address_identifiers.len(), IndexKind::ConstantPool => self.constant_pool.len(), + // Since bytecode version 7 + IndexKind::VariantFieldHandle => self.variant_field_handles.len(), + IndexKind::VariantFieldInstantiation => self.variant_field_instantiations.len(), + IndexKind::StructVariantHandle => self.struct_variant_handles.len(), + IndexKind::StructVariantInstantiation => self.struct_variant_instantiations.len(), + // XXX these two don't seem to belong here other @ IndexKind::LocalPool | other @ IndexKind::CodeDefinition | other @ IndexKind::FieldDefinition + | other @ IndexKind::VariantDefinition | other @ IndexKind::TypeParameter | other @ IndexKind::MemberCount => unreachable!("invalid kind for count: {:?}", other), } @@ -3050,6 +3381,10 @@ pub fn empty_module() -> CompiledModule { function_instantiations: vec![], field_instantiations: vec![], signatures: vec![Signature(vec![])], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], } } 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 b5446c81260e4..90b24f95c4c83 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 @@ -50,8 +50,12 @@ pub const FUNCTION_HANDLE_INDEX_MAX: u64 = TABLE_INDEX_MAX; pub const FUNCTION_INST_INDEX_MAX: u64 = TABLE_INDEX_MAX; pub const FIELD_HANDLE_INDEX_MAX: u64 = TABLE_INDEX_MAX; pub const FIELD_INST_INDEX_MAX: u64 = TABLE_INDEX_MAX; +pub const VARIANT_FIELD_HANDLE_INDEX_MAX: u64 = TABLE_INDEX_MAX; +pub const VARIANT_FIELD_INST_INDEX_MAX: u64 = TABLE_INDEX_MAX; pub const STRUCT_DEF_INST_INDEX_MAX: u64 = TABLE_INDEX_MAX; pub const CONSTANT_INDEX_MAX: u64 = TABLE_INDEX_MAX; +pub const STRUCT_VARIANT_HANDLE_INDEX_MAX: u64 = TABLE_INDEX_MAX; +pub const STRUCT_VARIANT_INST_INDEX_MAX: u64 = TABLE_INDEX_MAX; pub const BYTECODE_COUNT_MAX: u64 = 65535; pub const BYTECODE_INDEX_MAX: u64 = 65535; @@ -72,6 +76,8 @@ pub const ACQUIRES_COUNT_MAX: u64 = 255; pub const FIELD_COUNT_MAX: u64 = 255; pub const FIELD_OFFSET_MAX: u64 = 255; +pub const VARIANT_COUNT_MAX: u64 = 127; +pub const VARIANT_OFFSET_MAX: u64 = 127; pub const TYPE_PARAMETER_COUNT_MAX: u64 = 255; pub const TYPE_PARAMETER_INDEX_MAX: u64 = 65536; @@ -84,6 +90,8 @@ pub const SIGNATURE_TOKEN_DEPTH_MAX: usize = 256; /// /// The binary contains a subset of those tables. A table specification is a tuple (table type, /// start offset, byte count) for a given table. +/// +/// Notice there must not be more than `max(u8) - 1` tables. #[rustfmt::skip] #[allow(non_camel_case_types)] #[repr(u8)] @@ -100,10 +108,14 @@ pub enum TableType { STRUCT_DEFS = 0xA, STRUCT_DEF_INST = 0xB, FUNCTION_DEFS = 0xC, - FIELD_HANDLE = 0xD, + FIELD_HANDLES = 0xD, FIELD_INST = 0xE, FRIEND_DECLS = 0xF, METADATA = 0x10, + VARIANT_FIELD_HANDLES = 0x11, + VARIANT_FIELD_INST = 0x12, + STRUCT_VARIANT_HANDLES = 0x13, + STRUCT_VARIANT_INST = 0x14, } /// Constants for signature blob values. @@ -189,6 +201,7 @@ pub enum SerializedAddressSpecifier { pub enum SerializedNativeStructFlag { NATIVE = 0x1, DECLARED = 0x2, + DECLARED_VARIANTS = 0x3, } /// List of opcodes constants. @@ -274,6 +287,17 @@ pub enum Opcodes { CAST_U16 = 0x4B, CAST_U32 = 0x4C, CAST_U256 = 0x4D, + // Since bytecode version 7 + IMM_BORROW_VARIANT_FIELD = 0x4E, + MUT_BORROW_VARIANT_FIELD = 0x4F, + IMM_BORROW_VARIANT_FIELD_GENERIC = 0x50, + MUT_BORROW_VARIANT_FIELD_GENERIC = 0x51, + PACK_VARIANT = 0x52, + PACK_VARIANT_GENERIC = 0x53, + UNPACK_VARIANT = 0x54, + UNPACK_VARIANT_GENERIC = 0x55, + TEST_VARIANT = 0x56, + TEST_VARIANT_GENERIC = 0x57, } /// Upper limit on the binary size @@ -576,10 +600,6 @@ pub(crate) mod versioned_data { cursor: Cursor::new(&self.binary[start..end]), } } - - pub fn slice(&self, start: usize, end: usize) -> &'a [u8] { - &self.binary[start..end] - } } impl<'a> VersionedCursor<'a> { @@ -758,6 +778,17 @@ pub fn instruction_key(instruction: &Bytecode) -> u8 { CastU16 => Opcodes::CAST_U16, CastU32 => Opcodes::CAST_U32, CastU256 => Opcodes::CAST_U256, + // Since bytecode version 7 + ImmBorrowVariantField(_) => Opcodes::IMM_BORROW_VARIANT_FIELD, + ImmBorrowVariantFieldGeneric(_) => Opcodes::IMM_BORROW_VARIANT_FIELD_GENERIC, + MutBorrowVariantField(_) => Opcodes::MUT_BORROW_VARIANT_FIELD, + MutBorrowVariantFieldGeneric(_) => Opcodes::MUT_BORROW_VARIANT_FIELD_GENERIC, + PackVariant(_) => Opcodes::PACK_VARIANT, + PackVariantGeneric(_) => Opcodes::PACK_VARIANT_GENERIC, + UnpackVariant(_) => Opcodes::UNPACK_VARIANT, + UnpackVariantGeneric(_) => Opcodes::UNPACK_VARIANT_GENERIC, + TestVariant(_) => Opcodes::TEST_VARIANT, + TestVariantGeneric(_) => Opcodes::TEST_VARIANT_GENERIC, }; opcode as u8 } diff --git a/third_party/move/move-binary-format/src/lib.rs b/third_party/move/move-binary-format/src/lib.rs index 3bbd2285707e6..fb73dd72d974d 100644 --- a/third_party/move/move-binary-format/src/lib.rs +++ b/third_party/move/move-binary-format/src/lib.rs @@ -53,6 +53,12 @@ pub enum IndexKind { CodeDefinition, TypeParameter, MemberCount, + // Since bytecode version 7 + VariantDefinition, + VariantFieldHandle, + VariantFieldInstantiation, + StructVariantHandle, + StructVariantInstantiation, } impl IndexKind { @@ -79,6 +85,12 @@ impl IndexKind { CodeDefinition, TypeParameter, MemberCount, + // Since bytecode version 7 + VariantDefinition, + VariantFieldHandle, + VariantFieldInstantiation, + StructVariantHandle, + StructVariantInstantiation, ] } } @@ -99,6 +111,7 @@ impl fmt::Display for IndexKind { StructDefinition => "struct definition", FunctionDefinition => "function definition", FieldDefinition => "field definition", + VariantDefinition => "variant definition", Signature => "signature", Identifier => "identifier", AddressIdentifier => "address identifier", @@ -107,6 +120,10 @@ impl fmt::Display for IndexKind { CodeDefinition => "code definition pool", TypeParameter => "type parameter", MemberCount => "field offset", + VariantFieldHandle => "variant field handle", + VariantFieldInstantiation => "variant field instantiation", + StructVariantHandle => "struct variant handle", + StructVariantInstantiation => "struct variant instantiation", }; f.write_str(desc) diff --git a/third_party/move/move-binary-format/src/normalized.rs b/third_party/move/move-binary-format/src/normalized.rs index 651bfa9a05900..332f78cfe0412 100644 --- a/third_party/move/move-binary-format/src/normalized.rs +++ b/third_party/move/move-binary-format/src/normalized.rs @@ -312,6 +312,11 @@ impl Struct { StructFieldInformation::Declared(fields) => { fields.iter().map(|f| Field::new(m, f)).collect() }, + StructFieldInformation::DeclaredVariants(..) => { + // TODO(#13806): consider implementing (probably not as long we do not + // know whether normalized.rs will be retired) + panic!("variants not implemented") + }, }; let name = m.identifier_at(handle.name).to_owned(); let s = Struct { diff --git a/third_party/move/move-binary-format/src/proptest_types.rs b/third_party/move/move-binary-format/src/proptest_types.rs index 011d518efe4de..9625fc5457fb4 100644 --- a/third_party/move/move-binary-format/src/proptest_types.rs +++ b/third_party/move/move-binary-format/src/proptest_types.rs @@ -32,7 +32,7 @@ use constants::ConstantPoolGen; use functions::{ FnDefnMaterializeState, FnHandleMaterializeState, FunctionDefinitionGen, FunctionHandleGen, }; -use std::collections::{BTreeSet, HashMap}; +use std::collections::BTreeSet; /// Represents how large [`CompiledModule`] tables can be. pub type TableSize = u16; @@ -300,14 +300,10 @@ impl CompiledModuleStrategyGen { identifiers_len, struct_handles, ); - let mut struct_def_to_field_count: HashMap = HashMap::new(); let mut struct_defs: Vec = vec![]; for struct_def_gen in struct_def_gens { - if let (Some(struct_def), offset) = struct_def_gen.materialize(&mut state) { + if let Some(struct_def) = struct_def_gen.materialize(&mut state) { struct_defs.push(struct_def); - if offset > 0 { - struct_def_to_field_count.insert(struct_defs.len() - 1, offset); - } } } let StDefnMaterializeState { struct_handles, .. } = state; @@ -352,7 +348,6 @@ impl CompiledModuleStrategyGen { &struct_defs, signatures, function_handles, - struct_def_to_field_count, ); let mut function_defs: Vec = vec![]; for function_def_gen in function_def_gens { @@ -367,6 +362,10 @@ impl CompiledModuleStrategyGen { struct_def_instantiations, function_instantiations, field_instantiations, + variant_field_handles, + variant_field_instantiations, + struct_variant_handles, + struct_variant_instantiations, ) = state.return_tables(); // Build a compiled module @@ -386,6 +385,11 @@ impl CompiledModuleStrategyGen { struct_defs, function_defs, + variant_field_handles, + variant_field_instantiations, + struct_variant_handles, + struct_variant_instantiations, + signatures, identifiers, 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 e2857b3c80986..806ca57aba9e9 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 @@ -9,7 +9,10 @@ use crate::{ FunctionDefinition, FunctionHandle, FunctionHandleIndex, FunctionInstantiation, FunctionInstantiationIndex, IdentifierIndex, LocalIndex, ModuleHandleIndex, Signature, SignatureIndex, SignatureToken, StructDefInstantiation, StructDefInstantiationIndex, - StructDefinition, StructDefinitionIndex, StructHandle, TableIndex, Visibility, + StructDefinition, StructDefinitionIndex, StructFieldInformation, StructHandle, + StructVariantHandle, StructVariantHandleIndex, StructVariantInstantiation, + StructVariantInstantiationIndex, TableIndex, VariantFieldHandle, VariantFieldHandleIndex, + VariantFieldInstantiation, VariantFieldInstantiationIndex, VariantIndex, Visibility, }, internals::ModuleIndex, proptest_types::{ @@ -30,60 +33,54 @@ use std::{ hash::Hash, }; -#[derive(Debug, Default)] -struct SignatureState { - signatures: Vec, - signature_map: HashMap, +#[derive(Debug)] +struct HandleState { + handles: Vec, + map: HashMap, } -impl SignatureState { - fn new(signatures: Vec) -> Self { - let mut state = Self::default(); - for sig in signatures { - state.add_signature(sig); +impl Default for HandleState { + fn default() -> Self { + Self { + handles: vec![], + map: HashMap::new(), } - state - } - - fn signatures(self) -> Vec { - self.signatures } +} - fn add_signature(&mut self, sig: Signature) -> SignatureIndex { - debug_assert!(self.signatures.len() < TableSize::max_value() as usize); - if let Some(idx) = self.signature_map.get(&sig) { - return *idx; +impl HandleState +where + HandleT: Hash + PartialEq + Eq + Clone, + IndexT: Clone, +{ + fn add(&mut self, handle: HandleT, make_index: impl Fn(TableIndex) -> IndexT) -> IndexT { + debug_assert!(self.handles.len() < TableSize::max_value() as usize); + if let Some(idx) = self.map.get(&handle) { + return idx.clone(); } - let idx = SignatureIndex(self.signatures.len() as u16); - self.signatures.push(sig.clone()); - self.signature_map.insert(sig, idx); + let idx = make_index(self.handles.len() as TableIndex); + self.handles.push(handle.clone()); + self.map.insert(handle, idx.clone()); idx } -} -#[derive(Debug, Default)] -#[allow(unused)] -struct FieldHandleState { - field_handles: Vec, - field_map: HashMap, + fn handles(self) -> Vec { + self.handles + } } -impl FieldHandleState { - #[allow(unused)] - pub fn field_handles(self) -> Vec { - self.field_handles - } +type SignatureState = HandleState; +type FieldHandleState = HandleState; +type VariantFieldHandleState = HandleState; +type StructVariantHandleState = HandleState; - #[allow(unused)] - fn add_field_handle(&mut self, fh: FieldHandle) -> FieldHandleIndex { - debug_assert!(self.field_handles.len() < TableSize::max_value() as usize); - if let Some(idx) = self.field_map.get(&fh) { - return *idx; +impl SignatureState { + fn from(sigs: Vec) -> Self { + let mut state = Self::default(); + for sig in sigs { + state.add(sig, SignatureIndex); } - let idx = FieldHandleIndex(self.field_handles.len() as u16); - self.field_handles.push(fh.clone()); - self.field_map.insert(fh, idx); - idx + state } } @@ -150,17 +147,17 @@ impl<'a> FnHandleMaterializeState<'a> { module_handles_len, identifiers_len, struct_handles, - signatures: SignatureState::new(signatures), + signatures: SignatureState::from(signatures), function_handles: HashSet::new(), } } pub fn signatures(self) -> Vec { - self.signatures.signatures() + self.signatures.handles() } fn add_signature(&mut self, sig: Signature) -> SignatureIndex { - self.signatures.add_signature(sig) + self.signatures.add(sig, SignatureIndex) } } @@ -245,12 +242,15 @@ pub struct FnDefnMaterializeState<'a> { struct_defs: &'a [StructDefinition], signatures: SignatureState, function_handles: Vec, - struct_def_to_field_count: HashMap, def_function_handles: HashSet<(ModuleHandleIndex, IdentifierIndex)>, field_handles: FieldHandleState, + variant_field_handles: VariantFieldHandleState, type_instantiations: InstantiationState, function_instantiations: InstantiationState, field_instantiations: InstantiationState, + variant_field_instantiations: InstantiationState, + struct_variant_handles: StructVariantHandleState, + struct_variant_instantiations: InstantiationState, } impl<'a> FnDefnMaterializeState<'a> { @@ -262,7 +262,6 @@ impl<'a> FnDefnMaterializeState<'a> { struct_defs: &'a [StructDefinition], signatures: Vec, function_handles: Vec, - struct_def_to_field_count: HashMap, ) -> Self { Self { self_module_handle_idx, @@ -270,14 +269,17 @@ impl<'a> FnDefnMaterializeState<'a> { constant_pool_len, struct_handles, struct_defs, - signatures: SignatureState::new(signatures), + signatures: SignatureState::from(signatures), function_handles, - struct_def_to_field_count, def_function_handles: HashSet::new(), field_handles: FieldHandleState::default(), + variant_field_handles: VariantFieldHandleState::default(), type_instantiations: InstantiationState::new(), function_instantiations: InstantiationState::new(), field_instantiations: InstantiationState::new(), + variant_field_instantiations: InstantiationState::new(), + struct_variant_handles: StructVariantHandleState::default(), + struct_variant_instantiations: InstantiationState::new(), } } @@ -290,19 +292,27 @@ impl<'a> FnDefnMaterializeState<'a> { Vec, Vec, Vec, + Vec, + Vec, + Vec, + Vec, ) { ( - self.signatures.signatures(), + self.signatures.handles(), self.function_handles, - self.field_handles.field_handles(), + self.field_handles.handles(), self.type_instantiations.instantiations(), self.function_instantiations.instantiations(), self.field_instantiations.instantiations(), + self.variant_field_handles.handles(), + self.variant_field_instantiations.instantiations(), + self.struct_variant_handles.handles(), + self.struct_variant_instantiations.instantiations(), ) } fn add_signature(&mut self, sig: Signature) -> SignatureIndex { - self.signatures.add_signature(sig) + self.signatures.add(sig, SignatureIndex) } fn add_function_handle(&mut self, handle: FunctionHandle) -> FunctionHandleIndex { @@ -331,7 +341,7 @@ impl<'a> FnDefnMaterializeState<'a> { abilities: impl IntoIterator, ) -> SignatureIndex { let sig = self.get_signature_from_type_params(abilities); - self.signatures.add_signature(sig) + self.signatures.add(sig, SignatureIndex) } fn get_function_instantiation(&mut self, fh_idx: usize) -> FunctionInstantiationIndex { @@ -345,15 +355,19 @@ impl<'a> FnDefnMaterializeState<'a> { } fn get_type_instantiation(&mut self, sd_idx: usize) -> StructDefInstantiationIndex { - let sd = &self.struct_defs[sd_idx]; - let struct_handle = &self.struct_handles[sd.struct_handle.0 as usize]; - let sig_idx = self.add_signature_from_type_params(struct_handle.type_param_constraints()); + let sig_idx = self.get_struct_instantiation_sig(sd_idx); let si = StructDefInstantiation { def: StructDefinitionIndex(sd_idx as TableIndex), type_parameters: sig_idx, }; StructDefInstantiationIndex(self.type_instantiations.add_instantiation(si)) } + + fn get_struct_instantiation_sig(&mut self, sd_idx: usize) -> SignatureIndex { + let sd = &self.struct_defs[sd_idx]; + let struct_handle = &self.struct_handles[sd.struct_handle.0 as usize]; + self.add_signature_from_type_params(struct_handle.type_param_constraints()) + } } #[derive(Clone, Debug)] @@ -496,11 +510,16 @@ enum BytecodeGen { MutBorrowField((PropIndex, PropIndex)), ImmBorrowField((PropIndex, PropIndex)), + MutBorrowVariantField((PropIndex, PropIndex, PropIndex)), + ImmBorrowVariantField((PropIndex, PropIndex, PropIndex)), Call(PropIndex), Pack(PropIndex), Unpack(PropIndex), + PackVariant((PropIndex, PropIndex)), + UnpackVariant((PropIndex, PropIndex)), + Exists(PropIndex), MutBorrowGlobal(PropIndex), ImmBorrowGlobal(PropIndex), @@ -536,9 +555,15 @@ impl BytecodeGen { any::().prop_map(LdConst), (any::(), any::()).prop_map(ImmBorrowField), (any::(), any::()).prop_map(MutBorrowField), + (any::(), any::(), any::()) + .prop_map(ImmBorrowVariantField), + (any::(), any::(), any::()) + .prop_map(MutBorrowVariantField), any::().prop_map(Call), any::().prop_map(Pack), any::().prop_map(Unpack), + (any::(), any::()).prop_map(PackVariant), + (any::(), any::()).prop_map(UnpackVariant), any::().prop_map(Exists), any::().prop_map(ImmBorrowGlobal), any::().prop_map(MutBorrowGlobal), @@ -580,58 +605,16 @@ impl BytecodeGen { )) }, BytecodeGen::MutBorrowField((def, field)) => { - let sd_idx = def.index(state.struct_defs.len()); - let field_count = state.struct_def_to_field_count.get(&sd_idx)?; - if *field_count == 0 { - return None; - } - let fh_idx = state.field_handles.add_field_handle(FieldHandle { - owner: StructDefinitionIndex(sd_idx as TableIndex), - field: field.index(*field_count) as TableIndex, - }); - - let struct_handle = - &state.struct_handles[state.struct_defs[sd_idx].struct_handle.0 as usize]; - if struct_handle.type_parameters.is_empty() { - Bytecode::MutBorrowField(fh_idx) - } else { - let sig_idx = state - .add_signature_from_type_params(struct_handle.type_param_constraints()); - let fi_idx = state - .field_instantiations - .add_instantiation(FieldInstantiation { - handle: fh_idx, - type_parameters: sig_idx, - }); - Bytecode::MutBorrowFieldGeneric(FieldInstantiationIndex(fi_idx)) - } + Self::materialize_borrow(state, def, false, None, field)? }, BytecodeGen::ImmBorrowField((def, field)) => { - let sd_idx = def.index(state.struct_defs.len()); - let field_count = state.struct_def_to_field_count.get(&sd_idx)?; - if *field_count == 0 { - return None; - } - let fh_idx = state.field_handles.add_field_handle(FieldHandle { - owner: StructDefinitionIndex(sd_idx as TableIndex), - field: field.index(*field_count) as TableIndex, - }); - - let struct_handle = - &state.struct_handles[state.struct_defs[sd_idx].struct_handle.0 as usize]; - if struct_handle.type_parameters.is_empty() { - Bytecode::ImmBorrowField(fh_idx) - } else { - let sig_idx = state - .add_signature_from_type_params(struct_handle.type_param_constraints()); - let fi_idx = state - .field_instantiations - .add_instantiation(FieldInstantiation { - handle: fh_idx, - type_parameters: sig_idx, - }); - Bytecode::ImmBorrowFieldGeneric(FieldInstantiationIndex(fi_idx)) - } + Self::materialize_borrow(state, def, true, None, field)? + }, + BytecodeGen::MutBorrowVariantField((def, variant, field)) => { + Self::materialize_borrow(state, def, false, Some(variant), field)? + }, + BytecodeGen::ImmBorrowVariantField((def, variant, field)) => { + Self::materialize_borrow(state, def, true, Some(variant), field)? }, BytecodeGen::Call(idx) => { let func_handles_len = state.function_handles.len(); @@ -643,34 +626,15 @@ impl BytecodeGen { Bytecode::CallGeneric(state.get_function_instantiation(fh_idx)) } }, - BytecodeGen::Pack(idx) => { - let struct_defs_len = state.struct_defs.len(); - let sd_idx = idx.index(struct_defs_len); - - let sd = &state.struct_defs[sd_idx]; - if state.struct_handles[sd.struct_handle.0 as usize] - .type_parameters - .is_empty() - { - Bytecode::Pack(StructDefinitionIndex(sd_idx as TableIndex)) - } else { - Bytecode::PackGeneric(state.get_type_instantiation(sd_idx)) - } + BytecodeGen::Pack(def) => Self::materialize_pack_unpack(state, true, def, None)?, + BytecodeGen::Unpack(def) => Self::materialize_pack_unpack(state, false, def, None)?, + BytecodeGen::PackVariant((def, variant)) => { + Self::materialize_pack_unpack(state, true, def, Some(variant))? }, - BytecodeGen::Unpack(idx) => { - let struct_defs_len = state.struct_defs.len(); - let sd_idx = idx.index(struct_defs_len); - - let sd = &state.struct_defs[sd_idx]; - if state.struct_handles[sd.struct_handle.0 as usize] - .type_parameters - .is_empty() - { - Bytecode::Unpack(StructDefinitionIndex(sd_idx as TableIndex)) - } else { - Bytecode::UnpackGeneric(state.get_type_instantiation(sd_idx)) - } + BytecodeGen::UnpackVariant((def, variant)) => { + Self::materialize_pack_unpack(state, false, def, Some(variant))? }, + BytecodeGen::Exists(idx) => { let struct_defs_len = state.struct_defs.len(); let sd_idx = idx.index(struct_defs_len); @@ -793,72 +757,72 @@ impl BytecodeGen { if num > u16::MAX as u64 { return None; } - let sigs_len = state.signatures.signatures.len(); + let sigs_len = state.signatures.handles.len(); if sigs_len == 0 { return None; } let sig_idx = idx.index(sigs_len); - let sig = &state.signatures.signatures[sig_idx]; + let sig = &state.signatures.handles[sig_idx]; if !BytecodeGen::is_valid_vector_element_sig(sig) { return None; } Bytecode::VecPack(SignatureIndex(sig_idx as TableIndex), num) }, BytecodeGen::VecLen(idx) => { - let sigs_len = state.signatures.signatures.len(); + let sigs_len = state.signatures.handles.len(); if sigs_len == 0 { return None; } let sig_idx = idx.index(sigs_len); - let sig = &state.signatures.signatures[sig_idx]; + let sig = &state.signatures.handles[sig_idx]; if !BytecodeGen::is_valid_vector_element_sig(sig) { return None; } Bytecode::VecLen(SignatureIndex(sig_idx as TableIndex)) }, BytecodeGen::VecImmBorrow(idx) => { - let sigs_len = state.signatures.signatures.len(); + let sigs_len = state.signatures.handles.len(); if sigs_len == 0 { return None; } let sig_idx = idx.index(sigs_len); - let sig = &state.signatures.signatures[sig_idx]; + let sig = &state.signatures.handles[sig_idx]; if !BytecodeGen::is_valid_vector_element_sig(sig) { return None; } Bytecode::VecImmBorrow(SignatureIndex(sig_idx as TableIndex)) }, BytecodeGen::VecMutBorrow(idx) => { - let sigs_len = state.signatures.signatures.len(); + let sigs_len = state.signatures.handles.len(); if sigs_len == 0 { return None; } let sig_idx = idx.index(sigs_len); - let sig = &state.signatures.signatures[sig_idx]; + let sig = &state.signatures.handles[sig_idx]; if !BytecodeGen::is_valid_vector_element_sig(sig) { return None; } Bytecode::VecMutBorrow(SignatureIndex(sig_idx as TableIndex)) }, BytecodeGen::VecPushBack(idx) => { - let sigs_len = state.signatures.signatures.len(); + let sigs_len = state.signatures.handles.len(); if sigs_len == 0 { return None; } let sig_idx = idx.index(sigs_len); - let sig = &state.signatures.signatures[sig_idx]; + let sig = &state.signatures.handles[sig_idx]; if !BytecodeGen::is_valid_vector_element_sig(sig) { return None; } Bytecode::VecPushBack(SignatureIndex(sig_idx as TableIndex)) }, BytecodeGen::VecPopBack(idx) => { - let sigs_len = state.signatures.signatures.len(); + let sigs_len = state.signatures.handles.len(); if sigs_len == 0 { return None; } let sig_idx = idx.index(sigs_len); - let sig = &state.signatures.signatures[sig_idx]; + let sig = &state.signatures.handles[sig_idx]; if !BytecodeGen::is_valid_vector_element_sig(sig) { return None; } @@ -868,24 +832,24 @@ impl BytecodeGen { if num > u16::MAX as u64 { return None; } - let sigs_len = state.signatures.signatures.len(); + let sigs_len = state.signatures.handles.len(); if sigs_len == 0 { return None; } let sig_idx = idx.index(sigs_len); - let sig = &state.signatures.signatures[sig_idx]; + let sig = &state.signatures.handles[sig_idx]; if !BytecodeGen::is_valid_vector_element_sig(sig) { return None; } Bytecode::VecUnpack(SignatureIndex(sig_idx as TableIndex), num) }, BytecodeGen::VecSwap(idx) => { - let sigs_len = state.signatures.signatures.len(); + let sigs_len = state.signatures.handles.len(); if sigs_len == 0 { return None; } let sig_idx = idx.index(sigs_len); - let sig = &state.signatures.signatures[sig_idx]; + let sig = &state.signatures.handles[sig_idx]; if !BytecodeGen::is_valid_vector_element_sig(sig) { return None; } @@ -896,6 +860,202 @@ impl BytecodeGen { Some(bytecode) } + fn materialize_pack_unpack( + state: &mut FnDefnMaterializeState, + is_pack: bool, + def: PropIndex, + variant: Option, + ) -> Option { + let struct_defs_len = state.struct_defs.len(); + let struct_idx = def.index(struct_defs_len); + let struct_def_idx = StructDefinitionIndex(struct_idx as TableIndex); + let struct_def = &state.struct_defs[struct_idx]; + let struct_handle = &state.struct_handles[struct_def.struct_handle.0 as usize]; + let make_opc = |handle: StructDefinitionIndex| { + if is_pack { + Bytecode::Pack(handle) + } else { + Bytecode::Unpack(handle) + } + }; + let make_gen_opc = |handle: StructDefInstantiationIndex| { + if is_pack { + Bytecode::PackGeneric(handle) + } else { + Bytecode::UnpackGeneric(handle) + } + }; + let make_variant_opc = |handle: StructVariantHandleIndex| { + if is_pack { + Bytecode::PackVariant(handle) + } else { + Bytecode::UnpackVariant(handle) + } + }; + let make_gen_variant_opc = |handle: StructVariantInstantiationIndex| { + if is_pack { + Bytecode::PackVariantGeneric(handle) + } else { + Bytecode::UnpackVariantGeneric(handle) + } + }; + match (&struct_def.field_information, variant) { + (StructFieldInformation::Declared(..), None) => { + if struct_handle.type_parameters.is_empty() { + Some(make_opc(struct_def_idx)) + } else { + let sig_idx = state + .add_signature_from_type_params(struct_handle.type_param_constraints()); + let si_idx = + state + .type_instantiations + .add_instantiation(StructDefInstantiation { + def: struct_def_idx, + type_parameters: sig_idx, + }); + Some(make_gen_opc(StructDefInstantiationIndex( + si_idx as TableIndex, + ))) + } + }, + (StructFieldInformation::DeclaredVariants(..), Some(var_index)) => { + let variant = + var_index.index(struct_def.field_information.variant_count()) as VariantIndex; + // Create a new variant handle. Currently, this is not internalized as some + // other handles. + let sv_idx = state.struct_variant_handles.add( + StructVariantHandle { + struct_index: struct_def_idx, + variant, + }, + StructVariantHandleIndex, + ); + if struct_handle.type_parameters.is_empty() { + Some(make_variant_opc(sv_idx)) + } else { + let sig_idx = state + .add_signature_from_type_params(struct_handle.type_param_constraints()); + let si_idx = StructVariantInstantiationIndex( + state.struct_variant_instantiations.add_instantiation( + StructVariantInstantiation { + handle: sv_idx, + type_parameters: sig_idx, + }, + ) as TableIndex, + ); + Some(make_gen_variant_opc(si_idx)) + } + }, + _ => { + // All other cases pack/unpack not available + None + }, + } + } + + fn materialize_borrow( + state: &mut FnDefnMaterializeState, + def: PropIndex, + is_imm_borrow: bool, + variant: Option, + field: PropIndex, + ) -> Option { + let sd_idx = def.index(state.struct_defs.len()); + let struct_def = &state.struct_defs[sd_idx]; + let struct_handle = &state.struct_handles[struct_def.struct_handle.0 as usize]; + let make_opc = |handle: FieldHandleIndex| { + if is_imm_borrow { + Bytecode::ImmBorrowField(handle) + } else { + Bytecode::MutBorrowField(handle) + } + }; + let make_gen_opc = |handle: FieldInstantiationIndex| { + if is_imm_borrow { + Bytecode::ImmBorrowFieldGeneric(handle) + } else { + Bytecode::MutBorrowFieldGeneric(handle) + } + }; + let make_variant_opc = |handle: VariantFieldHandleIndex| { + if is_imm_borrow { + Bytecode::ImmBorrowVariantField(handle) + } else { + Bytecode::MutBorrowVariantField(handle) + } + }; + let make_gen_variant_opc = |handle: VariantFieldInstantiationIndex| { + if is_imm_borrow { + Bytecode::ImmBorrowVariantFieldGeneric(handle) + } else { + Bytecode::MutBorrowVariantFieldGeneric(handle) + } + }; + match (&struct_def.field_information, variant) { + (StructFieldInformation::Declared(..), None) => { + let field_count = struct_def.field_information.field_count(None); + if field_count == 0 { + return None; + } + let fh_idx = state.field_handles.add( + FieldHandle { + owner: StructDefinitionIndex(sd_idx as TableIndex), + field: field.index(field_count) as TableIndex, + }, + FieldHandleIndex, + ); + if struct_handle.type_parameters.is_empty() { + Some(make_opc(fh_idx)) + } else { + let sig_idx = state + .add_signature_from_type_params(struct_handle.type_param_constraints()); + let fi_idx = state + .field_instantiations + .add_instantiation(FieldInstantiation { + handle: fh_idx, + type_parameters: sig_idx, + }); + Some(make_gen_opc(FieldInstantiationIndex(fi_idx as TableIndex))) + } + }, + (StructFieldInformation::DeclaredVariants(..), Some(var_index)) => { + let variant = + var_index.index(struct_def.field_information.variant_count()) as VariantIndex; + let field_count = struct_def.field_information.field_count(Some(variant)); + if field_count == 0 { + return None; + } + let fh_idx = state.variant_field_handles.add( + VariantFieldHandle { + struct_index: StructDefinitionIndex(sd_idx as TableIndex), + variants: vec![variant], + field: field.index(field_count) as TableIndex, + }, + VariantFieldHandleIndex, + ); + if struct_handle.type_parameters.is_empty() { + Some(make_variant_opc(fh_idx)) + } else { + let sig_idx = state + .add_signature_from_type_params(struct_handle.type_param_constraints()); + let fi_idx = state.variant_field_instantiations.add_instantiation( + VariantFieldInstantiation { + handle: fh_idx, + type_parameters: sig_idx, + }, + ); + Some(make_gen_variant_opc(VariantFieldInstantiationIndex( + fi_idx as TableIndex, + ))) + } + }, + _ => { + // All other cases borrow not available + None + }, + } + } + /// Checks if the given type is well defined in the given context. /// No references are permitted. fn check_signature_token(token: &SignatureToken) -> bool { 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 b90a97902d5e8..03f5a4f7544c3 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 @@ -6,7 +6,7 @@ use crate::{ file_format::{ AbilitySet, FieldDefinition, IdentifierIndex, ModuleHandleIndex, SignatureToken, StructDefinition, StructFieldInformation, StructHandle, StructHandleIndex, - StructTypeParameter, TableIndex, TypeSignature, + StructTypeParameter, TableIndex, TypeSignature, VariantDefinition, }, internals::ModuleIndex, proptest_types::{ @@ -148,6 +148,8 @@ pub struct StructDefinitionGen { type_parameters: Vec<(AbilitySetGen, bool)>, #[allow(dead_code)] is_public: bool, + // Variants to generate, with prop index the name of the variant. + variants: Vec, field_defs: Option>, } @@ -164,23 +166,22 @@ impl StructDefinitionGen { type_parameter_count, ), any::(), + vec(any::(), 0..4), // Generate up to 4 variants (0 is pure struct) option::of(vec(FieldDefinitionGen::strategy(), field_count)), ) .prop_map( - |(name_idx, abilities, type_parameters, is_public, field_defs)| Self { + |(name_idx, abilities, type_parameters, is_public, variants, field_defs)| Self { name_idx, abilities, type_parameters, is_public, + variants, field_defs, }, ) } - pub fn materialize( - self, - state: &mut StDefnMaterializeState, - ) -> (Option, usize) { + pub fn materialize(self, state: &mut StDefnMaterializeState) -> Option { let mut field_names = HashSet::new(); let mut fields = vec![]; match self.field_defs { @@ -216,27 +217,38 @@ impl StructDefinitionGen { }; match state.add_struct_handle(handle) { Some(struct_handle) => { - if fields.is_empty() { - ( - Some(StructDefinition { - struct_handle, - field_information: StructFieldInformation::Native, - }), - 0, - ) + let field_information = if self.variants.is_empty() { + if fields.is_empty() { + StructFieldInformation::Native + } else { + StructFieldInformation::Declared(fields) + } } else { - let field_count = fields.len(); - let field_information = StructFieldInformation::Declared(fields); - ( - Some(StructDefinition { - struct_handle, - field_information, - }), - field_count, + // partition fields among variants + let mut variant_fields: Vec> = + (0..self.variants.len()).map(|_| vec![]).collect(); + for (i, fd) in fields.into_iter().enumerate() { + variant_fields[i % self.variants.len()].push(fd) + } + StructFieldInformation::DeclaredVariants( + variant_fields + .into_iter() + .zip(self.variants.iter()) + .map(|(fields, name)| VariantDefinition { + name: IdentifierIndex( + name.index(state.identifiers_len) as TableIndex + ), + fields, + }) + .collect(), ) - } + }; + Some(StructDefinition { + struct_handle, + field_information, + }) }, - None => (None, 0), + None => None, } } } diff --git a/third_party/move/move-binary-format/src/serializer.rs b/third_party/move/move-binary-format/src/serializer.rs index 4d2ba20bf4d81..5b8290c0157da 100644 --- a/third_party/move/move-binary-format/src/serializer.rs +++ b/third_party/move/move-binary-format/src/serializer.rs @@ -118,6 +118,34 @@ fn serialize_field_inst_index( write_as_uleb128(binary, idx.0, FIELD_INST_INDEX_MAX) } +fn serialize_variant_field_handle_index( + binary: &mut BinaryData, + idx: &VariantFieldHandleIndex, +) -> Result<()> { + write_as_uleb128(binary, idx.0, VARIANT_FIELD_HANDLE_INDEX_MAX) +} + +fn serialize_variant_field_inst_index( + binary: &mut BinaryData, + idx: &VariantFieldInstantiationIndex, +) -> Result<()> { + write_as_uleb128(binary, idx.0, VARIANT_FIELD_INST_INDEX_MAX) +} + +fn serialize_struct_variant_handle_index( + binary: &mut BinaryData, + idx: &StructVariantHandleIndex, +) -> Result<()> { + write_as_uleb128(binary, idx.0, STRUCT_VARIANT_HANDLE_INDEX_MAX) +} + +fn serialize_struct_variant_inst_index( + binary: &mut BinaryData, + idx: &StructVariantInstantiationIndex, +) -> Result<()> { + write_as_uleb128(binary, idx.0, STRUCT_VARIANT_INST_INDEX_MAX) +} + fn serialize_function_inst_index( binary: &mut BinaryData, idx: &FunctionInstantiationIndex, @@ -168,10 +196,18 @@ fn serialize_field_count(binary: &mut BinaryData, len: usize) -> Result<()> { write_as_uleb128(binary, len as u64, FIELD_COUNT_MAX) } +fn serialize_variant_count(binary: &mut BinaryData, len: usize) -> Result<()> { + write_as_uleb128(binary, len as u64, VARIANT_COUNT_MAX) +} + fn serialize_field_offset(binary: &mut BinaryData, offset: u16) -> Result<()> { write_as_uleb128(binary, offset, FIELD_OFFSET_MAX) } +fn serialize_variant_offset(binary: &mut BinaryData, offset: u16) -> Result<()> { + write_as_uleb128(binary, offset, VARIANT_OFFSET_MAX) +} + fn serialize_acquires_count(binary: &mut BinaryData, len: usize) -> Result<()> { write_as_uleb128(binary, len as u64, ACQUIRES_COUNT_MAX) } @@ -300,6 +336,11 @@ struct ModuleSerializer { field_handles: (u32, u32), field_instantiations: (u32, u32), friend_decls: (u32, u32), + // Since bytecode version 7 + variant_field_handles: (u32, u32), + variant_field_instantiations: (u32, u32), + struct_variant_handles: (u32, u32), + struct_variant_instantiations: (u32, u32), } /// Holds data to compute the header of a transaction script binary. @@ -527,7 +568,7 @@ fn serialize_function_instantiation( /// A `String` gets serialized as follows: /// - `String` size as a ULEB128 /// - `String` bytes - *exact format to be defined, Rust utf8 right now* -fn serialize_identifier(binary: &mut BinaryData, string: &str) -> Result<()> { +fn serialize_identifier(binary: &mut BinaryData, string: &Identifier) -> Result<()> { let bytes = string.as_bytes(); serialize_identifier_size(binary, bytes.len())?; for byte in bytes { @@ -578,12 +619,8 @@ fn serialize_byte_blob( } /// Serializes a `StructDefinition`. -/// -/// A `StructDefinition` gets serialized as follows: -/// - `StructDefinition.handle` as a ULEB128 (index into the `ModuleHandle` table) -/// - `StructDefinition.field_count` as a ULEB128 (number of fields defined in the type) -/// - `StructDefinition.fields` as a ULEB128 (index into the `FieldDefinition` table) fn serialize_struct_definition( + major_version: u32, binary: &mut BinaryData, struct_definition: &StructDefinition, ) -> Result<()> { @@ -594,6 +631,21 @@ fn serialize_struct_definition( binary.push(SerializedNativeStructFlag::DECLARED as u8)?; serialize_field_definitions(binary, fields) }, + StructFieldInformation::DeclaredVariants(variants) => { + if major_version >= VERSION_7 { + binary.push(SerializedNativeStructFlag::DECLARED_VARIANTS as u8)?; + serialize_variant_count(binary, variants.len())?; + for variant in variants { + serialize_variant_definition(binary, variant)? + } + Ok(()) + } else { + Err(anyhow!( + "Struct variants not supported in bytecode version {}", + major_version + )) + } + }, } } @@ -606,7 +658,7 @@ fn serialize_struct_def_instantiation( Ok(()) } -/// Serializes `FieldDefinition` within a struct. +/// Serializes `FieldDefinition` list within a struct. fn serialize_field_definitions(binary: &mut BinaryData, fields: &[FieldDefinition]) -> Result<()> { serialize_field_count(binary, fields.len())?; for field_definition in fields { @@ -615,12 +667,7 @@ fn serialize_field_definitions(binary: &mut BinaryData, fields: &[FieldDefinitio Ok(()) } -/// Serializes a `FieldDefinition`. -/// -/// A `FieldDefinition` gets serialized as follows: -/// - `FieldDefinition.struct_` as a ULEB128 (index into the `StructHandle` table) -/// - `StructDefinition.name` as a ULEB128 (index into the `IdentifierPool` table) -/// - `StructDefinition.signature` a serialized `TypeSignatureToekn`) +/// Serializes a `FieldDefinition` within a struct. fn serialize_field_definition( binary: &mut BinaryData, field_definition: &FieldDefinition, @@ -629,6 +676,14 @@ fn serialize_field_definition( serialize_signature_token(binary, &field_definition.signature.0) } +fn serialize_variant_definition( + binary: &mut BinaryData, + variant_definition: &VariantDefinition, +) -> Result<()> { + serialize_identifier_index(binary, &variant_definition.name)?; + serialize_field_definitions(binary, &variant_definition.fields) +} + fn serialize_field_handle(binary: &mut BinaryData, field_handle: &FieldHandle) -> Result<()> { serialize_struct_def_index(binary, &field_handle.owner)?; serialize_field_offset(binary, field_handle.field)?; @@ -644,6 +699,46 @@ fn serialize_field_instantiation( Ok(()) } +fn serialize_variant_field_handle( + binary: &mut BinaryData, + handle: &VariantFieldHandle, +) -> Result<()> { + serialize_struct_def_index(binary, &handle.struct_index)?; + serialize_field_offset(binary, handle.field)?; + serialize_variant_count(binary, handle.variants.len())?; + for variant in &handle.variants { + serialize_variant_offset(binary, *variant)? + } + Ok(()) +} + +fn serialize_variant_field_instantiation( + binary: &mut BinaryData, + inst: &VariantFieldInstantiation, +) -> Result<()> { + serialize_variant_field_handle_index(binary, &inst.handle)?; + serialize_signature_index(binary, &inst.type_parameters)?; + Ok(()) +} + +fn serialize_struct_variant_handle( + binary: &mut BinaryData, + handle: &StructVariantHandle, +) -> Result<()> { + serialize_struct_def_index(binary, &handle.struct_index)?; + serialize_variant_offset(binary, handle.variant)?; + Ok(()) +} + +fn serialize_struct_variant_instantiation( + binary: &mut BinaryData, + inst: &StructVariantInstantiation, +) -> Result<()> { + serialize_struct_variant_handle_index(binary, &inst.handle)?; + serialize_signature_index(binary, &inst.type_parameters)?; + Ok(()) +} + /// Serializes a `Vec`. fn serialize_acquires(binary: &mut BinaryData, indices: &[StructDefinitionIndex]) -> Result<()> { serialize_acquires_count(binary, indices.len())?; @@ -933,6 +1028,22 @@ fn serialize_instruction_inner( binary.push(Opcodes::IMM_BORROW_FIELD_GENERIC as u8)?; serialize_field_inst_index(binary, field_idx) }, + Bytecode::MutBorrowVariantField(field_idx) => { + binary.push(Opcodes::MUT_BORROW_VARIANT_FIELD as u8)?; + serialize_variant_field_handle_index(binary, field_idx) + }, + Bytecode::MutBorrowVariantFieldGeneric(field_idx) => { + binary.push(Opcodes::MUT_BORROW_VARIANT_FIELD_GENERIC as u8)?; + serialize_variant_field_inst_index(binary, field_idx) + }, + Bytecode::ImmBorrowVariantField(field_idx) => { + binary.push(Opcodes::IMM_BORROW_VARIANT_FIELD as u8)?; + serialize_variant_field_handle_index(binary, field_idx) + }, + Bytecode::ImmBorrowVariantFieldGeneric(field_idx) => { + binary.push(Opcodes::IMM_BORROW_VARIANT_FIELD_GENERIC as u8)?; + serialize_variant_field_inst_index(binary, field_idx) + }, Bytecode::Call(method_idx) => { binary.push(Opcodes::CALL as u8)?; serialize_function_handle_index(binary, method_idx) @@ -957,6 +1068,30 @@ fn serialize_instruction_inner( binary.push(Opcodes::UNPACK_GENERIC as u8)?; serialize_struct_def_inst_index(binary, class_idx) }, + Bytecode::UnpackVariant(class_idx) => { + binary.push(Opcodes::UNPACK_VARIANT as u8)?; + serialize_struct_variant_handle_index(binary, class_idx) + }, + Bytecode::PackVariant(class_idx) => { + binary.push(Opcodes::PACK_VARIANT as u8)?; + serialize_struct_variant_handle_index(binary, class_idx) + }, + Bytecode::UnpackVariantGeneric(class_idx) => { + binary.push(Opcodes::UNPACK_VARIANT_GENERIC as u8)?; + serialize_struct_variant_inst_index(binary, class_idx) + }, + Bytecode::PackVariantGeneric(class_idx) => { + binary.push(Opcodes::PACK_VARIANT_GENERIC as u8)?; + serialize_struct_variant_inst_index(binary, class_idx) + }, + Bytecode::TestVariant(class_idx) => { + binary.push(Opcodes::TEST_VARIANT as u8)?; + serialize_struct_variant_handle_index(binary, class_idx) + }, + Bytecode::TestVariantGeneric(class_idx) => { + binary.push(Opcodes::TEST_VARIANT_GENERIC as u8)?; + serialize_struct_variant_inst_index(binary, class_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), @@ -1083,6 +1218,27 @@ fn serialize_code(major_version: u32, binary: &mut BinaryData, code: &[Bytecode] Ok(()) } +/// Generic function to serialize a table. Maintains a table counter, skipping empty table +/// entries, and returns start index and length in the binary. +fn serialize_table( + table_count: &mut u8, + binary: &mut BinaryData, + table: &[T], + serializer: impl Fn(&mut BinaryData, &T) -> Result<()>, +) -> Result<(u32, u32)> { + if !table.is_empty() { + // Note: table count is smaller than `max(u8)`, so wrapping_add is safe + *table_count = table_count.wrapping_add(1); + let start = check_index_in_binary(binary.len())?; + for elem in table { + serializer(binary, elem)? + } + Ok((start, checked_calculate_table_size(binary, start)?)) + } else { + Ok((0, 0)) + } +} + /// Compute the table size with a check for underflow fn checked_calculate_table_size(binary: &mut BinaryData, start: u32) -> Result { let offset = check_index_in_binary(binary.len())?; @@ -1183,171 +1339,65 @@ impl CommonSerializer { tables: &T, ) -> Result<()> { debug_assert!(self.table_count == 0); - self.serialize_module_handles(binary, tables.get_module_handles())?; - self.serialize_struct_handles(binary, tables.get_struct_handles())?; - self.serialize_function_handles(binary, tables.get_function_handles())?; + let mut table_count = self.table_count; // avoid &mut on self + self.module_handles = serialize_table( + &mut table_count, + binary, + tables.get_module_handles(), + serialize_module_handle, + )?; + self.struct_handles = serialize_table( + &mut table_count, + binary, + tables.get_struct_handles(), + serialize_struct_handle, + )?; + self.function_handles = serialize_table( + &mut table_count, + binary, + tables.get_function_handles(), + |binary, handle| serialize_function_handle(self.major_version, binary, handle), + )?; debug_assert!(self.table_count < 6); - self.serialize_function_instantiations(binary, tables.get_function_instantiations())?; - self.serialize_signatures(binary, tables.get_signatures())?; - self.serialize_identifiers(binary, tables.get_identifiers())?; - self.serialize_address_identifiers(binary, tables.get_address_identifiers())?; - self.serialize_constants(binary, tables.get_constant_pool())?; + self.function_instantiations = serialize_table( + &mut table_count, + binary, + tables.get_function_instantiations(), + serialize_function_instantiation, + )?; + self.signatures = serialize_table( + &mut table_count, + binary, + tables.get_signatures(), + serialize_signature, + )?; + self.identifiers = serialize_table( + &mut table_count, + binary, + tables.get_identifiers(), + serialize_identifier, + )?; + self.address_identifiers = serialize_table( + &mut table_count, + binary, + tables.get_address_identifiers(), + serialize_address, + )?; + self.constant_pool = serialize_table( + &mut table_count, + binary, + tables.get_constant_pool(), + serialize_constant, + )?; if self.major_version >= VERSION_5 { - self.serialize_metadata(binary, tables.get_metadata())?; - } - Ok(()) - } - - /// Serializes `ModuleHandle` table. - fn serialize_module_handles( - &mut self, - binary: &mut BinaryData, - module_handles: &[ModuleHandle], - ) -> Result<()> { - if !module_handles.is_empty() { - self.table_count += 1; - self.module_handles.0 = check_index_in_binary(binary.len())?; - for module_handle in module_handles { - serialize_module_handle(binary, module_handle)?; - } - self.module_handles.1 = checked_calculate_table_size(binary, self.module_handles.0)?; - } - Ok(()) - } - - /// Serializes `StructHandle` table. - fn serialize_struct_handles( - &mut self, - binary: &mut BinaryData, - struct_handles: &[StructHandle], - ) -> Result<()> { - if !struct_handles.is_empty() { - self.table_count += 1; - self.struct_handles.0 = check_index_in_binary(binary.len())?; - for struct_handle in struct_handles { - serialize_struct_handle(binary, struct_handle)?; - } - self.struct_handles.1 = checked_calculate_table_size(binary, self.struct_handles.0)?; - } - Ok(()) - } - - /// Serializes `FunctionHandle` table. - fn serialize_function_handles( - &mut self, - binary: &mut BinaryData, - function_handles: &[FunctionHandle], - ) -> Result<()> { - if !function_handles.is_empty() { - self.table_count += 1; - self.function_handles.0 = check_index_in_binary(binary.len())?; - for function_handle in function_handles { - serialize_function_handle(self.major_version, binary, function_handle)?; - } - self.function_handles.1 = - checked_calculate_table_size(binary, self.function_handles.0)?; - } - Ok(()) - } - - /// Serializes `FunctionInstantiation` table. - fn serialize_function_instantiations( - &mut self, - binary: &mut BinaryData, - function_instantiations: &[FunctionInstantiation], - ) -> Result<()> { - if !function_instantiations.is_empty() { - self.table_count += 1; - self.function_instantiations.0 = check_index_in_binary(binary.len())?; - for function_instantiation in function_instantiations { - serialize_function_instantiation(binary, function_instantiation)?; - } - self.function_instantiations.1 = - checked_calculate_table_size(binary, self.function_instantiations.0)?; - } - Ok(()) - } - - /// Serializes `Identifiers`. - fn serialize_identifiers( - &mut self, - binary: &mut BinaryData, - identifiers: &[Identifier], - ) -> Result<()> { - if !identifiers.is_empty() { - self.table_count += 1; - self.identifiers.0 = check_index_in_binary(binary.len())?; - for identifier in identifiers { - // User strings and identifiers use the same serialization. - serialize_identifier(binary, identifier.as_str())?; - } - self.identifiers.1 = checked_calculate_table_size(binary, self.identifiers.0)?; - } - Ok(()) - } - - /// Serializes `AddressIdentifiers`. - fn serialize_address_identifiers( - &mut self, - binary: &mut BinaryData, - addresses: &[AccountAddress], - ) -> Result<()> { - if !addresses.is_empty() { - self.table_count += 1; - self.address_identifiers.0 = check_index_in_binary(binary.len())?; - for address in addresses { - serialize_address(binary, address)?; - } - self.address_identifiers.1 = - checked_calculate_table_size(binary, self.address_identifiers.0)?; - } - Ok(()) - } - - /// Serializes `ConstantPool`. - fn serialize_constants( - &mut self, - binary: &mut BinaryData, - constants: &[Constant], - ) -> Result<()> { - if !constants.is_empty() { - self.table_count += 1; - self.constant_pool.0 = check_index_in_binary(binary.len())?; - for constant in constants { - serialize_constant(binary, constant)?; - } - self.constant_pool.1 = checked_calculate_table_size(binary, self.constant_pool.0)?; - } - Ok(()) - } - - /// Serializes metadata. - fn serialize_metadata(&mut self, binary: &mut BinaryData, metadata: &[Metadata]) -> Result<()> { - if !metadata.is_empty() { - self.table_count += 1; - self.metadata.0 = check_index_in_binary(binary.len())?; - for entry in metadata { - serialize_metadata_entry(binary, entry)?; - } - self.metadata.1 = checked_calculate_table_size(binary, self.metadata.0)?; - } - Ok(()) - } - - /// Serializes `SignaturePool` table. - fn serialize_signatures( - &mut self, - binary: &mut BinaryData, - signatures: &[Signature], - ) -> Result<()> { - if !signatures.is_empty() { - self.table_count += 1; - self.signatures.0 = check_index_in_binary(binary.len())?; - for signature in signatures { - serialize_signature(binary, signature)?; - } - self.signatures.1 = checked_calculate_table_size(binary, self.signatures.0)?; + self.metadata = serialize_table( + &mut table_count, + binary, + tables.get_metadata(), + serialize_metadata_entry, + )?; } + self.table_count = table_count; Ok(()) } @@ -1366,17 +1416,82 @@ impl ModuleSerializer { field_handles: (0, 0), field_instantiations: (0, 0), friend_decls: (0, 0), + // Since bytecode version 7 + variant_field_handles: (0, 0), + variant_field_instantiations: (0, 0), + struct_variant_handles: (0, 0), + struct_variant_instantiations: (0, 0), } } fn serialize_tables(&mut self, binary: &mut BinaryData, module: &CompiledModule) -> Result<()> { self.common.serialize_common_tables(binary, module)?; - self.serialize_struct_definitions(binary, &module.struct_defs)?; - self.serialize_struct_def_instantiations(binary, &module.struct_def_instantiations)?; - self.serialize_function_definitions(binary, &module.function_defs)?; - self.serialize_field_handles(binary, &module.field_handles)?; - self.serialize_field_instantiations(binary, &module.field_instantiations)?; - self.serialize_friend_declarations(binary, &module.friend_decls) + let mut table_count = self.common.table_count; // avoid holding &mut on self + self.struct_defs = serialize_table( + &mut table_count, + binary, + &module.struct_defs, + |binary, def| serialize_struct_definition(self.common.major_version, binary, def), + )?; + self.struct_def_instantiations = serialize_table( + &mut table_count, + binary, + &module.struct_def_instantiations, + serialize_struct_def_instantiation, + )?; + self.function_defs = serialize_table( + &mut table_count, + binary, + &module.function_defs, + |binary, def| self.serialize_function_definition(binary, def), + )?; + self.field_handles = serialize_table( + &mut table_count, + binary, + &module.field_handles, + serialize_field_handle, + )?; + self.field_instantiations = serialize_table( + &mut table_count, + binary, + &module.field_instantiations, + serialize_field_instantiation, + )?; + self.friend_decls = serialize_table( + &mut table_count, + binary, + &module.friend_decls, + serialize_module_handle, + )?; + + if self.common.major_version() >= VERSION_7 { + self.variant_field_handles = serialize_table( + &mut table_count, + binary, + &module.variant_field_handles, + serialize_variant_field_handle, + )?; + self.variant_field_instantiations = serialize_table( + &mut table_count, + binary, + &module.variant_field_instantiations, + serialize_variant_field_instantiation, + )?; + self.struct_variant_handles = serialize_table( + &mut table_count, + binary, + &module.struct_variant_handles, + serialize_struct_variant_handle, + )?; + self.struct_variant_instantiations = serialize_table( + &mut table_count, + binary, + &module.struct_variant_instantiations, + serialize_struct_variant_instantiation, + )?; + } + self.common.table_count = table_count; + Ok(()) } fn serialize_table_indices(&mut self, binary: &mut BinaryData) -> Result<()> { @@ -1401,7 +1516,7 @@ impl ModuleSerializer { )?; serialize_table_index( binary, - TableType::FIELD_HANDLE, + TableType::FIELD_HANDLES, self.field_handles.0, self.field_handles.1, )?; @@ -1417,91 +1532,33 @@ impl ModuleSerializer { self.friend_decls.0, self.friend_decls.1, )?; - Ok(()) - } - - /// Serializes `StructDefinition` table. - fn serialize_struct_definitions( - &mut self, - binary: &mut BinaryData, - struct_definitions: &[StructDefinition], - ) -> Result<()> { - if !struct_definitions.is_empty() { - self.common.table_count = self.common.table_count.wrapping_add(1); // the count will bound to a small number - self.struct_defs.0 = check_index_in_binary(binary.len())?; - for struct_definition in struct_definitions { - serialize_struct_definition(binary, struct_definition)?; - } - self.struct_defs.1 = checked_calculate_table_size(binary, self.struct_defs.0)?; - } - Ok(()) - } - - /// Serializes `StructInstantiation` table. - fn serialize_struct_def_instantiations( - &mut self, - binary: &mut BinaryData, - struct_def_instantiations: &[StructDefInstantiation], - ) -> Result<()> { - if !struct_def_instantiations.is_empty() { - self.common.table_count = self.common.table_count.wrapping_add(1); // the count will bound to a small number - self.struct_def_instantiations.0 = check_index_in_binary(binary.len())?; - for struct_instantiation in struct_def_instantiations { - serialize_struct_def_instantiation(binary, struct_instantiation)?; - } - self.struct_def_instantiations.1 = - checked_calculate_table_size(binary, self.struct_def_instantiations.0)?; - } - Ok(()) - } - - /// Serializes `FunctionDefinition` table. - fn serialize_field_handles( - &mut self, - binary: &mut BinaryData, - field_handles: &[FieldHandle], - ) -> Result<()> { - if !field_handles.is_empty() { - self.common.table_count += 1; - self.field_handles.0 = check_index_in_binary(binary.len())?; - for field_handle in field_handles { - serialize_field_handle(binary, field_handle)?; - } - self.field_handles.1 = checked_calculate_table_size(binary, self.field_handles.0)?; - } - Ok(()) - } - - fn serialize_field_instantiations( - &mut self, - binary: &mut BinaryData, - field_instantiations: &[FieldInstantiation], - ) -> Result<()> { - if !field_instantiations.is_empty() { - self.common.table_count += 1; - self.field_instantiations.0 = check_index_in_binary(binary.len())?; - for field_instantiation in field_instantiations { - serialize_field_instantiation(binary, field_instantiation)?; - } - self.field_instantiations.1 = - checked_calculate_table_size(binary, self.field_instantiations.0)?; + if self.common.major_version >= VERSION_7 { + serialize_table_index( + binary, + TableType::VARIANT_FIELD_HANDLES, + self.variant_field_handles.0, + self.variant_field_handles.1, + )?; + serialize_table_index( + binary, + TableType::VARIANT_FIELD_INST, + self.variant_field_instantiations.0, + self.variant_field_instantiations.1, + )?; + serialize_table_index( + binary, + TableType::STRUCT_VARIANT_HANDLES, + self.struct_variant_handles.0, + self.struct_variant_handles.1, + )?; + serialize_table_index( + binary, + TableType::STRUCT_VARIANT_INST, + self.struct_variant_instantiations.0, + self.struct_variant_instantiations.1, + )?; } - Ok(()) - } - fn serialize_function_definitions( - &mut self, - binary: &mut BinaryData, - function_definitions: &[FunctionDefinition], - ) -> Result<()> { - if !function_definitions.is_empty() { - self.common.table_count = self.common.table_count.wrapping_add(1); // the count will bound to a small number - self.function_defs.0 = check_index_in_binary(binary.len())?; - for function_definition in function_definitions { - self.serialize_function_definition(binary, function_definition)?; - } - self.function_defs.1 = checked_calculate_table_size(binary, self.function_defs.0)?; - } Ok(()) } @@ -1515,7 +1572,7 @@ impl ModuleSerializer { /// - bit 0x2: native indicator, indicates whether the function is a native function. /// - `FunctionDefinition.code` a variable size stream for the `CodeUnit` fn serialize_function_definition( - &mut self, + &self, binary: &mut BinaryData, function_definition: &FunctionDefinition, ) -> Result<()> { @@ -1548,22 +1605,6 @@ impl ModuleSerializer { } Ok(()) } - - fn serialize_friend_declarations( - &mut self, - binary: &mut BinaryData, - friend_declarations: &[ModuleHandle], - ) -> Result<()> { - if !friend_declarations.is_empty() { - self.common.table_count = self.common.table_count.wrapping_add(1); // the count will bound to a small number - self.friend_decls.0 = check_index_in_binary(binary.len())?; - for module in friend_declarations { - serialize_module_handle(binary, module)?; - } - self.friend_decls.1 = checked_calculate_table_size(binary, self.friend_decls.0)?; - } - Ok(()) - } } impl ScriptSerializer { diff --git a/third_party/move/move-binary-format/src/unit_tests/compatibility_tests.rs b/third_party/move/move-binary-format/src/unit_tests/compatibility_tests.rs index 5d2db0369afdd..6bdbf6de60a2e 100644 --- a/third_party/move/move-binary-format/src/unit_tests/compatibility_tests.rs +++ b/third_party/move/move-binary-format/src/unit_tests/compatibility_tests.rs @@ -65,6 +65,10 @@ fn mk_module(vis: u8) -> normalized::Module { struct_def_instantiations: vec![], function_instantiations: vec![], field_instantiations: vec![], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; normalized::Module::new(&m) } diff --git a/third_party/move/move-binary-format/src/views.rs b/third_party/move/move-binary-format/src/views.rs index c269e0855a6dd..b9ed38049ddaa 100644 --- a/third_party/move/move-binary-format/src/views.rs +++ b/third_party/move/move-binary-format/src/views.rs @@ -352,7 +352,9 @@ impl<'a, T: ModuleAccess> StructDefinitionView<'a, T> { pub fn is_native(&self) -> bool { match &self.struct_def.field_information { StructFieldInformation::Native => true, - StructFieldInformation::Declared { .. } => false, + StructFieldInformation::Declared(..) | StructFieldInformation::DeclaredVariants(..) => { + false + }, } } @@ -363,15 +365,19 @@ impl<'a, T: ModuleAccess> StructDefinitionView<'a, T> { pub fn fields( &self, ) -> Option> + Send> { + Some(self.fields_optional_variant(None)) + } + + pub fn fields_optional_variant( + &self, + variant: Option, + ) -> impl DoubleEndedIterator> + Send { let module = self.module; - match &self.struct_def.field_information { - StructFieldInformation::Native => None, - StructFieldInformation::Declared(fields) => Some( - fields - .iter() - .map(move |field_def| FieldDefinitionView::new(module, field_def)), - ), - } + self.struct_def + .field_information + .fields(variant) + .into_iter() + .map(move |field_def| FieldDefinitionView::new(module, field_def)) } pub fn name(&self) -> &'a IdentStr { @@ -729,3 +735,10 @@ impl_view_internals!(FieldDefinitionView, FieldDefinition, field_def); impl_view_internals!(TypeSignatureView, TypeSignature, type_signature); impl_view_internals!(SignatureView, Signature, signature); impl_view_internals!(SignatureTokenView, SignatureToken, token); + +/// A type to represent either a FieldHandleIndex or a VariantFieldHandleIndex. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum FieldOrVariantIndex { + FieldIndex(FieldHandleIndex), + VariantFieldIndex(VariantFieldHandleIndex), +} diff --git a/third_party/move/move-bytecode-spec/src/lib.rs b/third_party/move/move-bytecode-spec/src/lib.rs index a9c0fcb0b92b5..c0e573d29c304 100644 --- a/third_party/move/move-bytecode-spec/src/lib.rs +++ b/third_party/move/move-bytecode-spec/src/lib.rs @@ -133,6 +133,7 @@ static VALID_GROUPS: Lazy> = Lazy::new(|| { "comparison", "boolean", "struct", + "variant", "global", "vector", ] diff --git a/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/dependencies_tests.rs b/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/dependencies_tests.rs index ffc0d59f78dc8..0fb0b54bb01f4 100644 --- a/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/dependencies_tests.rs +++ b/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/dependencies_tests.rs @@ -70,6 +70,9 @@ fn mk_script_function_module() -> CompiledModule { }), }, ], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], signatures: vec![ Signature(vec![]), // void ], @@ -82,6 +85,7 @@ fn mk_script_function_module() -> CompiledModule { struct_def_instantiations: vec![], function_instantiations: vec![], field_instantiations: vec![], + variant_field_instantiations: vec![], }; move_bytecode_verifier::verify_module(&m).unwrap(); m @@ -165,6 +169,9 @@ fn mk_invoking_module(use_generic: bool, valid: bool) -> CompiledModule { }), }, ], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], signatures: vec![ Signature(vec![]), // void Signature(vec![SignatureToken::U64]), // u64 @@ -177,6 +184,7 @@ fn mk_invoking_module(use_generic: bool, valid: bool) -> CompiledModule { friend_decls: vec![], struct_def_instantiations: vec![], field_instantiations: vec![], + variant_field_instantiations: vec![], }; move_bytecode_verifier::verify_module(&m).unwrap(); m diff --git a/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/generic_ops_tests.rs b/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/generic_ops_tests.rs index b1b6ea8ff1b7e..7808e41642981 100644 --- a/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/generic_ops_tests.rs +++ b/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/generic_ops_tests.rs @@ -166,6 +166,9 @@ fn make_module() -> CompiledModule { }), }, ], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], signatures: vec![ Signature(vec![]), // void Signature(vec![SignatureToken::Signer]), // Signer @@ -183,6 +186,7 @@ fn make_module() -> CompiledModule { struct_def_instantiations: vec![], function_instantiations: vec![], field_instantiations: vec![], + variant_field_instantiations: vec![], } } diff --git a/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/limit_tests.rs b/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/limit_tests.rs index 0118c6455e5a9..4156b1c567fda 100644 --- a/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/limit_tests.rs +++ b/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/limit_tests.rs @@ -243,6 +243,10 @@ fn big_vec_unpacks() { code, }), }], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; // save module and verify that it can ser/de diff --git a/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/signature_tests.rs b/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/signature_tests.rs index e0b4267e3d72a..e7b9f123d8b86 100644 --- a/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/signature_tests.rs +++ b/third_party/move/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/signature_tests.rs @@ -127,6 +127,10 @@ fn no_verify_locals_good() { }), }, ], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; assert!(verify_module(&compiled_module_good).is_ok()); } @@ -211,6 +215,10 @@ fn big_signature_test() { code, }), }], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; // save module and verify that it can ser/de 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 0e1e80282c883..7669002173f0d 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 @@ -485,6 +485,19 @@ impl<'a> ApplyCodeUnitBoundsContext<'a> { | Gt | Le | Ge | Abort | Nop => { panic!("Bytecode has no internal index: {:?}", code[bytecode_idx]) }, + PackVariant(_) + | PackVariantGeneric(_) + | UnpackVariant(_) + | UnpackVariantGeneric(_) + | TestVariant(_) + | TestVariantGeneric(_) + | MutBorrowVariantField(_) + | MutBorrowVariantFieldGeneric(_) + | ImmBorrowVariantField(_) + | ImmBorrowVariantFieldGeneric(_) => { + // TODO(#13806): implement + panic!("Struct variant bytecode NYI: {:?}", code[bytecode_idx]) + }, }; code[bytecode_idx] = new_bytecode; @@ -543,5 +556,19 @@ fn is_interesting(bytecode: &Bytecode) -> bool { | LdU256(_) | 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 | Abort | Nop => false, + + PackVariant(_) + | PackVariantGeneric(_) + | UnpackVariant(_) + | UnpackVariantGeneric(_) + | TestVariant(_) + | TestVariantGeneric(_) + | MutBorrowVariantField(_) + | MutBorrowVariantFieldGeneric(_) + | ImmBorrowVariantField(_) + | ImmBorrowVariantFieldGeneric(_) => { + // TODO(#13806): implement + false + }, } } diff --git a/third_party/move/move-bytecode-verifier/invalid-mutations/src/signature.rs b/third_party/move/move-bytecode-verifier/invalid-mutations/src/signature.rs index 6181bf66d3ce6..e353e30547b23 100644 --- a/third_party/move/move-bytecode-verifier/invalid-mutations/src/signature.rs +++ b/third_party/move/move-bytecode-verifier/invalid-mutations/src/signature.rs @@ -59,15 +59,36 @@ impl<'a> FieldRefMutation<'a> { for (s_idx, f_idx) in self.mutations { let struct_idx = s_idx.index(module.struct_defs.len()); let struct_def = &mut module.struct_defs[struct_idx]; - if let StructFieldInformation::Declared(fields) = &mut struct_def.field_information { - if fields.is_empty() { - continue; - } - let field_idx = f_idx.index(fields.len()); - let field_def = &mut fields[field_idx]; - let new_ty = mutate_field(&field_def.signature.0); - fields[field_idx].signature = TypeSignature(new_ty); - mutations = true; + match &mut struct_def.field_information { + StructFieldInformation::Declared(fields) => { + if fields.is_empty() { + continue; + } + let field_idx = f_idx.index(fields.len()); + let field_def = &mut fields[field_idx]; + let new_ty = mutate_field(&field_def.signature.0); + fields[field_idx].signature = TypeSignature(new_ty); + mutations = true; + }, + StructFieldInformation::DeclaredVariants(variants) => { + let all_fields_count = variants.iter().map(|v| v.fields.len()).sum(); + if all_fields_count == 0 { + continue; + } + let mut field_idx = f_idx.index(all_fields_count); + let mut variant = 0; + let mut variant_fields = &mut variants[variant].fields; + while field_idx >= variant_fields.len() { + field_idx -= variant_fields.len(); + variant += 1; + variant_fields = &mut variants[variant].fields; + } + let field_def = &mut variant_fields[field_idx]; + let new_ty = mutate_field(&field_def.signature.0); + variant_fields[field_idx].signature = TypeSignature(new_ty); + mutations = true; + }, + StructFieldInformation::Native => {}, } } mutations diff --git a/third_party/move/move-bytecode-verifier/src/ability_field_requirements.rs b/third_party/move/move-bytecode-verifier/src/ability_field_requirements.rs index 4480800c71384..c4b67c9c87bda 100644 --- a/third_party/move/move-bytecode-verifier/src/ability_field_requirements.rs +++ b/third_party/move/move-bytecode-verifier/src/ability_field_requirements.rs @@ -8,7 +8,9 @@ use move_binary_format::{ access::ModuleAccess, binary_views::BinaryIndexedView, errors::{verification_error, Location, PartialVMResult, VMResult}, - file_format::{AbilitySet, CompiledModule, StructFieldInformation, TableIndex}, + file_format::{ + AbilitySet, CompiledModule, FieldDefinition, StructFieldInformation, TableIndex, + }, IndexKind, }; use move_core_types::vm_status::StatusCode; @@ -21,10 +23,6 @@ fn verify_module_impl(module: &CompiledModule) -> PartialVMResult<()> { let view = BinaryIndexedView::Module(module); for (idx, struct_def) in module.struct_defs().iter().enumerate() { let sh = module.struct_handle_at(struct_def.struct_handle); - let fields = match &struct_def.field_information { - StructFieldInformation::Native => continue, - StructFieldInformation::Declared(fields) => fields, - }; let required_abilities = sh .abilities .into_iter() @@ -37,15 +35,42 @@ fn verify_module_impl(module: &CompiledModule) -> PartialVMResult<()> { .iter() .map(|_| AbilitySet::ALL) .collect::>(); - for field in fields { - let field_abilities = view.abilities(&field.signature.0, &type_parameter_abilities)?; - if !required_abilities.is_subset(field_abilities) { - return Err(verification_error( - StatusCode::FIELD_MISSING_TYPE_ABILITY, - IndexKind::StructDefinition, - idx as TableIndex, - )); - } + match &struct_def.field_information { + StructFieldInformation::Native => continue, + StructFieldInformation::Declared(fields) => check_field_abilities( + view, + idx, + required_abilities, + &type_parameter_abilities, + fields.iter(), + )?, + StructFieldInformation::DeclaredVariants(variants) => check_field_abilities( + view, + idx, + required_abilities, + &type_parameter_abilities, + variants.iter().flat_map(|v| v.fields.iter()), + )?, + } + } + Ok(()) +} + +fn check_field_abilities<'a>( + view: BinaryIndexedView, + idx: usize, + required_abilities: AbilitySet, + type_parameter_abilities: &[AbilitySet], + fields: impl Iterator, +) -> PartialVMResult<()> { + for field in fields { + let field_abilities = view.abilities(&field.signature.0, type_parameter_abilities)?; + if !required_abilities.is_subset(field_abilities) { + return Err(verification_error( + StatusCode::FIELD_MISSING_TYPE_ABILITY, + IndexKind::StructDefinition, + idx as TableIndex, + )); } } Ok(()) 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 60e76973a7e95..6b15a043ea97b 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 @@ -119,6 +119,10 @@ impl<'a> AcquiresVerifier<'a> { | Bytecode::MutBorrowFieldGeneric(_) | Bytecode::ImmBorrowField(_) | Bytecode::ImmBorrowFieldGeneric(_) + | Bytecode::MutBorrowVariantField(_) + | Bytecode::MutBorrowVariantFieldGeneric(_) + | Bytecode::ImmBorrowVariantField(_) + | Bytecode::ImmBorrowVariantFieldGeneric(_) | Bytecode::LdU8(_) | Bytecode::LdU16(_) | Bytecode::LdU32(_) @@ -132,6 +136,12 @@ impl<'a> AcquiresVerifier<'a> { | Bytecode::PackGeneric(_) | Bytecode::Unpack(_) | Bytecode::UnpackGeneric(_) + | Bytecode::PackVariant(_) + | Bytecode::PackVariantGeneric(_) + | Bytecode::UnpackVariant(_) + | Bytecode::UnpackVariantGeneric(_) + | Bytecode::TestVariant(_) + | Bytecode::TestVariantGeneric(_) | Bytecode::ReadRef | Bytecode::WriteRef | Bytecode::CastU8 diff --git a/third_party/move/move-bytecode-verifier/src/check_duplication.rs b/third_party/move/move-bytecode-verifier/src/check_duplication.rs index 0847657f7cbe0..e79f279dc021d 100644 --- a/third_party/move/move-bytecode-verifier/src/check_duplication.rs +++ b/third_party/move/move-bytecode-verifier/src/check_duplication.rs @@ -13,9 +13,9 @@ use move_binary_format::{ access::{ModuleAccess, ScriptAccess}, errors::{verification_error, Location, PartialVMResult, VMResult}, file_format::{ - CompiledModule, CompiledScript, Constant, FunctionHandle, FunctionHandleIndex, - FunctionInstantiation, ModuleHandle, Signature, StructFieldInformation, StructHandle, - StructHandleIndex, TableIndex, + CompiledModule, CompiledScript, Constant, FieldDefinition, FunctionHandle, + FunctionHandleIndex, FunctionInstantiation, ModuleHandle, Signature, + StructFieldInformation, StructHandle, StructHandleIndex, TableIndex, }, IndexKind, }; @@ -30,7 +30,12 @@ pub struct DuplicationChecker<'a> { impl<'a> DuplicationChecker<'a> { pub fn verify_module(module: &'a CompiledModule) -> VMResult<()> { - Self::verify_module_impl(module).map_err(|e| e.finish(Location::Module(module.self_id()))) + let res = Self::verify_module_impl(module) + .map_err(|e| e.finish(Location::Module(module.self_id()))); + if let Err(e) = res.clone() { + println!("error: {}", e) + } + res } fn verify_module_impl(module: &'a CompiledModule) -> PartialVMResult<()> { @@ -47,7 +52,7 @@ impl<'a> DuplicationChecker<'a> { let checker = Self { module }; checker.check_field_handles()?; checker.check_field_instantiations()?; - checker.check_function_defintions()?; + checker.check_function_definitions()?; checker.check_struct_definitions()?; checker.check_struct_instantiations() } @@ -209,24 +214,24 @@ impl<'a> DuplicationChecker<'a> { } // Field names in structs must be unique for (struct_idx, struct_def) in self.module.struct_defs().iter().enumerate() { - let fields = match &struct_def.field_information { + match &struct_def.field_information { StructFieldInformation::Native => continue, - StructFieldInformation::Declared(fields) => fields, + StructFieldInformation::Declared(fields) => { + if fields.is_empty() { + return Err(verification_error( + StatusCode::ZERO_SIZED_STRUCT, + IndexKind::StructDefinition, + struct_idx as TableIndex, + )); + } + Self::check_duplicate_fields(fields.iter())? + }, + StructFieldInformation::DeclaredVariants(variants) => { + for variant in variants { + Self::check_duplicate_fields(variant.fields.iter())? + } + }, }; - if fields.is_empty() { - return Err(verification_error( - StatusCode::ZERO_SIZED_STRUCT, - IndexKind::StructDefinition, - struct_idx as TableIndex, - )); - } - if let Some(idx) = Self::first_duplicate_element(fields.iter().map(|x| x.name)) { - return Err(verification_error( - StatusCode::DUPLICATE_ELEMENT, - IndexKind::FieldDefinition, - idx, - )); - } } // Check that each struct definition is pointing to the self module if let Some(idx) = self.module.struct_defs().iter().position(|x| { @@ -259,7 +264,21 @@ impl<'a> DuplicationChecker<'a> { Ok(()) } - fn check_function_defintions(&self) -> PartialVMResult<()> { + fn check_duplicate_fields<'l>( + fields: impl Iterator, + ) -> PartialVMResult<()> { + if let Some(idx) = Self::first_duplicate_element(fields.map(|x| x.name)) { + Err(verification_error( + StatusCode::DUPLICATE_ELEMENT, + IndexKind::FieldDefinition, + idx, + )) + } else { + Ok(()) + } + } + + fn check_function_definitions(&self) -> PartialVMResult<()> { // FunctionDefinition - contained FunctionHandle defines uniqueness if let Some(idx) = Self::first_duplicate_element(self.module.function_defs().iter().map(|x| x.function)) 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 de4446e29c502..84abbb3306032 100644 --- a/third_party/move/move-bytecode-verifier/src/instruction_consistency.rs +++ b/third_party/move/move-bytecode-verifier/src/instruction_consistency.rs @@ -12,7 +12,8 @@ use move_binary_format::{ errors::{Location, PartialVMError, PartialVMResult, VMResult}, file_format::{ Bytecode, CodeOffset, CodeUnit, CompiledModule, CompiledScript, FieldHandleIndex, - FunctionDefinitionIndex, FunctionHandleIndex, StructDefinitionIndex, TableIndex, + FunctionDefinitionIndex, FunctionHandleIndex, StructDefinitionIndex, + StructVariantHandleIndex, TableIndex, VariantFieldHandleIndex, }, }; use move_core_types::vm_status::StatusCode; @@ -62,19 +63,32 @@ impl<'a> InstructionConsistency<'a> { use Bytecode::*; match instr { - MutBorrowField(field_handle_index) => { + MutBorrowField(field_handle_index) | ImmBorrowField(field_handle_index) => { self.check_field_op(offset, *field_handle_index, /* generic */ false)?; }, - MutBorrowFieldGeneric(field_inst_index) => { + MutBorrowFieldGeneric(field_inst_index) + | ImmBorrowFieldGeneric(field_inst_index) => { let field_inst = self.resolver.field_instantiation_at(*field_inst_index)?; self.check_field_op(offset, field_inst.handle, /* generic */ true)?; }, - ImmBorrowField(field_handle_index) => { - self.check_field_op(offset, *field_handle_index, /* generic */ false)?; - }, - ImmBorrowFieldGeneric(field_inst_index) => { - let field_inst = self.resolver.field_instantiation_at(*field_inst_index)?; - self.check_field_op(offset, field_inst.handle, /* non_ */ true)?; + MutBorrowVariantField(field_handle_index) + | ImmBorrowVariantField(field_handle_index) => { + self.check_variant_field_op( + offset, + *field_handle_index, + /* generic */ false, + )?; + }, + MutBorrowVariantFieldGeneric(field_inst_index) + | ImmBorrowVariantFieldGeneric(field_inst_index) => { + let field_inst = self + .resolver + .variant_field_instantiation_at(*field_inst_index)?; + self.check_variant_field_op( + offset, + field_inst.handle, + /* generic */ true, + )?; }, Call(idx) => { self.check_function_op(offset, *idx, /* generic */ false)?; @@ -83,54 +97,33 @@ impl<'a> InstructionConsistency<'a> { let func_inst = self.resolver.function_instantiation_at(*idx); self.check_function_op(offset, func_inst.handle, /* generic */ true)?; }, - Pack(idx) => { - self.check_type_op(offset, *idx, /* generic */ false)?; + Pack(idx) | Unpack(idx) => { + self.check_struct_op(offset, *idx, /* generic */ false)?; }, - PackGeneric(idx) => { + PackGeneric(idx) | UnpackGeneric(idx) => { let struct_inst = self.resolver.struct_instantiation_at(*idx)?; - self.check_type_op(offset, struct_inst.def, /* generic */ true)?; + self.check_struct_op(offset, struct_inst.def, /* generic */ true)?; }, - Unpack(idx) => { - self.check_type_op(offset, *idx, /* generic */ false)?; + PackVariant(idx) | UnpackVariant(idx) | TestVariant(idx) => { + self.check_variant_op(offset, *idx, /* generic */ false)?; }, - UnpackGeneric(idx) => { - let struct_inst = self.resolver.struct_instantiation_at(*idx)?; - self.check_type_op(offset, struct_inst.def, /* generic */ true)?; + PackVariantGeneric(idx) | UnpackVariantGeneric(idx) | TestVariantGeneric(idx) => { + let struct_inst = self.resolver.struct_variant_instantiation_at(*idx)?; + self.check_variant_op(offset, struct_inst.handle, /* generic */ true)?; }, - MutBorrowGlobal(idx) => { - self.check_type_op(offset, *idx, /* generic */ false)?; + MutBorrowGlobal(idx) | ImmBorrowGlobal(idx) => { + self.check_struct_op(offset, *idx, /* generic */ false)?; }, - MutBorrowGlobalGeneric(idx) => { + MutBorrowGlobalGeneric(idx) | ImmBorrowGlobalGeneric(idx) => { let struct_inst = self.resolver.struct_instantiation_at(*idx)?; - self.check_type_op(offset, struct_inst.def, /* generic */ true)?; + self.check_struct_op(offset, struct_inst.def, /* generic */ true)?; }, - ImmBorrowGlobal(idx) => { - self.check_type_op(offset, *idx, /* generic */ false)?; + Exists(idx) | MoveFrom(idx) | MoveTo(idx) => { + self.check_struct_op(offset, *idx, /* generic */ false)?; }, - ImmBorrowGlobalGeneric(idx) => { + ExistsGeneric(idx) | MoveFromGeneric(idx) | MoveToGeneric(idx) => { let struct_inst = self.resolver.struct_instantiation_at(*idx)?; - self.check_type_op(offset, struct_inst.def, /* generic */ true)?; - }, - Exists(idx) => { - self.check_type_op(offset, *idx, /* generic */ false)?; - }, - ExistsGeneric(idx) => { - let struct_inst = self.resolver.struct_instantiation_at(*idx)?; - self.check_type_op(offset, struct_inst.def, /* generic */ true)?; - }, - MoveFrom(idx) => { - self.check_type_op(offset, *idx, /* generic */ false)?; - }, - MoveFromGeneric(idx) => { - let struct_inst = self.resolver.struct_instantiation_at(*idx)?; - self.check_type_op(offset, struct_inst.def, /* generic */ true)?; - }, - MoveTo(idx) => { - self.check_type_op(offset, *idx, /* generic */ false)?; - }, - MoveToGeneric(idx) => { - let struct_inst = self.resolver.struct_instantiation_at(*idx)?; - self.check_type_op(offset, struct_inst.def, /* generic */ true)?; + self.check_struct_op(offset, struct_inst.def, /* generic */ true)?; }, VecPack(_, num) | VecUnpack(_, num) => { if *num > u16::MAX as u64 { @@ -167,14 +160,24 @@ impl<'a> InstructionConsistency<'a> { generic: bool, ) -> PartialVMResult<()> { let field_handle = self.resolver.field_handle_at(field_handle_index)?; - self.check_type_op(offset, field_handle.owner, generic) + self.check_struct_op(offset, field_handle.owner, generic) + } + + fn check_variant_field_op( + &self, + offset: usize, + field_handle_index: VariantFieldHandleIndex, + generic: bool, + ) -> PartialVMResult<()> { + let field_handle = self.resolver.variant_field_handle_at(field_handle_index)?; + self.check_struct_op(offset, field_handle.struct_index, generic) } fn current_function(&self) -> FunctionDefinitionIndex { self.current_function.unwrap_or(FunctionDefinitionIndex(0)) } - fn check_type_op( + fn check_struct_op( &self, offset: usize, struct_def_index: StructDefinitionIndex, @@ -191,6 +194,24 @@ impl<'a> InstructionConsistency<'a> { Ok(()) } + fn check_variant_op( + &self, + offset: usize, + idx: StructVariantHandleIndex, + generic: bool, + ) -> PartialVMResult<()> { + let variant_handle = self.resolver.struct_variant_handle_at(idx)?; + let struct_def = self.resolver.struct_def_at(variant_handle.struct_index)?; + let struct_handle = self.resolver.struct_handle_at(struct_def.struct_handle); + if struct_handle.type_parameters.is_empty() == generic { + return Err( + PartialVMError::new(StatusCode::GENERIC_MEMBER_OPCODE_MISMATCH) + .at_code_offset(self.current_function(), offset as CodeOffset), + ); + } + Ok(()) + } + fn check_function_op( &self, offset: usize, diff --git a/third_party/move/move-bytecode-verifier/src/limits.rs b/third_party/move/move-bytecode-verifier/src/limits.rs index e8019b70ff9f1..8d95b0b55aa13 100644 --- a/third_party/move/move-bytecode-verifier/src/limits.rs +++ b/third_party/move/move-bytecode-verifier/src/limits.rs @@ -9,6 +9,7 @@ use move_binary_format::{ IndexKind, }; use move_core_types::vm_status::StatusCode; +use std::cmp; pub struct LimitsVerifier<'a> { resolver: BinaryIndexedView<'a>, @@ -155,16 +156,36 @@ impl<'a> LimitsVerifier<'a> { } if let Some(max_fields_in_struct) = config.max_fields_in_struct { for def in defs { + let mut max = 0; match &def.field_information { - StructFieldInformation::Native => (), - StructFieldInformation::Declared(fields) => { - if fields.len() > max_fields_in_struct { - return Err(PartialVMError::new( - StatusCode::MAX_FIELD_DEFINITIONS_REACHED, - )); + StructFieldInformation::Native => {}, + StructFieldInformation::Declared(fields) => max += fields.len(), + StructFieldInformation::DeclaredVariants(variants) => { + // Notice we interpret the bound as a maximum of the combined + // size of fields of a given variant, not the + // sum of all fields in all variants. An upper bound for + // overall fields of a variant struct is given by + // `max_fields_in_struct * max_struct_variants` + for variant in variants { + let count = variant.fields.len(); + max = cmp::max(max, count) } }, } + if max > max_fields_in_struct { + return Err(PartialVMError::new( + StatusCode::MAX_FIELD_DEFINITIONS_REACHED, + )); + } + } + } + if let Some(max_struct_variants) = config.max_struct_variants { + for def in defs { + if matches!(&def.field_information, + StructFieldInformation::DeclaredVariants(variants) if variants.len() > max_struct_variants) + { + return Err(PartialVMError::new(StatusCode::MAX_STRUCT_VARIANTS_REACHED)); + } } } } 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 444a8c2f42414..7d27f91f54e83 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 @@ -100,6 +100,10 @@ fn execute_inner( | Bytecode::MutBorrowFieldGeneric(_) | Bytecode::ImmBorrowField(_) | Bytecode::ImmBorrowFieldGeneric(_) + | Bytecode::MutBorrowVariantField(_) + | Bytecode::MutBorrowVariantFieldGeneric(_) + | Bytecode::ImmBorrowVariantField(_) + | Bytecode::ImmBorrowVariantFieldGeneric(_) | Bytecode::LdU8(_) | Bytecode::LdU16(_) | Bytecode::LdU32(_) @@ -115,6 +119,12 @@ fn execute_inner( | Bytecode::PackGeneric(_) | Bytecode::Unpack(_) | Bytecode::UnpackGeneric(_) + | Bytecode::PackVariant(_) + | Bytecode::PackVariantGeneric(_) + | Bytecode::UnpackVariant(_) + | Bytecode::UnpackVariantGeneric(_) + | Bytecode::TestVariant(_) + | Bytecode::TestVariantGeneric(_) | 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 2e31fb5bcb509..11d368ac9d6e7 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 @@ -11,10 +11,11 @@ use move_binary_format::{ binary_views::FunctionView, errors::{PartialVMError, PartialVMResult}, file_format::{ - CodeOffset, FieldHandleIndex, FunctionDefinitionIndex, LocalIndex, Signature, - SignatureToken, StructDefinitionIndex, + CodeOffset, FunctionDefinitionIndex, LocalIndex, Signature, SignatureToken, + StructDefinitionIndex, }, safe_unwrap, + views::FieldOrVariantIndex, }; use move_borrow_graph::references::RefID; use move_core_types::vm_status::StatusCode; @@ -58,7 +59,7 @@ impl AbstractValue { enum Label { Local(LocalIndex), Global(StructDefinitionIndex), - Field(FieldHandleIndex), + Field(FieldOrVariantIndex), } // Needed for debugging with the borrow graph @@ -67,7 +68,10 @@ impl std::fmt::Display for Label { match self { Label::Local(i) => write!(f, "local#{}", i), Label::Global(i) => write!(f, "resource@{}", i), - Label::Field(i) => write!(f, "field#{}", i), + Label::Field(FieldOrVariantIndex::FieldIndex(i)) => write!(f, "field#{}", i), + Label::Field(FieldOrVariantIndex::VariantFieldIndex(i)) => { + write!(f, "variant_field#{}", i) + }, } } } @@ -172,7 +176,7 @@ impl AbstractState { self.borrow_graph.add_weak_borrow((), parent, child) } - fn add_field_borrow(&mut self, parent: RefID, field: FieldHandleIndex, child: RefID) { + fn add_field_borrow(&mut self, parent: RefID, field: FieldOrVariantIndex, child: RefID) { self.borrow_graph .add_strong_field_borrow((), parent, Label::Field(field), child) } @@ -248,7 +252,7 @@ impl AbstractState { /// checks if `id` is freezable /// - Mutable references are freezable if there are no consistent mutable borrows /// - Immutable references are not freezable by the typing rules - fn is_freezable(&self, id: RefID, at_field_opt: Option) -> bool { + fn is_freezable(&self, id: RefID, at_field_opt: Option) -> bool { assert!(self.borrow_graph.is_mutable(id)); !self.has_consistent_mutable_borrows(id, at_field_opt.map(Label::Field)) } @@ -256,7 +260,7 @@ impl AbstractState { /// checks if `id` is readable /// - Mutable references are readable if they are freezable /// - Immutable references are always readable - fn is_readable(&self, id: RefID, at_field_opt: Option) -> bool { + fn is_readable(&self, id: RefID, at_field_opt: Option) -> bool { let is_mutable = self.borrow_graph.is_mutable(id); !is_mutable || self.is_freezable(id, at_field_opt) } @@ -433,7 +437,7 @@ impl AbstractState { offset: CodeOffset, mut_: bool, id: RefID, - field: FieldHandleIndex, + field: FieldOrVariantIndex, ) -> PartialVMResult { // Any field borrows will be factored out, so don't check in the mutable case let is_mut_borrow_with_full_borrows = || mut_ && self.has_full_borrows(id); 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 90a89194b6b2d..2d70d91ae314f 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 @@ -23,9 +23,10 @@ use move_binary_format::{ errors::{PartialVMError, PartialVMResult}, file_format::{ Bytecode, CodeOffset, FunctionDefinitionIndex, FunctionHandle, IdentifierIndex, - SignatureIndex, SignatureToken, StructDefinition, StructFieldInformation, + SignatureIndex, SignatureToken, StructDefinition, StructVariantHandle, VariantIndex, }, safe_assert, safe_unwrap, + views::FieldOrVariantIndex, }; use move_core_types::vm_status::StatusCode; use std::collections::{BTreeSet, HashMap}; @@ -100,10 +101,11 @@ fn call( } fn num_fields(struct_def: &StructDefinition) -> usize { - match &struct_def.field_information { - StructFieldInformation::Native => 0, - StructFieldInformation::Declared(fields) => fields.len(), - } + struct_def.field_information.field_count(None) +} + +fn num_fields_variant(struct_def: &StructDefinition, variant: VariantIndex) -> usize { + struct_def.field_information.field_count(Some(variant)) } fn pack( @@ -130,6 +132,47 @@ fn unpack( Ok(()) } +fn pack_variant( + verifier: &mut ReferenceSafetyAnalysis, + struct_variant_handle: &StructVariantHandle, +) -> PartialVMResult<()> { + let struct_def = verifier + .resolver + .struct_def_at(struct_variant_handle.struct_index)?; + for _ in 0..num_fields_variant(struct_def, struct_variant_handle.variant) { + safe_assert!(safe_unwrap!(verifier.stack.pop()).is_value()) + } + verifier.stack.push(AbstractValue::NonReference); + Ok(()) +} + +fn unpack_variant( + verifier: &mut ReferenceSafetyAnalysis, + struct_variant_handle: &StructVariantHandle, +) -> PartialVMResult<()> { + let struct_def = verifier + .resolver + .struct_def_at(struct_variant_handle.struct_index)?; + safe_assert!(safe_unwrap!(verifier.stack.pop()).is_value()); + for _ in 0..num_fields_variant(struct_def, struct_variant_handle.variant) { + verifier.stack.push(AbstractValue::NonReference) + } + Ok(()) +} + +fn test_variant( + verifier: &mut ReferenceSafetyAnalysis, + state: &mut AbstractState, + _struct_variant_handle: &StructVariantHandle, + offset: CodeOffset, +) -> PartialVMResult<()> { + let id = safe_unwrap!(safe_unwrap!(verifier.stack.pop()).ref_id()); + // Testing a variant behaves like a read operation on the reference + let value = state.read_ref(offset, id)?; + verifier.stack.push(value); + Ok(()) +} + fn vec_element_type( verifier: &mut ReferenceSafetyAnalysis, idx: SignatureIndex, @@ -205,7 +248,12 @@ fn execute_inner( }, Bytecode::MutBorrowField(field_handle_index) => { let id = safe_unwrap!(safe_unwrap!(verifier.stack.pop()).ref_id()); - let value = state.borrow_field(offset, true, id, *field_handle_index)?; + let value = state.borrow_field( + offset, + true, + id, + FieldOrVariantIndex::FieldIndex(*field_handle_index), + )?; verifier.stack.push(value) }, Bytecode::MutBorrowFieldGeneric(field_inst_index) => { @@ -213,12 +261,22 @@ fn execute_inner( .resolver .field_instantiation_at(*field_inst_index)?; let id = safe_unwrap!(safe_unwrap!(verifier.stack.pop()).ref_id()); - let value = state.borrow_field(offset, true, id, field_inst.handle)?; + let value = state.borrow_field( + offset, + true, + id, + FieldOrVariantIndex::FieldIndex(field_inst.handle), + )?; verifier.stack.push(value) }, Bytecode::ImmBorrowField(field_handle_index) => { let id = safe_unwrap!(safe_unwrap!(verifier.stack.pop()).ref_id()); - let value = state.borrow_field(offset, false, id, *field_handle_index)?; + let value = state.borrow_field( + offset, + false, + id, + FieldOrVariantIndex::FieldIndex(*field_handle_index), + )?; verifier.stack.push(value) }, Bytecode::ImmBorrowFieldGeneric(field_inst_index) => { @@ -226,10 +284,60 @@ fn execute_inner( .resolver .field_instantiation_at(*field_inst_index)?; let id = safe_unwrap!(safe_unwrap!(verifier.stack.pop()).ref_id()); - let value = state.borrow_field(offset, false, id, field_inst.handle)?; + let value = state.borrow_field( + offset, + false, + id, + FieldOrVariantIndex::FieldIndex(field_inst.handle), + )?; + verifier.stack.push(value) + }, + Bytecode::MutBorrowVariantField(field_handle_index) => { + let id = safe_unwrap!(safe_unwrap!(verifier.stack.pop()).ref_id()); + let value = state.borrow_field( + offset, + true, + id, + FieldOrVariantIndex::VariantFieldIndex(*field_handle_index), + )?; + verifier.stack.push(value) + }, + Bytecode::MutBorrowVariantFieldGeneric(field_inst_index) => { + let field_inst = verifier + .resolver + .variant_field_instantiation_at(*field_inst_index)?; + let id = safe_unwrap!(safe_unwrap!(verifier.stack.pop()).ref_id()); + let value = state.borrow_field( + offset, + true, + id, + FieldOrVariantIndex::VariantFieldIndex(field_inst.handle), + )?; + verifier.stack.push(value) + }, + Bytecode::ImmBorrowVariantField(field_handle_index) => { + let id = safe_unwrap!(safe_unwrap!(verifier.stack.pop()).ref_id()); + let value = state.borrow_field( + offset, + false, + id, + FieldOrVariantIndex::VariantFieldIndex(*field_handle_index), + )?; + verifier.stack.push(value) + }, + Bytecode::ImmBorrowVariantFieldGeneric(field_inst_index) => { + let field_inst = verifier + .resolver + .variant_field_instantiation_at(*field_inst_index)?; + let id = safe_unwrap!(safe_unwrap!(verifier.stack.pop()).ref_id()); + let value = state.borrow_field( + offset, + false, + id, + FieldOrVariantIndex::VariantFieldIndex(field_inst.handle), + )?; verifier.stack.push(value) }, - Bytecode::MutBorrowGlobal(idx) => { safe_assert!(safe_unwrap!(verifier.stack.pop()).is_value()); let value = state.borrow_global(offset, true, *idx)?; @@ -361,6 +469,34 @@ fn execute_inner( unpack(verifier, struct_def)? }, + Bytecode::TestVariant(idx) => { + let handle = verifier.resolver.struct_variant_handle_at(*idx)?; + test_variant(verifier, state, handle, offset)? + }, + Bytecode::TestVariantGeneric(idx) => { + let inst = verifier.resolver.struct_variant_instantiation_at(*idx)?; + let handle = verifier.resolver.struct_variant_handle_at(inst.handle)?; + test_variant(verifier, state, handle, offset)? + }, + Bytecode::PackVariant(idx) => { + let handle = verifier.resolver.struct_variant_handle_at(*idx)?; + pack_variant(verifier, handle)? + }, + Bytecode::PackVariantGeneric(idx) => { + let inst = verifier.resolver.struct_variant_instantiation_at(*idx)?; + let handle = verifier.resolver.struct_variant_handle_at(inst.handle)?; + pack_variant(verifier, handle)? + }, + Bytecode::UnpackVariant(idx) => { + let handle = verifier.resolver.struct_variant_handle_at(*idx)?; + unpack_variant(verifier, handle)? + }, + Bytecode::UnpackVariantGeneric(idx) => { + let inst = verifier.resolver.struct_variant_instantiation_at(*idx)?; + let handle = verifier.resolver.struct_variant_handle_at(inst.handle)?; + unpack_variant(verifier, handle)? + }, + Bytecode::VecPack(idx, num) => { for _ in 0..*num { safe_assert!(safe_unwrap!(verifier.stack.pop()).is_value()) diff --git a/third_party/move/move-bytecode-verifier/src/regression_tests/reference_analysis.rs b/third_party/move/move-bytecode-verifier/src/regression_tests/reference_analysis.rs index bdefebf1544ea..f2ac68a054591 100644 --- a/third_party/move/move-bytecode-verifier/src/regression_tests/reference_analysis.rs +++ b/third_party/move/move-bytecode-verifier/src/regression_tests/reference_analysis.rs @@ -155,6 +155,10 @@ fn too_many_locals() { code: vec![CopyLoc(2), StLoc(33), Branch(0)], }), }], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; let res = crate::verify_module(&module); @@ -207,6 +211,10 @@ fn borrow_graph() { code: vec![MoveLoc(0), MoveLoc(1), StLoc(0), StLoc(1), Branch(0)], }), }], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; let res = crate::verify_module(&module); @@ -312,6 +320,10 @@ fn indirect_code() { code, }), }], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; let res = crate::verify_module_with_config(&VerifierConfig::unbounded(), &module).unwrap_err(); diff --git a/third_party/move/move-bytecode-verifier/src/signature.rs b/third_party/move/move-bytecode-verifier/src/signature.rs index 12cec4ceccf92..fdd845ffc871a 100644 --- a/third_party/move/move-bytecode-verifier/src/signature.rs +++ b/third_party/move/move-bytecode-verifier/src/signature.rs @@ -10,9 +10,9 @@ use move_binary_format::{ binary_views::BinaryIndexedView, errors::{Location, PartialVMError, PartialVMResult, VMResult}, file_format::{ - AbilitySet, Bytecode, CodeUnit, CompiledModule, CompiledScript, FunctionDefinition, - FunctionHandle, Signature, SignatureIndex, SignatureToken, StructDefinition, - StructFieldInformation, StructTypeParameter, TableIndex, + AbilitySet, Bytecode, CodeUnit, CompiledModule, CompiledScript, FieldDefinition, + FunctionDefinition, FunctionHandle, Signature, SignatureIndex, SignatureToken, + StructDefinition, StructFieldInformation, StructTypeParameter, TableIndex, }, file_format_common::VERSION_6, IndexKind, @@ -84,30 +84,60 @@ impl<'a> SignatureChecker<'a> { fn verify_fields(&self, struct_defs: &[StructDefinition]) -> PartialVMResult<()> { for (struct_def_idx, struct_def) in struct_defs.iter().enumerate() { - let fields = match &struct_def.field_information { - StructFieldInformation::Native => continue, - StructFieldInformation::Declared(fields) => fields, - }; - let struct_handle = self.resolver.struct_handle_at(struct_def.struct_handle); - let err_handler = |err: PartialVMError, idx| { - err.at_index(IndexKind::FieldDefinition, idx as TableIndex) - .at_index(IndexKind::StructDefinition, struct_def_idx as TableIndex) + match &struct_def.field_information { + StructFieldInformation::Native => {}, + StructFieldInformation::Declared(fields) => { + self.verify_fields_of_struct(struct_def_idx, struct_def, None, fields.iter())? + }, + StructFieldInformation::DeclaredVariants(variants) => { + variants + .iter() + .enumerate() + .try_for_each(|(variant_offset, variant)| { + self.verify_fields_of_struct( + struct_def_idx, + struct_def, + Some(variant_offset), + variant.fields.iter(), + ) + })? + }, }; - for (field_offset, field_def) in fields.iter().enumerate() { - self.check_signature_token(&field_def.signature.0) - .map_err(|err| err_handler(err, field_offset))?; - let type_param_constraints: Vec<_> = - struct_handle.type_param_constraints().collect(); - self.check_type_instantiation(&field_def.signature.0, &type_param_constraints) - .map_err(|err| err_handler(err, field_offset))?; + } + Ok(()) + } - self.check_phantom_params( - &field_def.signature.0, - false, - &struct_handle.type_parameters, - ) - .map_err(|err| err_handler(err, field_offset))?; + fn verify_fields_of_struct<'l>( + &self, + struct_def_idx: usize, + struct_def: &StructDefinition, + variant: Option, + fields: impl Iterator, + ) -> Result<(), PartialVMError> { + let struct_handle = self.resolver.struct_handle_at(struct_def.struct_handle); + let err_handler = |err: PartialVMError, offset| { + let err = err + .at_index(IndexKind::FieldDefinition, offset as TableIndex) + .at_index(IndexKind::StructDefinition, struct_def_idx as TableIndex); + if let Some(variant) = variant { + err.at_index(IndexKind::VariantDefinition, variant as TableIndex) + } else { + err } + }; + for (field_offset, field_def) in fields.enumerate() { + self.check_signature_token(&field_def.signature.0) + .map_err(|err| err_handler(err, field_offset))?; + let type_param_constraints: Vec<_> = struct_handle.type_param_constraints().collect(); + self.check_type_instantiation(&field_def.signature.0, &type_param_constraints) + .map_err(|err| err_handler(err, field_offset))?; + + self.check_phantom_params( + &field_def.signature.0, + false, + &struct_handle.type_parameters, + ) + .map_err(|err| err_handler(err, field_offset))?; } Ok(()) } @@ -166,6 +196,22 @@ impl<'a> SignatureChecker<'a> { type_parameters, ) }, + PackVariantGeneric(idx) | UnpackVariantGeneric(idx) | TestVariantGeneric(idx) => { + let variant_inst = self.resolver.struct_variant_instantiation_at(*idx)?; + let variant_handle = self + .resolver + .struct_variant_handle_at(variant_inst.handle)?; + let struct_def = self.resolver.struct_def_at(variant_handle.struct_index)?; + let struct_handle = self.resolver.struct_handle_at(struct_def.struct_handle); + let type_arguments = + &self.resolver.signature_at(variant_inst.type_parameters).0; + self.check_signature_tokens(type_arguments)?; + self.check_generic_instance( + type_arguments, + struct_handle.type_param_constraints(), + type_parameters, + ) + }, ImmBorrowFieldGeneric(idx) | MutBorrowFieldGeneric(idx) => { let field_inst = self.resolver.field_instantiation_at(*idx)?; let field_handle = self.resolver.field_handle_at(field_inst.handle)?; @@ -179,6 +225,19 @@ impl<'a> SignatureChecker<'a> { type_parameters, ) }, + ImmBorrowVariantFieldGeneric(idx) | MutBorrowVariantFieldGeneric(idx) => { + let field_inst = self.resolver.variant_field_instantiation_at(*idx)?; + let field_handle = self.resolver.variant_field_handle_at(field_inst.handle)?; + let struct_def = self.resolver.struct_def_at(field_handle.struct_index)?; + let struct_handle = self.resolver.struct_handle_at(struct_def.struct_handle); + let type_arguments = &self.resolver.signature_at(field_inst.type_parameters).0; + self.check_signature_tokens(type_arguments)?; + self.check_generic_instance( + type_arguments, + struct_handle.type_param_constraints(), + type_parameters, + ) + }, VecPack(idx, _) | VecLen(idx) | VecImmBorrow(idx) @@ -202,14 +261,70 @@ 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(_) + | PackVariant(_) + | UnpackVariant(_) + | TestVariant(_) + | 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(_) + | MutBorrowVariantField(_) + | ImmBorrowVariantField(_) + | MutBorrowGlobal(_) + | ImmBorrowGlobal(_) + | Exists(_) + | MoveTo(_) + | MoveFrom(_) + | Abort + | Nop => Ok(()), }; result.map_err(|err| { err.append_message_with_separator(' ', format!("at offset {} ", offset)) 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 48283b993c382..e618353a0725f 100644 --- a/third_party/move/move-bytecode-verifier/src/signature_v2.rs +++ b/third_party/move/move-bytecode-verifier/src/signature_v2.rs @@ -6,11 +6,12 @@ use move_binary_format::{ binary_views::BinaryIndexedView, errors::{Location, PartialVMError, PartialVMResult, VMResult}, file_format::{ - Ability, AbilitySet, Bytecode, CodeUnit, CompiledModule, CompiledScript, + Ability, AbilitySet, Bytecode, CodeUnit, CompiledModule, CompiledScript, FieldDefinition, FieldInstantiationIndex, FunctionDefinition, FunctionHandle, FunctionHandleIndex, FunctionInstantiationIndex, SignatureIndex, SignatureToken, StructDefInstantiationIndex, - StructDefinition, StructFieldInformation, StructHandle, StructTypeParameter, - TypeParameterIndex, + StructDefinition, StructDefinitionIndex, StructFieldInformation, StructHandle, + StructTypeParameter, StructVariantInstantiationIndex, TypeParameterIndex, + VariantFieldInstantiationIndex, }, IndexKind, }; @@ -318,6 +319,14 @@ struct SignatureChecker<'a, const N: usize> { >, field_inst_results: RefCell>>, + variant_field_inst_results: + RefCell>>, + struct_variant_inst_results: RefCell< + BTreeMap< + (StructVariantInstantiationIndex, AbilitySet), + &'a BitsetTypeParameterConstraints, + >, + >, } impl<'a, const N: usize> SignatureChecker<'a, N> { @@ -334,6 +343,8 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { func_inst_results: RefCell::new(BTreeMap::new()), struct_inst_results: RefCell::new(BTreeMap::new()), field_inst_results: RefCell::new(BTreeMap::new()), + variant_field_inst_results: RefCell::new(BTreeMap::new()), + struct_variant_inst_results: RefCell::new(BTreeMap::new()), } } @@ -507,16 +518,8 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { Ok(()) } - /// Checks if a struct instantiation is well-formed, in a context-less fashion. - /// - /// A struct instantiation is well-formed if - /// - There are no references in the type arguments - /// - All type arguments are well-formed and have declared abilities - /// - /// Returns the minimal set of constraints the type parameters need to satisfy, with the result - /// being cached. - /// - /// Time complexity: `O(total_size_of_all_type_args)` if not cached. + /// Checks if a struct instantiation is well-formed, in a context-less fashion, + /// with the result being cached. fn verify_struct_instantiation_contextless( &self, struct_inst_idx: StructDefInstantiationIndex, @@ -530,56 +533,54 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { btree_map::Entry::Occupied(entry) => *entry.into_mut(), btree_map::Entry::Vacant(entry) => { let struct_inst = self.resolver.struct_instantiation_at(struct_inst_idx)?; - let struct_def = self.resolver.struct_def_at(struct_inst.def)?; - let struct_handle = self.resolver.struct_handle_at(struct_def.struct_handle); - let ty_args_idx = struct_inst.type_parameters; - let ty_args = &self.resolver.signature_at(ty_args_idx).0; - - // TODO: is this needed? - if struct_handle.type_parameters.len() != ty_args.len() { - return Err( - PartialVMError::new(StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH) - .with_message(format!( - "expected {} type argument(s), got {}", - struct_handle.type_parameters.len(), - ty_args.len() - )) - .at_index(IndexKind::StructDefInstantiation, struct_inst_idx.0), - ); - } - - if !required_abilities.is_subset(struct_handle.abilities) { - return Err(PartialVMError::new(StatusCode::CONSTRAINT_NOT_SATISFIED) - .with_message(format!( - "expected struct with abilities {:?} got {:?}", - required_abilities, struct_handle.abilities - )) - .at_index(IndexKind::StructDefInstantiation, struct_inst_idx.0)); - } - - let mut constraints = BitsetTypeParameterConstraints::new(); - for (ty_idx, ty) in ty_args.iter().enumerate() { - if ty.is_reference() { - return Err(PartialVMError::new(StatusCode::INVALID_SIGNATURE_TOKEN) - .with_message("reference not allowed".to_string()) - .at_index(IndexKind::StructDefInstantiation, struct_inst_idx.0)); - } - - let arg_abilities = if struct_handle.type_parameters[ty_idx].is_phantom { - struct_handle.type_parameters[ty_idx].constraints - } else { - struct_handle.type_parameters[ty_idx] - .constraints - .union(required_abilities.requires()) - }; + let constraints = self + .verify_struct_type_params( + required_abilities, + struct_inst.def, + struct_inst.type_parameters, + ) + .map_err(|err| { + err.at_index(IndexKind::StructDefInstantiation, struct_inst_idx.0) + })?; + *entry.insert(self.constraints.alloc(constraints)) + }, + }; - constraints.merge(self.verify_type_in_signature_contextless( - ty_args_idx, - ty_idx, - arg_abilities, - )?); - } + Ok(r) + } + /// Checks if a struct variant instantiation is well-formed, in a context-less fashion, + /// with the result being cached. + fn verify_struct_variant_instantiation_contextless( + &self, + struct_variant_inst_idx: StructVariantInstantiationIndex, + required_abilities: AbilitySet, + ) -> PartialVMResult<&'a BitsetTypeParameterConstraints> { + let r = match self + .struct_variant_inst_results + .borrow_mut() + .entry((struct_variant_inst_idx, required_abilities)) + { + btree_map::Entry::Occupied(entry) => *entry.into_mut(), + btree_map::Entry::Vacant(entry) => { + let struct_variant_inst = self + .resolver + .struct_variant_instantiation_at(struct_variant_inst_idx)?; + let struct_variant_handle = self + .resolver + .struct_variant_handle_at(struct_variant_inst.handle)?; + let constraints = self + .verify_struct_type_params( + required_abilities, + struct_variant_handle.struct_index, + struct_variant_inst.type_parameters, + ) + .map_err(|err| { + err.at_index( + IndexKind::StructVariantInstantiation, + struct_variant_inst_idx.0, + ) + })?; *entry.insert(self.constraints.alloc(constraints)) }, }; @@ -587,6 +588,70 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { Ok(r) } + /// Checks if a struct instantiation is well-formed, in a context-less fashion. + /// + /// A struct instantiation is well-formed if + /// - There are no references in the type arguments + /// - All type arguments are well-formed and have declared abilities + /// + /// Returns the minimal set of constraints the type parameters need to satisfy. + /// + /// Time complexity: `O(total_size_of_all_type_args)` if not cached. + fn verify_struct_type_params( + &self, + required_abilities: AbilitySet, + struct_def_idx: StructDefinitionIndex, + ty_args_idx: SignatureIndex, + ) -> PartialVMResult> { + let struct_def = self.resolver.struct_def_at(struct_def_idx)?; + let struct_handle = self.resolver.struct_handle_at(struct_def.struct_handle); + let ty_args = &self.resolver.signature_at(ty_args_idx).0; + + if struct_handle.type_parameters.len() != ty_args.len() { + return Err( + PartialVMError::new(StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH).with_message( + format!( + "expected {} type argument(s), got {}", + struct_handle.type_parameters.len(), + ty_args.len() + ), + ), + ); + } + + if !required_abilities.is_subset(struct_handle.abilities) { + return Err( + PartialVMError::new(StatusCode::CONSTRAINT_NOT_SATISFIED).with_message(format!( + "expected struct with abilities {:?} got {:?}", + required_abilities, struct_handle.abilities + )), + ); + } + + let mut constraints = BitsetTypeParameterConstraints::new(); + for (ty_idx, ty) in ty_args.iter().enumerate() { + if ty.is_reference() { + return Err(PartialVMError::new(StatusCode::INVALID_SIGNATURE_TOKEN) + .with_message("reference not allowed".to_string())); + } + + let arg_abilities = if struct_handle.type_parameters[ty_idx].is_phantom { + struct_handle.type_parameters[ty_idx].constraints + } else { + struct_handle.type_parameters[ty_idx] + .constraints + .union(required_abilities.requires()) + }; + + constraints.merge(self.verify_type_in_signature_contextless( + ty_args_idx, + ty_idx, + arg_abilities, + )?); + } + Ok(constraints) + } + /// Checks if all struct instantiations are well-formed, in a context-less fashion. /// /// Time complexity: `O(total_size_of_all_type_args_in_all_instantiations)` @@ -600,16 +665,21 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { Ok(()) } - /// Checks if a field instantiation is well-formed, in a context-less fashion. - /// - /// A field instantiation is well-formed if - /// - There are no references in the type arguments - /// - All type arguments are well-formed and have declared abilities - /// - /// Returns the minimal set of constraints the type parameters need to satisfy, with the result - /// being cached. + /// Checks if all struct variant instantiations are well-formed, in a context-less fashion. /// - /// Time complexity: `O(total_size_of_all_type_args)` if not cached. + /// Time complexity: `O(total_size_of_all_type_args_in_all_instantiations)` + fn verify_struct_variant_instantiations_contextless(&self) -> PartialVMResult<()> { + for struct_inst_idx in 0..self.resolver.struct_variant_instantiations().unwrap().len() { + self.verify_struct_variant_instantiation_contextless( + StructVariantInstantiationIndex(struct_inst_idx as u16), + AbilitySet::EMPTY, + )?; + } + Ok(()) + } + + /// Checks if a field instantiation is well-formed, in a context-less fashion, + /// with the result being cached. fn verify_field_instantiation_contextless( &self, field_inst_idx: FieldInstantiationIndex, @@ -619,39 +689,46 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { btree_map::Entry::Vacant(entry) => { let field_inst = self.resolver.field_instantiation_at(field_inst_idx)?; let field_handle = self.resolver.field_handle_at(field_inst.handle)?; - let struct_def = self.resolver.struct_def_at(field_handle.owner)?; - let struct_handle = self.resolver.struct_handle_at(struct_def.struct_handle); - let ty_args_idx = field_inst.type_parameters; - let ty_args = &self.resolver.signature_at(ty_args_idx).0; - - // TODO: is this needed? - if struct_handle.type_parameters.len() != ty_args.len() { - return Err( - PartialVMError::new(StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH) - .with_message(format!( - "expected {} type argument(s), got {}", - struct_handle.type_parameters.len(), - ty_args.len() - )) - .at_index(IndexKind::FieldInstantiation, field_inst_idx.0), - ); - } - - let mut constraints = BitsetTypeParameterConstraints::new(); - for (ty_idx, ty) in ty_args.iter().enumerate() { - if ty.is_reference() { - return Err(PartialVMError::new(StatusCode::INVALID_SIGNATURE_TOKEN) - .with_message("reference not allowed".to_string()) - .at_index(IndexKind::FieldInstantiation, field_inst_idx.0)); - } - - constraints.merge(self.verify_type_in_signature_contextless( - ty_args_idx, - ty_idx, - struct_handle.type_parameters[ty_idx].constraints, - )?); - } + let constraints = self + .verify_struct_type_params( + AbilitySet::EMPTY, + field_handle.owner, + field_inst.type_parameters, + ) + .map_err(|err| err.at_index(IndexKind::FieldInstantiation, field_inst_idx.0))?; + *entry.insert(self.constraints.alloc(constraints)) + }, + }; + Ok(r) + } + /// Same like `verify_field_instantiation_contextless` but for variant fields. + fn verify_variant_field_instantiation_contextless( + &self, + field_inst_idx: VariantFieldInstantiationIndex, + ) -> PartialVMResult<&'a BitsetTypeParameterConstraints> { + let r = match self + .variant_field_inst_results + .borrow_mut() + .entry(field_inst_idx) + { + btree_map::Entry::Occupied(entry) => *entry.into_mut(), + btree_map::Entry::Vacant(entry) => { + let variant_field_inst = self + .resolver + .variant_field_instantiation_at(field_inst_idx)?; + let field_handle = self + .resolver + .variant_field_handle_at(variant_field_inst.handle)?; + let constraints = self + .verify_struct_type_params( + AbilitySet::EMPTY, + field_handle.struct_index, + variant_field_inst.type_parameters, + ) + .map_err(|err| { + err.at_index(IndexKind::VariantFieldInstantiation, field_inst_idx.0) + })?; *entry.insert(self.constraints.alloc(constraints)) }, }; @@ -670,6 +747,18 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { Ok(()) } + /// Checks if all variant field instantiations are well-formed, in a context-less fashion. + /// + /// Time complexity: `O(total_size_of_all_type_args_in_all_instantiations)` + fn verify_variant_field_instantiations_contextless(&self) -> PartialVMResult<()> { + for field_inst_idx in 0..self.resolver.variant_field_instantiations().unwrap().len() { + self.verify_variant_field_instantiation_contextless(VariantFieldInstantiationIndex( + field_inst_idx as u16, + ))?; + } + Ok(()) + } + /// Checks if a function handle is well-formed. /// /// A function handle is well-formed if all parameter and return types are well-formed, with references @@ -716,6 +805,9 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { BTreeMap::::new(); let mut checked_vec_insts = BTreeMap::::new(); let mut checked_field_insts = BTreeMap::::new(); + let mut checked_variant_field_insts = BTreeMap::::new(); + let mut checked_struct_variant_insts = + BTreeMap::::new(); for (offset, instr) in code.code.iter().enumerate() { let map_err = |res: PartialVMResult<()>| { @@ -739,6 +831,18 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { entry.insert(()); } }, + PackVariantGeneric(idx) | UnpackVariantGeneric(idx) | TestVariantGeneric(idx) => { + if let btree_map::Entry::Vacant(entry) = + checked_struct_variant_insts.entry(*idx) + { + let constraints = self.verify_struct_variant_instantiation_contextless( + *idx, + AbilitySet::EMPTY, + )?; + map_err(constraints.check_in_context(&ability_context))?; + entry.insert(()); + } + }, ExistsGeneric(idx) | MoveFromGeneric(idx) | MoveToGeneric(idx) @@ -762,6 +866,15 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { entry.insert(()); } }, + ImmBorrowVariantFieldGeneric(idx) | MutBorrowVariantFieldGeneric(idx) => { + if let btree_map::Entry::Vacant(entry) = checked_variant_field_insts.entry(*idx) + { + let constraints = + self.verify_variant_field_instantiation_contextless(*idx)?; + map_err(constraints.check_in_context(&ability_context))?; + entry.insert(()); + } + }, VecPack(idx, _) | VecLen(idx) | VecImmBorrow(idx) @@ -796,14 +909,70 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { // 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 => (), + Pop + | Ret + | Branch(_) + | BrTrue(_) + | BrFalse(_) + | LdU8(_) + | LdU16(_) + | LdU32(_) + | LdU64(_) + | LdU128(_) + | LdU256(_) + | LdConst(_) + | CastU8 + | CastU16 + | CastU32 + | CastU64 + | CastU128 + | CastU256 + | LdTrue + | LdFalse + | Call(_) + | Pack(_) + | Unpack(_) + | TestVariant(_) + | PackVariant(_) + | UnpackVariant(_) + | 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(_) + | MutBorrowVariantField(_) + | ImmBorrowVariantField(_) + | MutBorrowGlobal(_) + | ImmBorrowGlobal(_) + | Exists(_) + | MoveTo(_) + | MoveFrom(_) + | Abort + | Nop => (), } } @@ -839,10 +1008,6 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { /// /// Time complexity: `O(total_size_of_field_types)` fn verify_struct_def(&self, struct_def: &StructDefinition) -> PartialVMResult<()> { - let fields = match &struct_def.field_information { - StructFieldInformation::Native => return Ok(()), - StructFieldInformation::Declared(fields) => fields, - }; let struct_handle = self.resolver.struct_handle_at(struct_def.struct_handle); let context = struct_handle .type_param_constraints() @@ -855,13 +1020,40 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { .map(|idx| (idx as TypeParameterIndex, AbilitySet::ALL)) .collect::>(); - for field_def in fields.iter() { + match &struct_def.field_information { + StructFieldInformation::Native => Ok(()), + StructFieldInformation::Declared(fields) => self.verify_fields_of_struct( + &struct_handle, + &context, + required_abilities_conditional, + &context_all_abilities, + fields.iter(), + ), + StructFieldInformation::DeclaredVariants(variants) => self.verify_fields_of_struct( + &struct_handle, + &context, + required_abilities_conditional, + &context_all_abilities, + variants.iter().flat_map(|v| v.fields.iter()), + ), + } + } + + fn verify_fields_of_struct<'l>( + &self, + struct_handle: &&StructHandle, + context: &BitsetTypeParameterConstraints<{ N }>, + required_abilities_conditional: AbilitySet, + context_all_abilities: &BitsetTypeParameterConstraints<{ N }>, + fields: impl Iterator, + ) -> Result<(), PartialVMError> { + for field_def in fields { let field_ty = &field_def.signature.0; // Check if the field type itself is well-formed. check_ty_in_context( self.resolver.struct_handles(), - &context, + context, field_ty, false, AbilitySet::EMPTY, @@ -870,7 +1062,7 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { // Check if the field type satisfies the conditional ability requirements. check_ty_in_context( self.resolver.struct_handles(), - &context_all_abilities, + context_all_abilities, field_ty, false, required_abilities_conditional, @@ -884,7 +1076,6 @@ impl<'a, const N: usize> SignatureChecker<'a, N> { field_ty, )?; } - Ok(()) } @@ -908,6 +1099,8 @@ fn verify_module_impl(module: &CompiledModule) -> PartialVMResul checker.verify_function_instantiations_contextless()?; checker.verify_struct_instantiations_contextless()?; checker.verify_field_instantiations_contextless()?; + checker.verify_struct_variant_instantiations_contextless()?; + checker.verify_variant_field_instantiations_contextless()?; checker.verify_function_handles()?; checker.verify_function_defs()?; 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 1bd8a7d55a425..7e43a64fe36d6 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, StructFieldInformation}, + file_format::{Bytecode, CodeUnit, FunctionDefinitionIndex, Signature}, }; use move_core_types::vm_status::StatusCode; @@ -151,6 +151,12 @@ impl<'a> StackUsageVerifier<'a> { | Bytecode::MutBorrowFieldGeneric(_) | Bytecode::ImmBorrowField(_) | Bytecode::ImmBorrowFieldGeneric(_) + | Bytecode::MutBorrowVariantField(_) + | Bytecode::MutBorrowVariantFieldGeneric(_) + | Bytecode::ImmBorrowVariantField(_) + | Bytecode::ImmBorrowVariantFieldGeneric(_) + | Bytecode::TestVariant(_) + | Bytecode::TestVariantGeneric(_) | Bytecode::MoveFrom(_) | Bytecode::MoveFromGeneric(_) | Bytecode::CastU8 @@ -225,43 +231,69 @@ impl<'a> StackUsageVerifier<'a> { // Pack performs `num_fields` pops and one push Bytecode::Pack(idx) => { let struct_definition = self.resolver.struct_def_at(*idx)?; - let field_count = match &struct_definition.field_information { - // 'Native' here is an error that will be caught by the bytecode verifier later - StructFieldInformation::Native => 0, - StructFieldInformation::Declared(fields) => fields.len(), - }; - (field_count as u64, 1) + let field_count = struct_definition.field_information.field_count(None) as u64; + (field_count, 1) }, Bytecode::PackGeneric(idx) => { let struct_inst = self.resolver.struct_instantiation_at(*idx)?; let struct_definition = self.resolver.struct_def_at(struct_inst.def)?; - let field_count = match &struct_definition.field_information { - // 'Native' here is an error that will be caught by the bytecode verifier later - StructFieldInformation::Native => 0, - StructFieldInformation::Declared(fields) => fields.len(), - }; - (field_count as u64, 1) + let field_count = struct_definition.field_information.field_count(None) as u64; + (field_count, 1) + }, + Bytecode::PackVariant(idx) => { + let variant_handle = self.resolver.struct_variant_handle_at(*idx)?; + let struct_definition = self.resolver.struct_def_at(variant_handle.struct_index)?; + let field_count = struct_definition + .field_information + .field_count(Some(variant_handle.variant)) + as u64; + (field_count, 1) + }, + Bytecode::PackVariantGeneric(idx) => { + let variant_inst = self.resolver.struct_variant_instantiation_at(*idx)?; + let variant_handle = self + .resolver + .struct_variant_handle_at(variant_inst.handle)?; + let struct_definition = self.resolver.struct_def_at(variant_handle.struct_index)?; + let field_count = struct_definition + .field_information + .field_count(Some(variant_handle.variant)) + as u64; + (field_count, 1) }, // Unpack performs one pop and `num_fields` pushes Bytecode::Unpack(idx) => { let struct_definition = self.resolver.struct_def_at(*idx)?; - let field_count = match &struct_definition.field_information { - // 'Native' here is an error that will be caught by the bytecode verifier later - StructFieldInformation::Native => 0, - StructFieldInformation::Declared(fields) => fields.len(), - }; - (1, field_count as u64) + let field_count = struct_definition.field_information.field_count(None) as u64; + (1, field_count) }, Bytecode::UnpackGeneric(idx) => { let struct_inst = self.resolver.struct_instantiation_at(*idx)?; let struct_definition = self.resolver.struct_def_at(struct_inst.def)?; - let field_count = match &struct_definition.field_information { - // 'Native' here is an error that will be caught by the bytecode verifier later - StructFieldInformation::Native => 0, - StructFieldInformation::Declared(fields) => fields.len(), - }; - (1, field_count as u64) + let field_count = struct_definition.field_information.field_count(None) as u64; + (1, field_count) + }, + Bytecode::UnpackVariant(idx) => { + let variant_handle = self.resolver.struct_variant_handle_at(*idx)?; + let struct_definition = self.resolver.struct_def_at(variant_handle.struct_index)?; + let field_count = struct_definition + .field_information + .field_count(Some(variant_handle.variant)) + as u64; + (1, field_count) + }, + Bytecode::UnpackVariantGeneric(idx) => { + let variant_inst = self.resolver.struct_variant_instantiation_at(*idx)?; + let variant_handle = self + .resolver + .struct_variant_handle_at(variant_inst.handle)?; + let struct_definition = self.resolver.struct_def_at(variant_handle.struct_index)?; + let field_count = struct_definition + .field_information + .field_count(Some(variant_handle.variant)) + as u64; + (1, field_count) }, }) } 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 69b2aee72fe0c..e50efc0cf23b0 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, FieldHandleIndex, FunctionDefinitionIndex, - FunctionHandle, LocalIndex, Signature, SignatureToken, SignatureToken as ST, - StructDefinition, StructDefinitionIndex, StructFieldInformation, StructHandleIndex, + AbilitySet, Bytecode, CodeOffset, FunctionDefinitionIndex, FunctionHandle, LocalIndex, + Signature, SignatureToken, SignatureToken as ST, StructDefinition, StructDefinitionIndex, + StructFieldInformation, StructHandleIndex, VariantIndex, }, safe_unwrap, + views::FieldOrVariantIndex, }; use move_core_types::vm_status::StatusCode; @@ -131,7 +132,7 @@ fn borrow_field( meter: &mut impl Meter, offset: CodeOffset, mut_: bool, - field_handle_index: FieldHandleIndex, + field_handle_index: FieldOrVariantIndex, type_args: &Signature, ) -> PartialVMResult<()> { // load operand and check mutability constraints @@ -143,34 +144,80 @@ fn borrow_field( // check the reference on the stack is the expected type. // Load the type that owns the field according to the instruction. // For generic fields access, this step materializes that type - let field_handle = verifier.resolver.field_handle_at(field_handle_index)?; - let struct_def = verifier.resolver.struct_def_at(field_handle.owner)?; + let (struct_def_index, variants, field_idx) = match field_handle_index { + FieldOrVariantIndex::FieldIndex(idx) => { + let field_handle = verifier.resolver.field_handle_at(idx)?; + (field_handle.owner, None, field_handle.field as usize) + }, + FieldOrVariantIndex::VariantFieldIndex(idx) => { + let field_handle = verifier.resolver.variant_field_handle_at(idx)?; + ( + field_handle.struct_index, + Some(field_handle.variants.clone()), + field_handle.field as usize, + ) + }, + }; + let struct_def = verifier.resolver.struct_def_at(struct_def_index)?; let expected_type = materialize_type(struct_def.struct_handle, type_args); match operand { ST::Reference(inner) | ST::MutableReference(inner) if expected_type == *inner => (), _ => return Err(verifier.error(StatusCode::BORROWFIELD_TYPE_MISMATCH_ERROR, offset)), } - let field_def = match &struct_def.field_information { - StructFieldInformation::Native => { - return Err(verifier.error(StatusCode::BORROWFIELD_BAD_FIELD_ERROR, offset)); - }, - StructFieldInformation::Declared(fields) => { - // TODO: review the whole error story here, way too much is left to chances... - // definition of a more proper OM for the verifier could work around the problem - // (maybe, maybe not..) - &fields[field_handle.field as usize] - }, + // Check and determine the type loaded onto the stack + let field_ty = if let Some(variants) = variants { + if variants.is_empty() { + // It is not allowed to have no variants provided here, otherwise we cannot + // determine the type. + return Err(verifier.error(StatusCode::ZERO_VARIANTS_ERROR, offset)); + } + // For all provided variants, the field type must be the same. + let mut field_ty = None; + for variant in variants { + if let Some(field_def) = struct_def + .field_information + .fields(Some(variant)) + .get(field_idx) + { + let ty = instantiate(&field_def.signature.0, type_args); + if let Some(field_ty) = &field_ty { + // More than one field possible, compare types. + if &ty != field_ty { + return Err( + verifier.error(StatusCode::BORROWFIELD_TYPE_MISMATCH_ERROR, offset) + ); + } + } else { + field_ty = Some(ty) + } + } else { + // If the struct variant has no field at this idx, this is an error + return Err(verifier.error(StatusCode::BORROWFIELD_BAD_FIELD_ERROR, offset)); + } + } + field_ty + } else { + struct_def + .field_information + .fields(None) + .get(field_idx) + .map(|field_def| instantiate(&field_def.signature.0, type_args)) }; - let field_type = Box::new(instantiate(&field_def.signature.0, type_args)); - verifier.push( - meter, - if mut_ { - ST::MutableReference(field_type) - } else { - ST::Reference(field_type) - }, - )?; + if let Some(field_ty) = field_ty { + verifier.push( + meter, + if mut_ { + ST::MutableReference(Box::new(field_ty)) + } else { + ST::Reference(Box::new(field_ty)) + }, + )?; + } else { + // If the field is not defined, we are reporting an error in `instruction_consistency`. + // Here push a dummy type to keep the abstract stack happy + verifier.push(meter, ST::Bool)?; + } Ok(()) } @@ -258,20 +305,31 @@ fn type_fields_signature( _meter: &mut impl Meter, // TODO: metering offset: CodeOffset, struct_def: &StructDefinition, + variant: Option, type_args: &Signature, ) -> PartialVMResult { - match &struct_def.field_information { - StructFieldInformation::Native => { + match (&struct_def.field_information, variant) { + (StructFieldInformation::Declared(fields), None) => Ok(Signature( + fields + .iter() + .map(|field_def| instantiate(&field_def.signature.0, type_args)) + .collect(), + )), + (StructFieldInformation::DeclaredVariants(variants), Some(variant)) + if (variant as usize) < variants.len() => + { + Ok(Signature( + variants[variant as usize] + .fields + .iter() + .map(|field_def| instantiate(&field_def.signature.0, type_args)) + .collect(), + )) + }, + _ => { // TODO: this is more of "unreachable" Err(verifier.error(StatusCode::PACK_TYPE_MISMATCH_ERROR, offset)) }, - StructFieldInformation::Declared(fields) => { - let mut field_sig = vec![]; - for field_def in fields.iter() { - field_sig.push(instantiate(&field_def.signature.0, type_args)); - } - Ok(Signature(field_sig)) - }, } } @@ -280,10 +338,11 @@ fn pack( meter: &mut impl Meter, offset: CodeOffset, struct_def: &StructDefinition, + variant: Option, type_args: &Signature, ) -> PartialVMResult<()> { let struct_type = materialize_type(struct_def.struct_handle, type_args); - let field_sig = type_fields_signature(verifier, meter, offset, struct_def, type_args)?; + let field_sig = type_fields_signature(verifier, meter, offset, struct_def, variant, type_args)?; for sig in field_sig.0.iter().rev() { let arg = safe_unwrap!(verifier.stack.pop()); if &arg != sig { @@ -300,6 +359,7 @@ fn unpack( meter: &mut impl Meter, offset: CodeOffset, struct_def: &StructDefinition, + variant: Option, type_args: &Signature, ) -> PartialVMResult<()> { let struct_type = materialize_type(struct_def.struct_handle, type_args); @@ -311,13 +371,29 @@ fn unpack( return Err(verifier.error(StatusCode::UNPACK_TYPE_MISMATCH_ERROR, offset)); } - let field_sig = type_fields_signature(verifier, meter, offset, struct_def, type_args)?; + let field_sig = type_fields_signature(verifier, meter, offset, struct_def, variant, type_args)?; for sig in field_sig.0 { verifier.push(meter, sig)? } Ok(()) } +fn test_variant( + verifier: &mut TypeSafetyChecker, + meter: &mut impl Meter, + offset: CodeOffset, + struct_def: &StructDefinition, + type_args: &Signature, +) -> PartialVMResult<()> { + let struct_type = materialize_type(struct_def.struct_handle, type_args); + let arg = safe_unwrap!(verifier.stack.pop()); + match arg { + ST::Reference(inner) | ST::MutableReference(inner) if struct_type == *inner => (), + _ => return Err(verifier.error(StatusCode::TEST_VARIANT_TYPE_MISMATCH_ERROR, offset)), + } + verifier.push(meter, ST::Bool) +} + fn exists( verifier: &mut TypeSafetyChecker, meter: &mut impl Meter, @@ -487,7 +563,7 @@ fn verify_instr( meter, offset, true, - *field_handle_index, + FieldOrVariantIndex::FieldIndex(*field_handle_index), &Signature(vec![]), )?, @@ -497,7 +573,14 @@ fn verify_instr( .field_instantiation_at(*field_inst_index)?; let type_inst = verifier.resolver.signature_at(field_inst.type_parameters); verifier.charge_tys(meter, &type_inst.0)?; - borrow_field(verifier, meter, offset, true, field_inst.handle, type_inst)? + borrow_field( + verifier, + meter, + offset, + true, + FieldOrVariantIndex::FieldIndex(field_inst.handle), + type_inst, + )? }, Bytecode::ImmBorrowField(field_handle_index) => borrow_field( @@ -505,7 +588,7 @@ fn verify_instr( meter, offset, false, - *field_handle_index, + FieldOrVariantIndex::FieldIndex(*field_handle_index), &Signature(vec![]), )?, @@ -515,7 +598,64 @@ fn verify_instr( .field_instantiation_at(*field_inst_index)?; let type_inst = verifier.resolver.signature_at(field_inst.type_parameters); verifier.charge_tys(meter, &type_inst.0)?; - borrow_field(verifier, meter, offset, false, field_inst.handle, type_inst)? + borrow_field( + verifier, + meter, + offset, + false, + FieldOrVariantIndex::FieldIndex(field_inst.handle), + type_inst, + )? + }, + + Bytecode::MutBorrowVariantField(field_handle_index) => borrow_field( + verifier, + meter, + offset, + true, + FieldOrVariantIndex::VariantFieldIndex(*field_handle_index), + &Signature(vec![]), + )?, + + Bytecode::MutBorrowVariantFieldGeneric(field_inst_index) => { + let field_inst = verifier + .resolver + .variant_field_instantiation_at(*field_inst_index)?; + let type_inst = verifier.resolver.signature_at(field_inst.type_parameters); + verifier.charge_tys(meter, &type_inst.0)?; + borrow_field( + verifier, + meter, + offset, + true, + FieldOrVariantIndex::VariantFieldIndex(field_inst.handle), + type_inst, + )? + }, + + Bytecode::ImmBorrowVariantField(field_handle_index) => borrow_field( + verifier, + meter, + offset, + false, + FieldOrVariantIndex::VariantFieldIndex(*field_handle_index), + &Signature(vec![]), + )?, + + Bytecode::ImmBorrowVariantFieldGeneric(field_inst_index) => { + let field_inst = verifier + .resolver + .variant_field_instantiation_at(*field_inst_index)?; + let type_inst = verifier.resolver.signature_at(field_inst.type_parameters); + verifier.charge_tys(meter, &type_inst.0)?; + borrow_field( + verifier, + meter, + offset, + false, + FieldOrVariantIndex::VariantFieldIndex(field_inst.handle), + type_inst, + )? }, Bytecode::LdU8(_) => { @@ -592,18 +732,17 @@ fn verify_instr( meter, offset, struct_definition, + None, &Signature(vec![]), )? }, - Bytecode::PackGeneric(idx) => { let struct_inst = verifier.resolver.struct_instantiation_at(*idx)?; let struct_def = verifier.resolver.struct_def_at(struct_inst.def)?; let type_args = verifier.resolver.signature_at(struct_inst.type_parameters); verifier.charge_tys(meter, &type_args.0)?; - pack(verifier, meter, offset, struct_def, type_args)? + pack(verifier, meter, offset, struct_def, None, type_args)? }, - Bytecode::Unpack(idx) => { let struct_definition = verifier.resolver.struct_def_at(*idx)?; unpack( @@ -611,16 +750,84 @@ fn verify_instr( meter, offset, struct_definition, + None, &Signature(vec![]), )? }, - Bytecode::UnpackGeneric(idx) => { let struct_inst = verifier.resolver.struct_instantiation_at(*idx)?; let struct_def = verifier.resolver.struct_def_at(struct_inst.def)?; let type_args = verifier.resolver.signature_at(struct_inst.type_parameters); verifier.charge_tys(meter, &type_args.0)?; - unpack(verifier, meter, offset, struct_def, type_args)? + unpack(verifier, meter, offset, struct_def, None, type_args)? + }, + + Bytecode::PackVariant(idx) => { + let handle = verifier.resolver.struct_variant_handle_at(*idx)?; + let struct_definition = verifier.resolver.struct_def_at(handle.struct_index)?; + pack( + verifier, + meter, + offset, + struct_definition, + Some(handle.variant), + &Signature(vec![]), + )? + }, + Bytecode::PackVariantGeneric(idx) => { + let inst = verifier.resolver.struct_variant_instantiation_at(*idx)?; + let handle = verifier.resolver.struct_variant_handle_at(inst.handle)?; + let struct_def = verifier.resolver.struct_def_at(handle.struct_index)?; + let type_args = verifier.resolver.signature_at(inst.type_parameters); + verifier.charge_tys(meter, &type_args.0)?; + pack( + verifier, + meter, + offset, + struct_def, + Some(handle.variant), + type_args, + )? + }, + Bytecode::UnpackVariant(idx) => { + let handle = verifier.resolver.struct_variant_handle_at(*idx)?; + let struct_definition = verifier.resolver.struct_def_at(handle.struct_index)?; + unpack( + verifier, + meter, + offset, + struct_definition, + Some(handle.variant), + &Signature(vec![]), + )? + }, + Bytecode::UnpackVariantGeneric(idx) => { + let inst = verifier.resolver.struct_variant_instantiation_at(*idx)?; + let handle = verifier.resolver.struct_variant_handle_at(inst.handle)?; + let struct_def = verifier.resolver.struct_def_at(handle.struct_index)?; + let type_args = verifier.resolver.signature_at(inst.type_parameters); + verifier.charge_tys(meter, &type_args.0)?; + unpack( + verifier, + meter, + offset, + struct_def, + Some(handle.variant), + type_args, + )? + }, + + Bytecode::TestVariant(idx) => { + let handle = verifier.resolver.struct_variant_handle_at(*idx)?; + let struct_def = verifier.resolver.struct_def_at(handle.struct_index)?; + test_variant(verifier, meter, offset, struct_def, &Signature(vec![]))? + }, + Bytecode::TestVariantGeneric(idx) => { + let inst = verifier.resolver.struct_variant_instantiation_at(*idx)?; + let handle = verifier.resolver.struct_variant_handle_at(inst.handle)?; + let struct_def = verifier.resolver.struct_def_at(handle.struct_index)?; + let type_args = verifier.resolver.signature_at(inst.type_parameters); + test_variant(verifier, meter, offset, struct_def, type_args)? }, Bytecode::ReadRef => { diff --git a/third_party/move/move-bytecode-verifier/src/verifier.rs b/third_party/move/move-bytecode-verifier/src/verifier.rs index 64cc0844a2180..eca392ae71de9 100644 --- a/third_party/move/move-bytecode-verifier/src/verifier.rs +++ b/third_party/move/move-bytecode-verifier/src/verifier.rs @@ -31,6 +31,7 @@ pub struct VerifierConfig { pub max_push_size: Option, pub max_dependency_depth: Option, pub max_struct_definitions: Option, + pub max_struct_variants: Option, pub max_fields_in_struct: Option, pub max_function_definitions: Option, pub max_back_edges_per_function: Option, @@ -194,6 +195,8 @@ impl Default for VerifierConfig { max_struct_definitions: None, // Max count of fields in a struct max_fields_in_struct: None, + // Max count of variants in a struct + max_struct_variants: None, // Max count of functions in a module max_function_definitions: None, // Max size set to 10000 to restrict number of pushes in one function @@ -242,6 +245,7 @@ impl VerifierConfig { max_dependency_depth: Some(100), max_struct_definitions: Some(200), max_fields_in_struct: Some(30), + max_struct_variants: Some(90), max_function_definitions: Some(1000), // Do not use back edge constraints as they are superseded by metering diff --git a/third_party/move/move-bytecode-verifier/transactional-tests/tests/type_safety/phantom_params/bytecode_ops_abilities_bad.exp b/third_party/move/move-bytecode-verifier/transactional-tests/tests/type_safety/phantom_params/bytecode_ops_abilities_bad.exp index 9a4d7deeba221..d3090ae863756 100644 --- a/third_party/move/move-bytecode-verifier/transactional-tests/tests/type_safety/phantom_params/bytecode_ops_abilities_bad.exp +++ b/third_party/move/move-bytecode-verifier/transactional-tests/tests/type_safety/phantom_params/bytecode_ops_abilities_bad.exp @@ -41,7 +41,7 @@ Error: Unable to publish module '00000000000000000000000000000000000000000000000 major_status: CONSTRAINT_NOT_SATISFIED, sub_status: None, location: 0x1::M6, - indices: [(Signature, 2), (FunctionDefinition, 0)], + indices: [(Signature, 2), (StructDefInstantiation, 0), (FunctionDefinition, 0)], offsets: [], } @@ -50,7 +50,7 @@ Error: Unable to publish module '00000000000000000000000000000000000000000000000 major_status: CONSTRAINT_NOT_SATISFIED, sub_status: None, location: 0x1::M7, - indices: [(Signature, 2), (FunctionDefinition, 0)], + indices: [(Signature, 2), (StructDefInstantiation, 0), (FunctionDefinition, 0)], offsets: [], } @@ -59,7 +59,7 @@ Error: Unable to publish module '00000000000000000000000000000000000000000000000 major_status: CONSTRAINT_NOT_SATISFIED, sub_status: None, location: 0x1::M8, - indices: [(Signature, 2), (FunctionDefinition, 0)], + indices: [(Signature, 2), (StructDefInstantiation, 0), (FunctionDefinition, 0)], offsets: [], } diff --git a/third_party/move/move-compiler-v2/src/bytecode_generator.rs b/third_party/move/move-compiler-v2/src/bytecode_generator.rs index e26177acf6055..3650dbaa0c935 100644 --- a/third_party/move/move-compiler-v2/src/bytecode_generator.rs +++ b/third_party/move/move-compiler-v2/src/bytecode_generator.rs @@ -10,7 +10,7 @@ use move_model::{ ast::{Exp, ExpData, MatchArm, Operation, Pattern, SpecBlockTarget, TempIndex, Value}, exp_rewriter::{ExpRewriter, ExpRewriterFunctions, RewriteTarget}, model::{ - FieldId, FunId, FunctionEnv, GlobalEnv, Loc, NodeId, Parameter, QualifiedId, + FieldEnv, FieldId, FunId, FunctionEnv, GlobalEnv, Loc, NodeId, Parameter, QualifiedId, QualifiedInstId, StructId, }, symbol::Symbol, @@ -1115,11 +1115,6 @@ impl<'env> Generator<'env> { field_id: FieldId, oper: &Exp, ) { - let field_offset = { - let struct_env = self.env().get_struct(struct_id); - let field_env = struct_env.get_field(field_id); - field_env.get_offset() - }; let temp = self.gen_auto_ref_arg(oper, kind); // Get instantiation of field. It is not contained in the select expression but in the // type of its operand. @@ -1128,15 +1123,12 @@ impl<'env> Generator<'env> { .skip_reference() .get_struct(self.env()) { + let struct_env = self.env().get_struct(struct_id); + let field_env = struct_env.get_field(field_id); self.emit_call( id, vec![target], - BytecodeOperation::BorrowField( - struct_id.module_id, - struct_id.id, - inst.to_vec(), - field_offset, - ), + self.borrow_field_operation(struct_id.instantiate(inst.to_vec()), &field_env), vec![temp], ); } else { @@ -1161,9 +1153,6 @@ impl<'env> Generator<'env> { field: FieldId, oper: &Exp, ) { - let struct_env = self.env().get_struct(str.to_qualified_id()); - let field_offset = struct_env.get_field(field).get_offset(); - // Compile operand in reference mode, defaulting to immutable mode. let oper_temp = self.gen_auto_ref_arg(oper, ReferenceKind::Immutable); let oper_type = self.get_node_type(oper.node_id()); @@ -1200,10 +1189,12 @@ impl<'env> Generator<'env> { } else { target }; + let struct_env = self.env().get_struct(str.to_qualified_id()); + let field_env = struct_env.get_field(field); self.emit_call( id, vec![borrow_dest], - BytecodeOperation::BorrowField(str.module_id, str.id, str.inst, field_offset), + self.borrow_field_operation(str, &field_env), vec![oper_temp], ); if need_read_ref { @@ -1212,6 +1203,36 @@ impl<'env> Generator<'env> { ]) } } + + fn borrow_field_operation( + &self, + str: QualifiedInstId, + field_env: &FieldEnv, + ) -> BytecodeOperation { + let struct_env = &field_env.struct_env; + if struct_env.has_variants() { + // Need to generate a BorrowVariantField, specifying all the variants in + // which the field is defined. + let variants_with_field = struct_env + .get_variants() + .filter(|v| { + struct_env.get_fields_of_variant(*v).any(|f| { + f.get_name() == field_env.get_name() && f.get_type() == field_env.get_type() + }) + }) + .collect_vec(); + BytecodeOperation::BorrowVariantField( + str.module_id, + str.id, + variants_with_field, + str.inst, + field_env.get_offset(), + ) + } else { + // Regular BorrowField + BytecodeOperation::BorrowField(str.module_id, str.id, str.inst, field_env.get_offset()) + } + } } // ====================================================================================== @@ -1684,10 +1705,10 @@ impl<'env> Generator<'env> { *id, vec![*temp], if let Some(var) = variant { - BytecodeOperation::BorrowFieldVariant( + BytecodeOperation::BorrowVariantField( str.module_id, str.id, - var, + vec![var], str.inst.to_owned(), field_offset, ) diff --git a/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs b/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs index 0b2f2ea62b29c..c7c4610d733c0 100644 --- a/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs +++ b/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs @@ -16,6 +16,7 @@ use move_model::{ ast::{ExpData, Spec, SpecBlockTarget, TempIndex}, exp_rewriter::{ExpRewriter, ExpRewriterFunctions, RewriteTarget}, model::{FunId, FunctionEnv, Loc, NodeId, Parameter, QualifiedId, StructId, TypeParameter}, + symbol::Symbol, ty::{PrimitiveType, Type}, }; use move_stackless_bytecode::{ @@ -374,12 +375,6 @@ impl<'a> FunctionGenerator<'a> { self.flush_any_conflicts(ctx, dest, source); let fun_ctx = ctx.fun_ctx; match oper { - Operation::TestVariant(..) - | Operation::PackVariant(..) - | Operation::UnpackVariant(..) - | Operation::BorrowFieldVariant(..) => { - fun_ctx.internal_error("variants not yet implemented") - }, Operation::Function(mid, fid, inst) => { self.gen_call(ctx, dest, mid.qualified(*fid), inst, source); }, @@ -394,6 +389,18 @@ impl<'a> FunctionGenerator<'a> { FF::Bytecode::PackGeneric, ); }, + Operation::PackVariant(mid, sid, variant, inst) => { + self.gen_struct_variant_oper( + ctx, + dest, + mid.qualified(*sid), + *variant, + inst, + source, + FF::Bytecode::PackVariant, + FF::Bytecode::PackVariantGeneric, + ); + }, Operation::Unpack(mid, sid, inst) => { self.gen_struct_oper( ctx, @@ -405,6 +412,30 @@ impl<'a> FunctionGenerator<'a> { FF::Bytecode::UnpackGeneric, ); }, + Operation::UnpackVariant(mid, sid, variant, inst) => { + self.gen_struct_variant_oper( + ctx, + dest, + mid.qualified(*sid), + *variant, + inst, + source, + FF::Bytecode::UnpackVariant, + FF::Bytecode::UnpackVariantGeneric, + ); + }, + Operation::TestVariant(mid, sid, variant, inst) => { + self.gen_struct_variant_oper( + ctx, + dest, + mid.qualified(*sid), + *variant, + inst, + source, + FF::Bytecode::TestVariant, + FF::Bytecode::TestVariantGeneric, + ); + }, Operation::MoveTo(mid, sid, inst) => { self.gen_struct_oper( ctx, @@ -453,6 +484,18 @@ impl<'a> FunctionGenerator<'a> { dest, mid.qualified(*sid), inst.clone(), + None, + *offset, + source, + ); + }, + Operation::BorrowVariantField(mid, sid, variants, inst, offset) => { + self.gen_borrow_field( + ctx, + dest, + mid.qualified(*sid), + inst.clone(), + Some(variants), *offset, source, ); @@ -637,6 +680,39 @@ impl<'a> FunctionGenerator<'a> { self.abstract_push_result(ctx, dest); } + fn gen_struct_variant_oper( + &mut self, + ctx: &BytecodeContext, + dest: &[TempIndex], + id: QualifiedId, + variant: Symbol, + inst: &[Type], + source: &[TempIndex], + mk_simple: impl FnOnce(FF::StructVariantHandleIndex) -> FF::Bytecode, + mk_generic: impl FnOnce(FF::StructVariantInstantiationIndex) -> FF::Bytecode, + ) { + let fun_ctx = ctx.fun_ctx; + self.abstract_push_args(ctx, source, None); + let struct_env = &fun_ctx.module.env.get_struct(id); + if inst.is_empty() { + let idx = + self.gen + .struct_variant_index(&fun_ctx.module, &fun_ctx.loc, struct_env, variant); + self.emit(mk_simple(idx)) + } else { + let idx = self.gen.struct_variant_inst_index( + &fun_ctx.module, + &fun_ctx.loc, + struct_env, + variant, + inst.to_vec(), + ); + self.emit(mk_generic(idx)) + } + self.abstract_pop_n(ctx, source.len()); + self.abstract_push_result(ctx, dest); + } + /// Generate code for the borrow-field instruction. fn gen_borrow_field( &mut self, @@ -644,31 +720,65 @@ impl<'a> FunctionGenerator<'a> { dest: &[TempIndex], id: QualifiedId, inst: Vec, + variants: Option<&[Symbol]>, offset: usize, source: &[TempIndex], ) { let fun_ctx = ctx.fun_ctx; self.abstract_push_args(ctx, source, None); let struct_env = &fun_ctx.module.env.get_struct(id); - let field_env = &struct_env.get_field_by_offset(offset); let is_mut = fun_ctx.fun.get_local_type(dest[0]).is_mutable_reference(); - if inst.is_empty() { - let idx = self - .gen - .field_index(&fun_ctx.module, &fun_ctx.loc, field_env); - if is_mut { - self.emit(FF::Bytecode::MutBorrowField(idx)) + + if let Some(variants) = variants { + assert!(!variants.is_empty()); + let field_env = + &struct_env.get_field_by_offset_optional_variant(Some(variants[0]), offset); + if inst.is_empty() { + let idx = self.gen.variant_field_index( + &fun_ctx.module, + &fun_ctx.loc, + variants, + field_env, + ); + if is_mut { + self.emit(FF::Bytecode::MutBorrowVariantField(idx)) + } else { + self.emit(FF::Bytecode::ImmBorrowVariantField(idx)) + } } else { - self.emit(FF::Bytecode::ImmBorrowField(idx)) + let idx = self.gen.variant_field_inst_index( + &fun_ctx.module, + &fun_ctx.loc, + variants, + field_env, + inst, + ); + if is_mut { + self.emit(FF::Bytecode::MutBorrowVariantFieldGeneric(idx)) + } else { + self.emit(FF::Bytecode::ImmBorrowVariantFieldGeneric(idx)) + } } } else { - let idx = self - .gen - .field_inst_index(&fun_ctx.module, &fun_ctx.loc, field_env, inst); - if is_mut { - self.emit(FF::Bytecode::MutBorrowFieldGeneric(idx)) + let field_env = &struct_env.get_field_by_offset_optional_variant(None, offset); + if inst.is_empty() { + let idx = self + .gen + .field_index(&fun_ctx.module, &fun_ctx.loc, field_env); + if is_mut { + self.emit(FF::Bytecode::MutBorrowField(idx)) + } else { + self.emit(FF::Bytecode::ImmBorrowField(idx)) + } } else { - self.emit(FF::Bytecode::ImmBorrowFieldGeneric(idx)) + let idx = self + .gen + .field_inst_index(&fun_ctx.module, &fun_ctx.loc, field_env, inst); + if is_mut { + self.emit(FF::Bytecode::MutBorrowFieldGeneric(idx)) + } else { + self.emit(FF::Bytecode::ImmBorrowFieldGeneric(idx)) + } } } self.abstract_pop_n(ctx, source.len()); diff --git a/third_party/move/move-compiler-v2/src/file_format_generator/mod.rs b/third_party/move/move-compiler-v2/src/file_format_generator/mod.rs index 2143de5397d3c..ae048ab325738 100644 --- a/third_party/move/move-compiler-v2/src/file_format_generator/mod.rs +++ b/third_party/move/move-compiler-v2/src/file_format_generator/mod.rs @@ -145,6 +145,8 @@ const MAX_STRUCT_COUNT: usize = FF::TableIndex::MAX as usize; const MAX_SIGNATURE_COUNT: usize = FF::TableIndex::MAX as usize; const MAX_STRUCT_DEF_COUNT: usize = FF::TableIndex::MAX as usize; const MAX_STRUCT_DEF_INST_COUNT: usize = FF::TableIndex::MAX as usize; +const MAX_STRUCT_VARIANT_COUNT: usize = FF::TableIndex::MAX as usize; +const MAX_STRUCT_VARIANT_INST_COUNT: usize = FF::TableIndex::MAX as usize; const MAX_FIELD_COUNT: usize = FF::TableIndex::MAX as usize; const MAX_FIELD_INST_COUNT: usize = FF::TableIndex::MAX as usize; const MAX_FUNCTION_COUNT: usize = FF::TableIndex::MAX as usize; diff --git a/third_party/move/move-compiler-v2/src/file_format_generator/module_generator.rs b/third_party/move/move-compiler-v2/src/file_format_generator/module_generator.rs index cf1a3962b96cc..4ed3bccf0f9f0 100644 --- a/third_party/move/move-compiler-v2/src/file_format_generator/module_generator.rs +++ b/third_party/move/move-compiler-v2/src/file_format_generator/module_generator.rs @@ -6,11 +6,12 @@ use crate::{ function_generator::FunctionGenerator, MAX_ADDRESS_COUNT, MAX_CONST_COUNT, MAX_FIELD_COUNT, MAX_FIELD_INST_COUNT, MAX_FUNCTION_COUNT, MAX_FUNCTION_INST_COUNT, MAX_IDENTIFIER_COUNT, MAX_MODULE_COUNT, MAX_SIGNATURE_COUNT, MAX_STRUCT_COUNT, MAX_STRUCT_DEF_COUNT, - MAX_STRUCT_DEF_INST_COUNT, + MAX_STRUCT_DEF_INST_COUNT, MAX_STRUCT_VARIANT_COUNT, MAX_STRUCT_VARIANT_INST_COUNT, }, Experiment, Options, }; use codespan_reporting::diagnostic::Severity; +use itertools::Itertools; use move_binary_format::{ file_format as FF, file_format::{AccessKind, FunctionHandle, ModuleHandle, StructDefinitionIndex, TableIndex}, @@ -77,6 +78,22 @@ pub struct ModuleGenerator { types_to_signature: BTreeMap, FF::SignatureIndex>, /// A mapping from constants sequences (with the corresponding type information) to pool indices. cons_to_idx: BTreeMap<(Constant, Type), FF::ConstantPoolIndex>, + variant_field_to_idx: + BTreeMap<(QualifiedId, Vec, usize), FF::VariantFieldHandleIndex>, + variant_field_inst_to_idx: BTreeMap< + ( + QualifiedId, + Vec, + usize, + FF::SignatureIndex, + ), + FF::VariantFieldInstantiationIndex, + >, + struct_variant_to_idx: BTreeMap<(QualifiedId, Symbol), FF::StructVariantHandleIndex>, + struct_variant_inst_to_idx: BTreeMap< + (QualifiedId, Symbol, FF::SignatureIndex), + FF::StructVariantInstantiationIndex, + >, /// The file-format module we are building. pub module: FF::CompiledModule, /// The source map for the module. @@ -147,6 +164,10 @@ impl ModuleGenerator { field_inst_to_idx: Default::default(), types_to_signature: Default::default(), cons_to_idx: Default::default(), + variant_field_to_idx: Default::default(), + variant_field_inst_to_idx: Default::default(), + struct_variant_to_idx: Default::default(), + struct_variant_inst_to_idx: Default::default(), fun_inst_to_idx: Default::default(), main_handle: None, script_handle: None, @@ -216,23 +237,24 @@ impl ModuleGenerator { .expect(SOURCE_MAP_OK); } let struct_handle = self.struct_index(ctx, loc, struct_env); - let fields = struct_env.get_fields(); - let field_information = if struct_env.is_native() { + let field_information = if struct_env.has_variants() { + let variants = struct_env + .get_variants() + .map(|v| FF::VariantDefinition { + name: self.name_index(ctx, struct_env.get_variant_loc(v), v), + fields: struct_env + .get_fields_of_variant(v) + .map(|f| self.field(ctx, def_idx, &f)) + .collect_vec(), + }) + .collect_vec(); + FF::StructFieldInformation::DeclaredVariants(variants) + } else if struct_env.is_native() { FF::StructFieldInformation::Native } else { + let fields = struct_env.get_fields(); FF::StructFieldInformation::Declared( - fields - .map(|f| { - let field_loc = f.get_loc(); - self.source_map - .add_struct_field_mapping(def_idx, ctx.env.to_ir_loc(field_loc)) - .expect(SOURCE_MAP_OK); - let name = self.name_index(ctx, field_loc, f.get_name()); - let signature = - FF::TypeSignature(self.signature_token(ctx, loc, &f.get_type())); - FF::FieldDefinition { name, signature } - }) - .collect(), + fields.map(|f| self.field(ctx, def_idx, &f)).collect(), ) }; let def = FF::StructDefinition { @@ -242,6 +264,25 @@ impl ModuleGenerator { self.module.struct_defs.push(def) } + fn field( + &mut self, + ctx: &ModuleContext, + struct_def_idx: StructDefinitionIndex, + field_env: &FieldEnv, + ) -> FF::FieldDefinition { + let field_loc = field_env.get_loc(); + let variant_idx = field_env + .get_variant() + .and_then(|v| field_env.struct_env.get_variant_idx(v)); + self.source_map + .add_struct_field_mapping(struct_def_idx, variant_idx, ctx.env.to_ir_loc(field_loc)) + .expect(SOURCE_MAP_OK); + let name = self.name_index(ctx, field_loc, field_env.get_name()); + let signature = + FF::TypeSignature(self.signature_token(ctx, field_loc, &field_env.get_type())); + FF::FieldDefinition { name, signature } + } + /// Obtains or creates an index for a signature, a sequence of types. pub fn signature( &mut self, @@ -763,6 +804,140 @@ impl ModuleGenerator { field_inst_idx } + /// Obtains or creates a variant field handle index. + pub fn variant_field_index( + &mut self, + ctx: &ModuleContext, + loc: &Loc, + variants: &[Symbol], + field_env: &FieldEnv, + ) -> FF::VariantFieldHandleIndex { + let key = ( + field_env.struct_env.get_qualified_id(), + variants.to_vec(), + field_env.get_offset(), + ); + if let Some(idx) = self.variant_field_to_idx.get(&key) { + return *idx; + } + let field_idx = FF::VariantFieldHandleIndex(ctx.checked_bound( + loc, + self.module.variant_field_handles.len(), + MAX_FIELD_COUNT, + "variant field", + )); + let variant_offsets = variants + .iter() + .filter_map(|v| field_env.struct_env.get_variant_idx(*v)) + .collect_vec(); + let owner = self.struct_def_index(ctx, loc, &field_env.struct_env); + self.module + .variant_field_handles + .push(FF::VariantFieldHandle { + struct_index: owner, + variants: variant_offsets, + field: field_env.get_offset() as FF::MemberCount, + }); + self.variant_field_to_idx.insert(key, field_idx); + field_idx + } + + /// Obtains or creates a variant field instantiation handle index. + pub fn variant_field_inst_index( + &mut self, + ctx: &ModuleContext, + loc: &Loc, + variants: &[Symbol], + field_env: &FieldEnv, + inst: Vec, + ) -> FF::VariantFieldInstantiationIndex { + let type_parameters = self.signature(ctx, loc, inst); + let key = ( + field_env.struct_env.get_qualified_id(), + variants.to_vec(), + field_env.get_offset(), + type_parameters, + ); + if let Some(idx) = self.variant_field_inst_to_idx.get(&key) { + return *idx; + } + let idx = FF::VariantFieldInstantiationIndex(ctx.checked_bound( + loc, + self.module.variant_field_instantiations.len(), + MAX_FIELD_INST_COUNT, + "variant field instantiation", + )); + let handle = self.variant_field_index(ctx, loc, variants, field_env); + self.module + .variant_field_instantiations + .push(FF::VariantFieldInstantiation { + handle, + type_parameters, + }); + self.variant_field_inst_to_idx.insert(key, idx); + idx + } + + /// Obtains or creates a struct variant handle index. + pub fn struct_variant_index( + &mut self, + ctx: &ModuleContext, + loc: &Loc, + struct_env: &StructEnv, + variant: Symbol, + ) -> FF::StructVariantHandleIndex { + let key = (struct_env.get_qualified_id(), variant); + if let Some(idx) = self.struct_variant_to_idx.get(&key) { + return *idx; + } + let idx = FF::StructVariantHandleIndex(ctx.checked_bound( + loc, + self.module.struct_variant_handles.len(), + MAX_STRUCT_VARIANT_COUNT, + "struct variant", + )); + let struct_index = self.struct_def_index(ctx, loc, struct_env); + self.module + .struct_variant_handles + .push(FF::StructVariantHandle { + struct_index, + variant: struct_env.get_variant_idx(variant).expect("variant idx"), + }); + self.struct_variant_to_idx.insert(key, idx); + idx + } + + /// Obtains or creates a struct variant instantiation index. + pub fn struct_variant_inst_index( + &mut self, + ctx: &ModuleContext, + loc: &Loc, + struct_env: &StructEnv, + variant: Symbol, + inst: Vec, + ) -> FF::StructVariantInstantiationIndex { + let type_parameters = self.signature(ctx, loc, inst); + let key = (struct_env.get_qualified_id(), variant, type_parameters); + if let Some(idx) = self.struct_variant_inst_to_idx.get(&key) { + return *idx; + } + let idx = FF::StructVariantInstantiationIndex(ctx.checked_bound( + loc, + self.module.struct_variant_instantiations.len(), + MAX_STRUCT_VARIANT_INST_COUNT, + "struct variant instantiation", + )); + let handle = self.struct_variant_index(ctx, loc, struct_env, variant); + self.module + .struct_variant_instantiations + .push(FF::StructVariantInstantiation { + handle, + type_parameters, + }); + self.struct_variant_inst_to_idx.insert(key, idx); + idx + } + /// Obtains or generates a constant index. pub fn constant_index( &mut self, diff --git a/third_party/move/move-compiler-v2/src/pipeline/reference_safety_processor.rs b/third_party/move/move-compiler-v2/src/pipeline/reference_safety_processor.rs index 6f3b047d233af..1bc0f468233fc 100644 --- a/third_party/move/move-compiler-v2/src/pipeline/reference_safety_processor.rs +++ b/third_party/move/move-compiler-v2/src/pipeline/reference_safety_processor.rs @@ -120,6 +120,7 @@ use move_binary_format::{file_format, file_format::CodeOffset}; use move_model::{ ast::TempIndex, model::{FieldId, FunId, FunctionEnv, GlobalEnv, Loc, Parameter, QualifiedInstId, StructId}, + symbol::Symbol, ty::Type, }; use move_stackless_bytecode::{ @@ -1562,6 +1563,7 @@ impl<'env, 'state> LifetimeAnalysisStep<'env, 'state> { fn borrow_field( &mut self, struct_: QualifiedInstId, + variant: Option, field_offs: &usize, dest: TempIndex, src: TempIndex, @@ -1571,7 +1573,9 @@ impl<'env, 'state> LifetimeAnalysisStep<'env, 'state> { self.state.mark_derived_from(child, src); let loc = self.cur_loc(); let struct_env = self.global_env().get_struct(struct_.to_qualified_id()); - let field_id = struct_env.get_field_by_offset(*field_offs).get_id(); + let field_id = struct_env + .get_field_by_offset_optional_variant(variant, *field_offs) + .get_id(); let is_mut = self.ty(dest).is_mutable_reference(); self.state.add_edge( label, @@ -1999,6 +2003,19 @@ impl<'env> TransferFunctions for LifeTimeAnalysis<'env> { let (dest, src) = (dests[0], srcs[0]); step.borrow_field( mid.qualified_inst(*sid, inst.clone()), + None, + field_offs, + dest, + src, + ); + }, + BorrowVariantField(mid, sid, variants, inst, field_offs) => { + let (dest, src) = (dests[0], srcs[0]); + + step.borrow_field( + mid.qualified_inst(*sid, inst.clone()), + // Use one representative variant + Some(variants[0]), field_offs, dest, src, diff --git a/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ability_err.exp b/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ability_err.exp index 868caa539cf43..9fb6f4c14fe92 100644 --- a/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ability_err.exp +++ b/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ability_err.exp @@ -74,7 +74,7 @@ public fun m::condition_requires_copy($t0: m::Outer): m::Outer { 1: $t3 := test_variant m::Outer::One($t2) 2: if ($t3) goto 3 else goto 12 3: label L2 - 4: $t4 := borrow_field_variant.i($t2) + 4: $t4 := borrow_variant_field.i($t2) 5: $t5 := read_ref($t4) 6: $t3 := m::consume($t5) 7: if ($t3) goto 8 else goto 12 diff --git a/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ok.exp b/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ok.exp index 78433c71bd858..5bdd2dfe7a96a 100644 --- a/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ok.exp +++ b/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ok.exp @@ -3,6 +3,16 @@ module 0xc0ffee::m { struct Box { x: u64, } + enum CommonFields { + Foo { + x: u64, + y: u64, + } + Bar { + x: u64, + z: u64, + } + } enum Inner { Inner1 { x: u64, @@ -151,6 +161,17 @@ module 0xc0ffee::m { } } + private fun select_common_fields(s: m::CommonFields): u64 { + Add(select m::CommonFields.x(s), match (s) { + m::CommonFields::Foo{ x: _, y } => { + y + } + m::CommonFields::Bar{ x: _, z } => { + z + } + } + ) + } } // end 0xc0ffee::m ============ initial bytecode ================ @@ -275,7 +296,7 @@ public fun m::is_some_specialized($t0: &m::Option>): bool { 6: $t2 := test_variant m::Option>::Some($t0) 7: if ($t2) goto 8 else goto 15 8: label L4 - 9: $t3 := borrow_field_variant>::Some>.value($t0) + 9: $t3 := borrow_variant_field>::Some>.value($t0) 10: $t2 := test_variant m::Option::None($t3) 11: if ($t2) goto 12 else goto 15 12: label L5 @@ -285,7 +306,7 @@ public fun m::is_some_specialized($t0: &m::Option>): bool { 16: $t2 := test_variant m::Option>::Some($t0) 17: if ($t2) goto 18 else goto 25 18: label L7 - 19: $t4 := borrow_field_variant>::Some>.value($t0) + 19: $t4 := borrow_variant_field>::Some>.value($t0) 20: $t2 := test_variant m::Option::Some($t4) 21: if ($t2) goto 22 else goto 25 22: label L8 @@ -372,7 +393,7 @@ public fun m::outer_value_nested($t0: m::Outer): u64 { 8: $t3 := test_variant m::Outer::One($t2) 9: if ($t3) goto 10 else goto 19 10: label L4 - 11: $t4 := borrow_field_variant.i($t2) + 11: $t4 := borrow_variant_field.i($t2) 12: $t3 := test_variant m::Inner::Inner1($t4) 13: if ($t3) goto 14 else goto 19 14: label L5 @@ -435,7 +456,7 @@ public fun m::outer_value_with_cond($t0: m::Outer): u64 { 8: $t3 := test_variant m::Outer::One($t2) 9: if ($t3) goto 10 else goto 21 10: label L4 - 11: $t4 := borrow_field_variant.i($t2) + 11: $t4 := borrow_variant_field.i($t2) 12: $t5 := infer($t4) 13: $t3 := m::is_inner1($t5) 14: if ($t3) goto 15 else goto 21 @@ -488,7 +509,7 @@ public fun m::outer_value_with_cond_ref($t0: &m::Outer): bool { 6: $t2 := test_variant m::Outer::One($t0) 7: if ($t2) goto 8 else goto 15 8: label L4 - 9: $t3 := borrow_field_variant.i($t0) + 9: $t3 := borrow_variant_field.i($t0) 10: $t2 := m::is_inner1($t3) 11: if ($t2) goto 12 else goto 15 12: label L5 @@ -498,14 +519,14 @@ public fun m::outer_value_with_cond_ref($t0: &m::Outer): bool { 16: $t2 := test_variant m::Outer::One($t0) 17: if ($t2) goto 18 else goto 22 18: label L7 - 19: $t4 := borrow_field_variant.i($t0) + 19: $t4 := borrow_variant_field.i($t0) 20: $t1 := m::is_inner1($t4) 21: goto 32 22: label L6 23: $t2 := test_variant m::Outer::Two($t0) 24: if ($t2) goto 25 else goto 29 25: label L9 - 26: $t5 := borrow_field_variant.i($t0) + 26: $t5 := borrow_variant_field.i($t0) 27: $t1 := m::is_inner1($t5) 28: goto 32 29: label L8 @@ -516,57 +537,44 @@ public fun m::outer_value_with_cond_ref($t0: &m::Outer): bool { } -Diagnostics: -bug: file format generator: variants not yet implemented - ┌─ tests/bytecode-generator/matching_ok.move:19:16 - │ -19 │ public fun inner_value(self: Inner): u64 { - │ ^^^^^^^^^^^ - -bug: file format generator: variants not yet implemented - ┌─ tests/bytecode-generator/matching_ok.move:27:16 - │ -27 │ public fun is_inner1(self: &Inner): bool { - │ ^^^^^^^^^ - -bug: file format generator: variants not yet implemented - ┌─ tests/bytecode-generator/matching_ok.move:35:16 - │ -35 │ public fun outer_value(o: Outer): u64 { - │ ^^^^^^^^^^^ - -bug: file format generator: variants not yet implemented - ┌─ tests/bytecode-generator/matching_ok.move:45:16 - │ -45 │ public fun outer_value_nested(o: Outer): u64 { - │ ^^^^^^^^^^^^^^^^^^ - -bug: file format generator: variants not yet implemented - ┌─ tests/bytecode-generator/matching_ok.move:56:16 - │ -56 │ public fun outer_value_with_cond(o: Outer): u64 { - │ ^^^^^^^^^^^^^^^^^^^^^ - -bug: file format generator: variants not yet implemented - ┌─ tests/bytecode-generator/matching_ok.move:67:16 - │ -67 │ public fun outer_value_with_cond_ref(o: &Outer): bool { - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - -bug: file format generator: variants not yet implemented - ┌─ tests/bytecode-generator/matching_ok.move:82:16 - │ -82 │ public fun is_some(x: &Option): bool { - │ ^^^^^^^ +[variant baseline] +fun m::select_common_fields($t0: m::CommonFields): u64 { + var $t1: u64 + var $t2: u64 + var $t3: &m::CommonFields + var $t4: &u64 + var $t5: u64 + var $t6: &m::CommonFields + var $t7: bool + var $t8: u64 + var $t9: u64 + var $t10: u64 + var $t11: u64 + var $t12: u64 + 0: $t3 := borrow_local($t0) + 1: $t4 := borrow_variant_field.x($t3) + 2: $t2 := read_ref($t4) + 3: $t6 := borrow_local($t0) + 4: $t7 := test_variant m::CommonFields::Foo($t6) + 5: if ($t7) goto 6 else goto 10 + 6: label L2 + 7: ($t9, $t8) := unpack_variant m::CommonFields::Foo($t0) + 8: $t5 := infer($t8) + 9: goto 20 + 10: label L1 + 11: $t7 := test_variant m::CommonFields::Bar($t6) + 12: if ($t7) goto 13 else goto 17 + 13: label L4 + 14: ($t11, $t10) := unpack_variant m::CommonFields::Bar($t0) + 15: $t5 := infer($t10) + 16: goto 20 + 17: label L3 + 18: $t12 := 15621340914461310977 + 19: abort($t12) + 20: label L0 + 21: $t1 := +($t2, $t5) + 22: return $t1 +} -bug: file format generator: variants not yet implemented - ┌─ tests/bytecode-generator/matching_ok.move:89:16 - │ -89 │ public fun is_some_specialized(x: &Option>): bool { - │ ^^^^^^^^^^^^^^^^^^^ -bug: file format generator: variants not yet implemented - ┌─ tests/bytecode-generator/matching_ok.move:97:16 - │ -97 │ public fun is_some_dropped(x: Option): bool { - │ ^^^^^^^^^^^^^^^ +============ bytecode verification succeeded ======== diff --git a/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ok.move b/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ok.move index 94c2e5dac952d..4b8d06af3672c 100644 --- a/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ok.move +++ b/third_party/move/move-compiler-v2/tests/bytecode-generator/matching_ok.move @@ -100,4 +100,14 @@ module 0xc0ffee::m { _ => true } } + + // Common fields + enum CommonFields { + Foo{x: u64, y: u64}, + Bar{z: u64, x: u64} + } + + fun select_common_fields(s: CommonFields): u64 { + s.x + (match (s) { Foo{x: _, y} => y, Bar{z, x: _} => z }) + } } diff --git a/third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.exp b/third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.exp new file mode 100644 index 0000000000000..d29a0cb550746 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.exp @@ -0,0 +1,505 @@ + +============ disassembled file-format ================== +// Move bytecode v7 +module c0ffee.m { +struct Box has drop { + x: u64 +} +enum CommonFields { + Foo{ + x: u64, + y: u64 + }, + Bar{ + x: u64, + z: u64 + } +} +enum Inner { + Inner1{ + x: u64 + }, + Inner2{ + x: u64, + y: u64 + } +} +enum Option has drop { + None{ + + }, + Some{ + value: Ty0 + } +} +enum Outer { + None{ + + }, + One{ + i: Inner + }, + Two{ + i: Inner, + b: Box + } +} + +public inner_value(Arg0: Inner): u64 /* def_idx: 0 */ { +L1: loc0: &Inner +L2: loc1: u64 +B0: + 0: ImmBorrowLoc[0](Arg0: Inner) + 1: StLoc[1](loc0: &Inner) + 2: CopyLoc[1](loc0: &Inner) + 3: TestVariant[0](Inner/Inner1) + 4: BrFalse(11) +B1: + 5: MoveLoc[1](loc0: &Inner) + 6: Pop + 7: MoveLoc[0](Arg0: Inner) + 8: UnpackVariant[0](Inner/Inner1) + 9: StLoc[2](loc1: u64) + 10: Branch(21) +B2: + 11: MoveLoc[1](loc0: &Inner) + 12: TestVariant[1](Inner/Inner2) + 13: BrFalse(19) +B3: + 14: MoveLoc[0](Arg0: Inner) + 15: UnpackVariant[1](Inner/Inner2) + 16: Add + 17: StLoc[2](loc1: u64) + 18: Branch(21) +B4: + 19: LdU64(15621340914461310977) + 20: Abort +B5: + 21: MoveLoc[2](loc1: u64) + 22: Ret +} +public is_inner1(Arg0: &Inner): bool /* def_idx: 1 */ { +L1: loc0: bool +L2: loc1: &Inner +B0: + 0: CopyLoc[0](Arg0: &Inner) + 1: TestVariant[0](Inner/Inner1) + 2: BrFalse(8) +B1: + 3: MoveLoc[0](Arg0: &Inner) + 4: Pop + 5: LdTrue + 6: StLoc[1](loc0: bool) + 7: Branch(17) +B2: + 8: MoveLoc[0](Arg0: &Inner) + 9: StLoc[2](loc1: &Inner) + 10: MoveLoc[2](loc1: &Inner) + 11: Pop + 12: LdFalse + 13: StLoc[1](loc0: bool) + 14: Branch(17) +B3: + 15: LdU64(15621340914461310977) + 16: Abort +B4: + 17: MoveLoc[1](loc0: bool) + 18: Ret +} +public is_some(Arg0: &Option): bool /* def_idx: 2 */ { +L1: loc0: bool +B0: + 0: CopyLoc[0](Arg0: &Option) + 1: TestVariantGeneric[0](Option/None) + 2: BrFalse(8) +B1: + 3: MoveLoc[0](Arg0: &Option) + 4: Pop + 5: LdFalse + 6: StLoc[1](loc0: bool) + 7: Branch(16) +B2: + 8: MoveLoc[0](Arg0: &Option) + 9: TestVariantGeneric[1](Option/Some) + 10: BrFalse(14) +B3: + 11: LdTrue + 12: StLoc[1](loc0: bool) + 13: Branch(16) +B4: + 14: LdU64(15621340914461310977) + 15: Abort +B5: + 16: MoveLoc[1](loc0: bool) + 17: Ret +} +public is_some_dropped(Arg0: Option): bool /* def_idx: 3 */ { +L1: loc0: bool +L2: loc1: Option +B0: + 0: ImmBorrowLoc[0](Arg0: Option) + 1: TestVariantGeneric[0](Option/None) + 2: BrFalse(8) +B1: + 3: MoveLoc[0](Arg0: Option) + 4: UnpackVariantGeneric[0](Option/None) + 5: LdFalse + 6: StLoc[1](loc0: bool) + 7: Branch(15) +B2: + 8: MoveLoc[0](Arg0: Option) + 9: StLoc[2](loc1: Option) + 10: LdTrue + 11: StLoc[1](loc0: bool) + 12: Branch(15) +B3: + 13: LdU64(15621340914461310977) + 14: Abort +B4: + 15: MoveLoc[1](loc0: bool) + 16: Ret +} +public is_some_specialized(Arg0: &Option>): bool /* def_idx: 4 */ { +L1: loc0: bool +B0: + 0: CopyLoc[0](Arg0: &Option>) + 1: TestVariantGeneric[2](Option/None>) + 2: BrFalse(8) +B1: + 3: MoveLoc[0](Arg0: &Option>) + 4: Pop + 5: LdFalse + 6: StLoc[1](loc0: bool) + 7: Branch(32) +B2: + 8: CopyLoc[0](Arg0: &Option>) + 9: TestVariantGeneric[3](Option/Some>) + 10: BrFalse(20) +B3: + 11: CopyLoc[0](Arg0: &Option>) + 12: ImmBorrowVariantFieldGeneric[0](Some.value: Ty0) + 13: TestVariantGeneric[4](Option/None) + 14: BrFalse(20) +B4: + 15: MoveLoc[0](Arg0: &Option>) + 16: Pop + 17: LdFalse + 18: StLoc[1](loc0: bool) + 19: Branch(32) +B5: + 20: CopyLoc[0](Arg0: &Option>) + 21: TestVariantGeneric[3](Option/Some>) + 22: BrFalse(30) +B6: + 23: MoveLoc[0](Arg0: &Option>) + 24: ImmBorrowVariantFieldGeneric[0](Some.value: Ty0) + 25: TestVariantGeneric[5](Option/Some) + 26: BrFalse(30) +B7: + 27: LdTrue + 28: StLoc[1](loc0: bool) + 29: Branch(32) +B8: + 30: LdU64(15621340914461310977) + 31: Abort +B9: + 32: MoveLoc[1](loc0: bool) + 33: Ret +} +public outer_value(Arg0: Outer): u64 /* def_idx: 5 */ { +L1: loc0: &Outer +L2: loc1: u64 +L3: loc2: Box +B0: + 0: ImmBorrowLoc[0](Arg0: Outer) + 1: StLoc[1](loc0: &Outer) + 2: CopyLoc[1](loc0: &Outer) + 3: TestVariant[4](Outer/None) + 4: BrFalse(12) +B1: + 5: MoveLoc[1](loc0: &Outer) + 6: Pop + 7: MoveLoc[0](Arg0: Outer) + 8: UnpackVariant[4](Outer/None) + 9: LdU64(0) + 10: StLoc[2](loc1: u64) + 11: Branch(37) +B2: + 12: CopyLoc[1](loc0: &Outer) + 13: TestVariant[5](Outer/One) + 14: BrFalse(22) +B3: + 15: MoveLoc[1](loc0: &Outer) + 16: Pop + 17: MoveLoc[0](Arg0: Outer) + 18: UnpackVariant[5](Outer/One) + 19: Call inner_value(Inner): u64 + 20: StLoc[2](loc1: u64) + 21: Branch(37) +B4: + 22: MoveLoc[1](loc0: &Outer) + 23: TestVariant[6](Outer/Two) + 24: BrFalse(35) +B5: + 25: MoveLoc[0](Arg0: Outer) + 26: UnpackVariant[6](Outer/Two) + 27: StLoc[3](loc2: Box) + 28: Call inner_value(Inner): u64 + 29: ImmBorrowLoc[3](loc2: Box) + 30: ImmBorrowField[0](Box.x: u64) + 31: ReadRef + 32: Add + 33: StLoc[2](loc1: u64) + 34: Branch(37) +B6: + 35: LdU64(15621340914461310977) + 36: Abort +B7: + 37: MoveLoc[2](loc1: u64) + 38: Ret +} +public outer_value_nested(Arg0: Outer): u64 /* def_idx: 6 */ { +L1: loc0: &Outer +L2: loc1: u64 +L3: loc2: Box +B0: + 0: ImmBorrowLoc[0](Arg0: Outer) + 1: StLoc[1](loc0: &Outer) + 2: CopyLoc[1](loc0: &Outer) + 3: TestVariant[4](Outer/None) + 4: BrFalse(12) +B1: + 5: MoveLoc[1](loc0: &Outer) + 6: Pop + 7: MoveLoc[0](Arg0: Outer) + 8: UnpackVariant[4](Outer/None) + 9: LdU64(0) + 10: StLoc[2](loc1: u64) + 11: Branch(51) +B2: + 12: CopyLoc[1](loc0: &Outer) + 13: TestVariant[5](Outer/One) + 14: BrFalse(26) +B3: + 15: CopyLoc[1](loc0: &Outer) + 16: ImmBorrowVariantField[1](One.i: Inner) + 17: TestVariant[0](Inner/Inner1) + 18: BrFalse(26) +B4: + 19: MoveLoc[1](loc0: &Outer) + 20: Pop + 21: MoveLoc[0](Arg0: Outer) + 22: UnpackVariant[5](Outer/One) + 23: UnpackVariant[0](Inner/Inner1) + 24: StLoc[2](loc1: u64) + 25: Branch(51) +B5: + 26: CopyLoc[1](loc0: &Outer) + 27: TestVariant[5](Outer/One) + 28: BrFalse(36) +B6: + 29: MoveLoc[1](loc0: &Outer) + 30: Pop + 31: MoveLoc[0](Arg0: Outer) + 32: UnpackVariant[5](Outer/One) + 33: Call inner_value(Inner): u64 + 34: StLoc[2](loc1: u64) + 35: Branch(51) +B7: + 36: MoveLoc[1](loc0: &Outer) + 37: TestVariant[6](Outer/Two) + 38: BrFalse(49) +B8: + 39: MoveLoc[0](Arg0: Outer) + 40: UnpackVariant[6](Outer/Two) + 41: StLoc[3](loc2: Box) + 42: Call inner_value(Inner): u64 + 43: ImmBorrowLoc[3](loc2: Box) + 44: ImmBorrowField[0](Box.x: u64) + 45: ReadRef + 46: Add + 47: StLoc[2](loc1: u64) + 48: Branch(51) +B9: + 49: LdU64(15621340914461310977) + 50: Abort +B10: + 51: MoveLoc[2](loc1: u64) + 52: Ret +} +public outer_value_with_cond(Arg0: Outer): u64 /* def_idx: 7 */ { +L1: loc0: &Outer +L2: loc1: u64 +L3: loc2: &Inner +L4: loc3: Box +B0: + 0: ImmBorrowLoc[0](Arg0: Outer) + 1: StLoc[1](loc0: &Outer) + 2: CopyLoc[1](loc0: &Outer) + 3: TestVariant[4](Outer/None) + 4: BrFalse(12) +B1: + 5: MoveLoc[1](loc0: &Outer) + 6: Pop + 7: MoveLoc[0](Arg0: Outer) + 8: UnpackVariant[4](Outer/None) + 9: LdU64(0) + 10: StLoc[2](loc1: u64) + 11: Branch(55) +B2: + 12: CopyLoc[1](loc0: &Outer) + 13: TestVariant[5](Outer/One) + 14: BrFalse(30) +B3: + 15: CopyLoc[1](loc0: &Outer) + 16: ImmBorrowVariantField[1](One.i: Inner) + 17: StLoc[3](loc2: &Inner) + 18: MoveLoc[3](loc2: &Inner) + 19: Call is_inner1(&Inner): bool + 20: BrFalse(30) +B4: + 21: MoveLoc[1](loc0: &Outer) + 22: Pop + 23: MoveLoc[0](Arg0: Outer) + 24: UnpackVariant[5](Outer/One) + 25: Call inner_value(Inner): u64 + 26: LdU64(2) + 27: Mod + 28: StLoc[2](loc1: u64) + 29: Branch(55) +B5: + 30: CopyLoc[1](loc0: &Outer) + 31: TestVariant[5](Outer/One) + 32: BrFalse(40) +B6: + 33: MoveLoc[1](loc0: &Outer) + 34: Pop + 35: MoveLoc[0](Arg0: Outer) + 36: UnpackVariant[5](Outer/One) + 37: Call inner_value(Inner): u64 + 38: StLoc[2](loc1: u64) + 39: Branch(55) +B7: + 40: MoveLoc[1](loc0: &Outer) + 41: TestVariant[6](Outer/Two) + 42: BrFalse(53) +B8: + 43: MoveLoc[0](Arg0: Outer) + 44: UnpackVariant[6](Outer/Two) + 45: StLoc[4](loc3: Box) + 46: Call inner_value(Inner): u64 + 47: ImmBorrowLoc[4](loc3: Box) + 48: ImmBorrowField[0](Box.x: u64) + 49: ReadRef + 50: Add + 51: StLoc[2](loc1: u64) + 52: Branch(55) +B9: + 53: LdU64(15621340914461310977) + 54: Abort +B10: + 55: MoveLoc[2](loc1: u64) + 56: Ret +} +public outer_value_with_cond_ref(Arg0: &Outer): bool /* def_idx: 8 */ { +L1: loc0: bool +B0: + 0: CopyLoc[0](Arg0: &Outer) + 1: TestVariant[4](Outer/None) + 2: BrFalse(8) +B1: + 3: MoveLoc[0](Arg0: &Outer) + 4: Pop + 5: LdFalse + 6: StLoc[1](loc0: bool) + 7: Branch(40) +B2: + 8: CopyLoc[0](Arg0: &Outer) + 9: TestVariant[5](Outer/One) + 10: BrFalse(20) +B3: + 11: CopyLoc[0](Arg0: &Outer) + 12: ImmBorrowVariantField[1](One.i: Inner) + 13: Call is_inner1(&Inner): bool + 14: BrFalse(20) +B4: + 15: MoveLoc[0](Arg0: &Outer) + 16: Pop + 17: LdTrue + 18: StLoc[1](loc0: bool) + 19: Branch(40) +B5: + 20: CopyLoc[0](Arg0: &Outer) + 21: TestVariant[5](Outer/One) + 22: BrFalse(28) +B6: + 23: MoveLoc[0](Arg0: &Outer) + 24: ImmBorrowVariantField[1](One.i: Inner) + 25: Call is_inner1(&Inner): bool + 26: StLoc[1](loc0: bool) + 27: Branch(40) +B7: + 28: CopyLoc[0](Arg0: &Outer) + 29: TestVariant[6](Outer/Two) + 30: BrFalse(36) +B8: + 31: MoveLoc[0](Arg0: &Outer) + 32: ImmBorrowVariantField[2](Two.i: Inner) + 33: Call is_inner1(&Inner): bool + 34: StLoc[1](loc0: bool) + 35: Branch(40) +B9: + 36: MoveLoc[0](Arg0: &Outer) + 37: Pop + 38: LdU64(15621340914461310977) + 39: Abort +B10: + 40: MoveLoc[1](loc0: bool) + 41: Ret +} +select_common_fields(Arg0: CommonFields): u64 /* def_idx: 9 */ { +L1: loc0: &CommonFields +L2: loc1: bool +L3: loc2: u64 +L4: loc3: u64 +B0: + 0: ImmBorrowLoc[0](Arg0: CommonFields) + 1: ImmBorrowVariantField[3](Foo.x|Bar.x: u64) + 2: ReadRef + 3: ImmBorrowLoc[0](Arg0: CommonFields) + 4: StLoc[1](loc0: &CommonFields) + 5: CopyLoc[1](loc0: &CommonFields) + 6: TestVariant[7](CommonFields/Foo) + 7: StLoc[2](loc1: bool) + 8: StLoc[3](loc2: u64) + 9: MoveLoc[2](loc1: bool) + 10: BrFalse(18) +B1: + 11: MoveLoc[1](loc0: &CommonFields) + 12: Pop + 13: MoveLoc[0](Arg0: CommonFields) + 14: UnpackVariant[7](CommonFields/Foo) + 15: StLoc[4](loc3: u64) + 16: Pop + 17: Branch(28) +B2: + 18: MoveLoc[1](loc0: &CommonFields) + 19: TestVariant[8](CommonFields/Bar) + 20: BrFalse(26) +B3: + 21: MoveLoc[0](Arg0: CommonFields) + 22: UnpackVariant[8](CommonFields/Bar) + 23: StLoc[4](loc3: u64) + 24: Pop + 25: Branch(28) +B4: + 26: LdU64(15621340914461310977) + 27: Abort +B5: + 28: MoveLoc[3](loc2: u64) + 29: MoveLoc[4](loc3: u64) + 30: Add + 31: Ret +} +} +============ bytecode verification succeeded ======== diff --git a/third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.move b/third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.move new file mode 100644 index 0000000000000..12e5a72b6848a --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.move @@ -0,0 +1,114 @@ +module 0xc0ffee::m { + + enum Inner { + Inner1{ x: u64 } + Inner2{ x: u64, y: u64 } + } + + struct Box has drop { + x: u64 + } + + enum Outer { + None, + One{i: Inner}, + Two{i: Inner, b: Box}, + } + + /// Simple matching + public fun inner_value(self: Inner): u64 { + match (self) { + Inner1{x} => x, + Inner2{x, y} => x + y + } + } + + /// Matching with wildcard and reference + public fun is_inner1(self: &Inner): bool { + match (self) { + Inner1{x: _} => true, + _ => false + } + } + + /// Matching which delegates ownership + public fun outer_value(o: Outer): u64 { + match (o) { + None => 0, + // `i` is moved and consumed by `inner_value` + One{i} => i.inner_value(), + Two{i, b} => i.inner_value() + b.x + } + } + + /// Nested matching with delegation + public fun outer_value_nested(o: Outer): u64 { + match (o) { + None => 0, + // Nested match will require multiple probing steps + One{i: Inner::Inner1{x}} => x, + One{i} => i.inner_value(), + Two{i, b} => i.inner_value() + b.x + } + } + + /// Matching with condition + public fun outer_value_with_cond(o: Outer): u64 { + match (o) { + None => 0, + // Match with condition requires probing and conversion from 'Deref(Borrow(x))` to `x`. + One{i} if i.is_inner1() => i.inner_value() % 2, + One{i} => i.inner_value(), + Two{i, b} => i.inner_value() + b.x + } + } + + /// Matching with condition with references and wildcard + public fun outer_value_with_cond_ref(o: &Outer): bool { + match (o) { + None => false, + One{i} if i.is_inner1() => true, + One{i} => i.is_inner1(), + Two{i, b: _} => i.is_inner1() + } + } + + /// Matching with abilities and generics + enum Option has drop { + None, + Some{value: A} + } + + public fun is_some(x: &Option): bool { + match (x) { + None => false, + Some{value: _} => true + } + } + + public fun is_some_specialized(x: &Option>): bool { + match (x) { + None => false, + Some{value: Option::None} => false, + Some{value: Option::Some{value: _}} => true, + } + } + + public fun is_some_dropped(x: Option): bool { + match (x) { + None => false, + _ => true + } + } + + // Common fields + enum CommonFields { + Foo{x: u64, y: u64}, + Bar{z: u64, x: u64} + } + + fun select_common_fields(s: CommonFields): u64 { + s.x + (match (s) { Foo{x: _, y} => y, Bar{z, x: _} => z }) + } + +} diff --git a/third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.opt.exp b/third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.opt.exp new file mode 100644 index 0000000000000..999d16e1e3db8 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/file-format-generator/struct_variants.opt.exp @@ -0,0 +1,492 @@ + +============ disassembled file-format ================== +// Move bytecode v7 +module c0ffee.m { +struct Box has drop { + x: u64 +} +enum CommonFields { + Foo{ + x: u64, + y: u64 + }, + Bar{ + x: u64, + z: u64 + } +} +enum Inner { + Inner1{ + x: u64 + }, + Inner2{ + x: u64, + y: u64 + } +} +enum Option has drop { + None{ + + }, + Some{ + value: Ty0 + } +} +enum Outer { + None{ + + }, + One{ + i: Inner + }, + Two{ + i: Inner, + b: Box + } +} + +public inner_value(Arg0: Inner): u64 /* def_idx: 0 */ { +L1: loc0: &Inner +L2: loc1: u64 +B0: + 0: ImmBorrowLoc[0](Arg0: Inner) + 1: StLoc[1](loc0: &Inner) + 2: CopyLoc[1](loc0: &Inner) + 3: TestVariant[0](Inner/Inner1) + 4: BrFalse(11) +B1: + 5: MoveLoc[1](loc0: &Inner) + 6: Pop + 7: MoveLoc[0](Arg0: Inner) + 8: UnpackVariant[0](Inner/Inner1) + 9: StLoc[2](loc1: u64) + 10: Branch(21) +B2: + 11: MoveLoc[1](loc0: &Inner) + 12: TestVariant[1](Inner/Inner2) + 13: BrFalse(19) +B3: + 14: MoveLoc[0](Arg0: Inner) + 15: UnpackVariant[1](Inner/Inner2) + 16: Add + 17: StLoc[2](loc1: u64) + 18: Branch(21) +B4: + 19: LdU64(15621340914461310977) + 20: Abort +B5: + 21: MoveLoc[2](loc1: u64) + 22: Ret +} +public is_inner1(Arg0: &Inner): bool /* def_idx: 1 */ { +L1: loc0: bool +B0: + 0: CopyLoc[0](Arg0: &Inner) + 1: TestVariant[0](Inner/Inner1) + 2: BrFalse(8) +B1: + 3: MoveLoc[0](Arg0: &Inner) + 4: Pop + 5: LdTrue + 6: StLoc[1](loc0: bool) + 7: Branch(13) +B2: + 8: MoveLoc[0](Arg0: &Inner) + 9: Pop + 10: LdFalse + 11: StLoc[1](loc0: bool) + 12: Branch(13) +B3: + 13: MoveLoc[1](loc0: bool) + 14: Ret +} +public is_some(Arg0: &Option): bool /* def_idx: 2 */ { +L1: loc0: bool +B0: + 0: CopyLoc[0](Arg0: &Option) + 1: TestVariantGeneric[0](Option/None) + 2: BrFalse(8) +B1: + 3: MoveLoc[0](Arg0: &Option) + 4: Pop + 5: LdFalse + 6: StLoc[1](loc0: bool) + 7: Branch(16) +B2: + 8: MoveLoc[0](Arg0: &Option) + 9: TestVariantGeneric[1](Option/Some) + 10: BrFalse(14) +B3: + 11: LdTrue + 12: StLoc[1](loc0: bool) + 13: Branch(16) +B4: + 14: LdU64(15621340914461310977) + 15: Abort +B5: + 16: MoveLoc[1](loc0: bool) + 17: Ret +} +public is_some_dropped(Arg0: Option): bool /* def_idx: 3 */ { +L1: loc0: bool +B0: + 0: ImmBorrowLoc[0](Arg0: Option) + 1: TestVariantGeneric[0](Option/None) + 2: BrFalse(8) +B1: + 3: MoveLoc[0](Arg0: Option) + 4: UnpackVariantGeneric[0](Option/None) + 5: LdFalse + 6: StLoc[1](loc0: bool) + 7: Branch(11) +B2: + 8: LdTrue + 9: StLoc[1](loc0: bool) + 10: Branch(11) +B3: + 11: MoveLoc[1](loc0: bool) + 12: Ret +} +public is_some_specialized(Arg0: &Option>): bool /* def_idx: 4 */ { +L1: loc0: bool +B0: + 0: CopyLoc[0](Arg0: &Option>) + 1: TestVariantGeneric[2](Option/None>) + 2: BrFalse(8) +B1: + 3: MoveLoc[0](Arg0: &Option>) + 4: Pop + 5: LdFalse + 6: StLoc[1](loc0: bool) + 7: Branch(32) +B2: + 8: CopyLoc[0](Arg0: &Option>) + 9: TestVariantGeneric[3](Option/Some>) + 10: BrFalse(20) +B3: + 11: CopyLoc[0](Arg0: &Option>) + 12: ImmBorrowVariantFieldGeneric[0](Some.value: Ty0) + 13: TestVariantGeneric[4](Option/None) + 14: BrFalse(20) +B4: + 15: MoveLoc[0](Arg0: &Option>) + 16: Pop + 17: LdFalse + 18: StLoc[1](loc0: bool) + 19: Branch(32) +B5: + 20: CopyLoc[0](Arg0: &Option>) + 21: TestVariantGeneric[3](Option/Some>) + 22: BrFalse(30) +B6: + 23: MoveLoc[0](Arg0: &Option>) + 24: ImmBorrowVariantFieldGeneric[0](Some.value: Ty0) + 25: TestVariantGeneric[5](Option/Some) + 26: BrFalse(30) +B7: + 27: LdTrue + 28: StLoc[1](loc0: bool) + 29: Branch(32) +B8: + 30: LdU64(15621340914461310977) + 31: Abort +B9: + 32: MoveLoc[1](loc0: bool) + 33: Ret +} +public outer_value(Arg0: Outer): u64 /* def_idx: 5 */ { +L1: loc0: &Outer +L2: loc1: u64 +L3: loc2: Box +B0: + 0: ImmBorrowLoc[0](Arg0: Outer) + 1: StLoc[1](loc0: &Outer) + 2: CopyLoc[1](loc0: &Outer) + 3: TestVariant[4](Outer/None) + 4: BrFalse(12) +B1: + 5: MoveLoc[1](loc0: &Outer) + 6: Pop + 7: MoveLoc[0](Arg0: Outer) + 8: UnpackVariant[4](Outer/None) + 9: LdU64(0) + 10: StLoc[2](loc1: u64) + 11: Branch(37) +B2: + 12: CopyLoc[1](loc0: &Outer) + 13: TestVariant[5](Outer/One) + 14: BrFalse(22) +B3: + 15: MoveLoc[1](loc0: &Outer) + 16: Pop + 17: MoveLoc[0](Arg0: Outer) + 18: UnpackVariant[5](Outer/One) + 19: Call inner_value(Inner): u64 + 20: StLoc[2](loc1: u64) + 21: Branch(37) +B4: + 22: MoveLoc[1](loc0: &Outer) + 23: TestVariant[6](Outer/Two) + 24: BrFalse(35) +B5: + 25: MoveLoc[0](Arg0: Outer) + 26: UnpackVariant[6](Outer/Two) + 27: StLoc[3](loc2: Box) + 28: Call inner_value(Inner): u64 + 29: ImmBorrowLoc[3](loc2: Box) + 30: ImmBorrowField[0](Box.x: u64) + 31: ReadRef + 32: Add + 33: StLoc[2](loc1: u64) + 34: Branch(37) +B6: + 35: LdU64(15621340914461310977) + 36: Abort +B7: + 37: MoveLoc[2](loc1: u64) + 38: Ret +} +public outer_value_nested(Arg0: Outer): u64 /* def_idx: 6 */ { +L1: loc0: &Outer +L2: loc1: u64 +L3: loc2: Box +B0: + 0: ImmBorrowLoc[0](Arg0: Outer) + 1: StLoc[1](loc0: &Outer) + 2: CopyLoc[1](loc0: &Outer) + 3: TestVariant[4](Outer/None) + 4: BrFalse(12) +B1: + 5: MoveLoc[1](loc0: &Outer) + 6: Pop + 7: MoveLoc[0](Arg0: Outer) + 8: UnpackVariant[4](Outer/None) + 9: LdU64(0) + 10: StLoc[2](loc1: u64) + 11: Branch(51) +B2: + 12: CopyLoc[1](loc0: &Outer) + 13: TestVariant[5](Outer/One) + 14: BrFalse(26) +B3: + 15: CopyLoc[1](loc0: &Outer) + 16: ImmBorrowVariantField[1](One.i: Inner) + 17: TestVariant[0](Inner/Inner1) + 18: BrFalse(26) +B4: + 19: MoveLoc[1](loc0: &Outer) + 20: Pop + 21: MoveLoc[0](Arg0: Outer) + 22: UnpackVariant[5](Outer/One) + 23: UnpackVariant[0](Inner/Inner1) + 24: StLoc[2](loc1: u64) + 25: Branch(51) +B5: + 26: CopyLoc[1](loc0: &Outer) + 27: TestVariant[5](Outer/One) + 28: BrFalse(36) +B6: + 29: MoveLoc[1](loc0: &Outer) + 30: Pop + 31: MoveLoc[0](Arg0: Outer) + 32: UnpackVariant[5](Outer/One) + 33: Call inner_value(Inner): u64 + 34: StLoc[2](loc1: u64) + 35: Branch(51) +B7: + 36: MoveLoc[1](loc0: &Outer) + 37: TestVariant[6](Outer/Two) + 38: BrFalse(49) +B8: + 39: MoveLoc[0](Arg0: Outer) + 40: UnpackVariant[6](Outer/Two) + 41: StLoc[3](loc2: Box) + 42: Call inner_value(Inner): u64 + 43: ImmBorrowLoc[3](loc2: Box) + 44: ImmBorrowField[0](Box.x: u64) + 45: ReadRef + 46: Add + 47: StLoc[2](loc1: u64) + 48: Branch(51) +B9: + 49: LdU64(15621340914461310977) + 50: Abort +B10: + 51: MoveLoc[2](loc1: u64) + 52: Ret +} +public outer_value_with_cond(Arg0: Outer): u64 /* def_idx: 7 */ { +L1: loc0: &Outer +L2: loc1: u64 +L3: loc2: Box +B0: + 0: ImmBorrowLoc[0](Arg0: Outer) + 1: StLoc[1](loc0: &Outer) + 2: CopyLoc[1](loc0: &Outer) + 3: TestVariant[4](Outer/None) + 4: BrFalse(12) +B1: + 5: MoveLoc[1](loc0: &Outer) + 6: Pop + 7: MoveLoc[0](Arg0: Outer) + 8: UnpackVariant[4](Outer/None) + 9: LdU64(0) + 10: StLoc[2](loc1: u64) + 11: Branch(53) +B2: + 12: CopyLoc[1](loc0: &Outer) + 13: TestVariant[5](Outer/One) + 14: BrFalse(28) +B3: + 15: CopyLoc[1](loc0: &Outer) + 16: ImmBorrowVariantField[1](One.i: Inner) + 17: Call is_inner1(&Inner): bool + 18: BrFalse(28) +B4: + 19: MoveLoc[1](loc0: &Outer) + 20: Pop + 21: MoveLoc[0](Arg0: Outer) + 22: UnpackVariant[5](Outer/One) + 23: Call inner_value(Inner): u64 + 24: LdU64(2) + 25: Mod + 26: StLoc[2](loc1: u64) + 27: Branch(53) +B5: + 28: CopyLoc[1](loc0: &Outer) + 29: TestVariant[5](Outer/One) + 30: BrFalse(38) +B6: + 31: MoveLoc[1](loc0: &Outer) + 32: Pop + 33: MoveLoc[0](Arg0: Outer) + 34: UnpackVariant[5](Outer/One) + 35: Call inner_value(Inner): u64 + 36: StLoc[2](loc1: u64) + 37: Branch(53) +B7: + 38: MoveLoc[1](loc0: &Outer) + 39: TestVariant[6](Outer/Two) + 40: BrFalse(51) +B8: + 41: MoveLoc[0](Arg0: Outer) + 42: UnpackVariant[6](Outer/Two) + 43: StLoc[3](loc2: Box) + 44: Call inner_value(Inner): u64 + 45: ImmBorrowLoc[3](loc2: Box) + 46: ImmBorrowField[0](Box.x: u64) + 47: ReadRef + 48: Add + 49: StLoc[2](loc1: u64) + 50: Branch(53) +B9: + 51: LdU64(15621340914461310977) + 52: Abort +B10: + 53: MoveLoc[2](loc1: u64) + 54: Ret +} +public outer_value_with_cond_ref(Arg0: &Outer): bool /* def_idx: 8 */ { +L1: loc0: bool +B0: + 0: CopyLoc[0](Arg0: &Outer) + 1: TestVariant[4](Outer/None) + 2: BrFalse(8) +B1: + 3: MoveLoc[0](Arg0: &Outer) + 4: Pop + 5: LdFalse + 6: StLoc[1](loc0: bool) + 7: Branch(40) +B2: + 8: CopyLoc[0](Arg0: &Outer) + 9: TestVariant[5](Outer/One) + 10: BrFalse(20) +B3: + 11: CopyLoc[0](Arg0: &Outer) + 12: ImmBorrowVariantField[1](One.i: Inner) + 13: Call is_inner1(&Inner): bool + 14: BrFalse(20) +B4: + 15: MoveLoc[0](Arg0: &Outer) + 16: Pop + 17: LdTrue + 18: StLoc[1](loc0: bool) + 19: Branch(40) +B5: + 20: CopyLoc[0](Arg0: &Outer) + 21: TestVariant[5](Outer/One) + 22: BrFalse(28) +B6: + 23: MoveLoc[0](Arg0: &Outer) + 24: ImmBorrowVariantField[1](One.i: Inner) + 25: Call is_inner1(&Inner): bool + 26: StLoc[1](loc0: bool) + 27: Branch(40) +B7: + 28: CopyLoc[0](Arg0: &Outer) + 29: TestVariant[6](Outer/Two) + 30: BrFalse(36) +B8: + 31: MoveLoc[0](Arg0: &Outer) + 32: ImmBorrowVariantField[2](Two.i: Inner) + 33: Call is_inner1(&Inner): bool + 34: StLoc[1](loc0: bool) + 35: Branch(40) +B9: + 36: MoveLoc[0](Arg0: &Outer) + 37: Pop + 38: LdU64(15621340914461310977) + 39: Abort +B10: + 40: MoveLoc[1](loc0: bool) + 41: Ret +} +select_common_fields(Arg0: CommonFields): u64 /* def_idx: 9 */ { +L1: loc0: &CommonFields +L2: loc1: bool +L3: loc2: u64 +L4: loc3: u64 +B0: + 0: ImmBorrowLoc[0](Arg0: CommonFields) + 1: StLoc[1](loc0: &CommonFields) + 2: MoveLoc[1](loc0: &CommonFields) + 3: ImmBorrowVariantField[3](Foo.x|Bar.x: u64) + 4: ReadRef + 5: ImmBorrowLoc[0](Arg0: CommonFields) + 6: StLoc[1](loc0: &CommonFields) + 7: CopyLoc[1](loc0: &CommonFields) + 8: TestVariant[7](CommonFields/Foo) + 9: StLoc[2](loc1: bool) + 10: StLoc[3](loc2: u64) + 11: MoveLoc[2](loc1: bool) + 12: BrFalse(20) +B1: + 13: MoveLoc[1](loc0: &CommonFields) + 14: Pop + 15: MoveLoc[0](Arg0: CommonFields) + 16: UnpackVariant[7](CommonFields/Foo) + 17: StLoc[4](loc3: u64) + 18: Pop + 19: Branch(30) +B2: + 20: MoveLoc[1](loc0: &CommonFields) + 21: TestVariant[8](CommonFields/Bar) + 22: BrFalse(28) +B3: + 23: MoveLoc[0](Arg0: CommonFields) + 24: UnpackVariant[8](CommonFields/Bar) + 25: StLoc[4](loc3: u64) + 26: Pop + 27: Branch(30) +B4: + 28: LdU64(15621340914461310977) + 29: Abort +B5: + 30: MoveLoc[3](loc2: u64) + 31: MoveLoc[4](loc3: u64) + 32: Add + 33: Ret +} +} +============ bytecode verification succeeded ======== diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.move b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.move new file mode 100644 index 0000000000000..06382f7161d17 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.move @@ -0,0 +1,43 @@ +//# publish +module 0x42::m { + + enum Data has drop { + V1{x: u64}, + V2{x: u64, y: bool} + } + + fun get_y(self: &Data): bool { + match (self) { + V2{x: _, y} => *y, + _ => abort 33 + } + } + + fun test_get_x_v1(): u64 { + let d = Data::V1{x: 43}; + d.x + } + + fun test_get_x_v2(): u64 { + let d = Data::V2{x: 43, y: false}; + d.x + } + + fun test_get_y_v1(): bool { + let d = Data::V1{x: 43}; + d.get_y() + } + + fun test_get_y_v2(): bool { + let d = Data::V2{x: 43, y: true}; + d.get_y() + } +} + +//# run 0x42::m::test_get_x_v1 + +//# run 0x42::m::test_get_x_v2 + +//# run 0x42::m::test_get_y_v1 + +//# run 0x42::m::test_get_y_v2 diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.no-optimize.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.no-optimize.exp new file mode 100644 index 0000000000000..fc020d774df59 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.no-optimize.exp @@ -0,0 +1,19 @@ +processed 5 tasks + +task 1 'run'. lines 37-37: +return values: 43 + +task 2 'run'. lines 39-39: +return values: 43 + +task 3 'run'. lines 41-41: +Error: Function execution failed with VMError: { + major_status: ABORTED, + sub_status: Some(33), + location: 0x42::m, + indices: [], + offsets: [(FunctionDefinitionIndex(0), 13)], +} + +task 4 'run'. lines 43-43: +return values: true diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.optimize-no-simplify.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.optimize-no-simplify.exp new file mode 100644 index 0000000000000..fdb1dbad56255 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.optimize-no-simplify.exp @@ -0,0 +1,19 @@ +processed 5 tasks + +task 1 'run'. lines 37-37: +return values: 43 + +task 2 'run'. lines 39-39: +return values: 43 + +task 3 'run'. lines 41-41: +Error: Function execution failed with VMError: { + major_status: ABORTED, + sub_status: Some(33), + location: 0x42::m, + indices: [], + offsets: [(FunctionDefinitionIndex(0), 11)], +} + +task 4 'run'. lines 43-43: +return values: true diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.optimize.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.optimize.exp new file mode 100644 index 0000000000000..fdb1dbad56255 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.optimize.exp @@ -0,0 +1,19 @@ +processed 5 tasks + +task 1 'run'. lines 37-37: +return values: 43 + +task 2 'run'. lines 39-39: +return values: 43 + +task 3 'run'. lines 41-41: +Error: Function execution failed with VMError: { + major_status: ABORTED, + sub_status: Some(33), + location: 0x42::m, + indices: [], + offsets: [(FunctionDefinitionIndex(0), 11)], +} + +task 4 'run'. lines 43-43: +return values: true diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_matching.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_matching.exp new file mode 100644 index 0000000000000..3e25bc6106615 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_matching.exp @@ -0,0 +1,43 @@ +processed 15 tasks + +task 1 'run'. lines 175-175: +return values: true + +task 2 'run'. lines 177-177: +return values: false + +task 3 'run'. lines 179-179: +return values: 7 + +task 4 'run'. lines 181-181: +return values: 0 + +task 5 'run'. lines 183-183: +return values: 3 + +task 6 'run'. lines 185-185: +return values: 8 + +task 7 'run'. lines 187-187: +return values: 27 + +task 8 'run'. lines 189-189: +return values: 12 + +task 9 'run'. lines 191-191: +return values: 1 + +task 10 'run'. lines 193-193: +return values: true + +task 11 'run'. lines 195-195: +return values: false + +task 12 'run'. lines 197-197: +return values: true + +task 13 'run'. lines 199-199: +return values: false + +task 14 'run'. lines 201-201: +return values: true diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_matching.move b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_matching.move new file mode 100644 index 0000000000000..a20260e81dc5a --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_matching.move @@ -0,0 +1,201 @@ +//# publish +module 0x42::m { + + enum Inner has drop { + Inner1{ x: u64 } + Inner2{ x: u64, y: u64 } + } + + struct Box has drop { + x: u64 + } + + enum Outer has drop { + None, + One{i: Inner}, + Two{i: Inner, b: Box}, + } + + /// Simple matching + public fun inner_value(self: Inner): u64 { + match (self) { + Inner1{x} => x, + Inner2{x, y} => x + y + } + } + + /// Matching with wildcard and reference + public fun is_inner1(self: &Inner): bool { + match (self) { + Inner1{x: _} => true, + _ => false + } + } + + /// Matching which delegates ownership + public fun outer_value(o: Outer): u64 { + match (o) { + None => 0, + // `i` is moved and consumed by `inner_value` + One{i} => i.inner_value(), + Two{i, b} => i.inner_value() + b.x + } + } + + /// Nested matching with delegation + public fun outer_value_nested(o: Outer): u64 { + match (o) { + None => 0, + // Nested match will require multiple probing steps + One{i: Inner::Inner1{x}} => x, + One{i} => i.inner_value(), + Two{i, b} => i.inner_value() + b.x + } + } + + /// Matching with condition + public fun outer_value_with_cond(o: Outer): u64 { + match (o) { + None => 0, + // Match with condition requires probing and conversion from 'Deref(Borrow(x))` to `x`. + One{i} if i.is_inner1() => i.inner_value() % 2, + One{i} => i.inner_value(), + Two{i, b} => i.inner_value() + b.x + } + } + + /// Matching with condition with references and wildcard + public fun outer_value_with_cond_ref(o: &Outer): bool { + match (o) { + None => false, + One{i} if i.is_inner1() => true, + One{i} => i.is_inner1(), + Two{i, b: _} => i.is_inner1() + } + } + + /// Matching with abilities and generics + enum Option has drop { + None, + Some{value: A} + } + + public fun is_some(x: &Option): bool { + match (x) { + None => false, + Some{value: _} => true + } + } + + public fun is_some_specialized(x: &Option>): bool { + match (x) { + None => false, + Some{value: Option::None} => false, + Some{value: Option::Some{value: _}} => true, + } + } + + public fun is_some_dropped(x: Option): bool { + match (x) { + None => false, + _ => true + } + } + + // Common fields + enum CommonFields { + Foo{x: u64, y: u64}, + Bar{z: u64, x: u64} + } + + fun select_common_fields(s: CommonFields): u64 { + s.x + (match (s) { Foo{x: _, y} => y, Bar{z, x: _} => z }) + } + + // ------------------- + // Test entry points + + fun t1_is_inner1(): bool { + is_inner1(&Inner::Inner1{x: 2}) + } + + fun t2_is_inner1(): bool { + is_inner1(&Inner::Inner2{x: 2, y: 3}) + } + + fun t1_inner_value(): u64 { + inner_value(Inner::Inner2{x: 2, y: 5}) + } + + fun t1_outer_value(): u64 { + outer_value(Outer::None{}) + } + + fun t2_outer_value(): u64 { + outer_value(Outer::One{i: Inner::Inner2{x: 1, y: 2}}) + } + + fun t3_outer_value(): u64 { + outer_value(Outer::Two{i: Inner::Inner1{x: 1}, b: Box{x: 7}}) + } + + fun t1_outer_value_nested(): u64 { + outer_value_nested(Outer::One{i: Inner::Inner1{x: 27}}) + } + + fun t2_outer_value_nested(): u64 { + outer_value_nested(Outer::Two{i: Inner::Inner1{x: 5}, b: Box{x: 7}}) + } + + fun t1_outer_value_with_cond(): u64 { + outer_value_with_cond(Outer::One{i: Inner::Inner1{x: 43}}) + } + + fun t1_outer_value_with_cond_ref(): bool { + outer_value_with_cond_ref(&Outer::One{i: Inner::Inner1{x: 43}}) + } + + fun t1_is_some(): bool { + is_some(&Option::None{}) + } + + fun t2_is_some(): bool { + is_some(&Option::Some{value: 3}) + } + + fun t1_is_some_specialized(): bool { + is_some_specialized(&Option::Some{value: Option::None{}}) + } + + fun t2_is_some_specialized(): bool { + is_some_specialized(&Option::Some{value: Option::Some{value: 1}}) + } +} + +//# run 0x42::m::t1_is_inner1 + +//# run 0x42::m::t2_is_inner1 + +//# run 0x42::m::t1_inner_value + +//# run 0x42::m::t1_outer_value + +//# run 0x42::m::t2_outer_value + +//# run 0x42::m::t3_outer_value + +//# run 0x42::m::t1_outer_value_nested + +//# run 0x42::m::t2_outer_value_nested + +//# run 0x42::m::t1_outer_value_with_cond + +//# run 0x42::m::t1_outer_value_with_cond_ref + +//# run 0x42::m::t1_is_some + +//# run 0x42::m::t2_is_some + +//# run 0x42::m::t1_is_some_specialized + +//# run 0x42::m::t2_is_some_specialized diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/tests.rs b/third_party/move/move-compiler-v2/transactional-tests/tests/tests.rs index 121249ce8f792..b138ea40a66b4 100644 --- a/third_party/move/move-compiler-v2/transactional-tests/tests/tests.rs +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/tests.rs @@ -98,6 +98,8 @@ const SEPARATE_BASELINE: &[&str] = &[ "no-v1-comparison/print_bytecode.move", // The output of the tests could be different depending on the language version "/operator_eval/", + // Creates different code if optimized + "no-v1-comparison/enum/enum_field_select.move", ]; fn get_config_by_name(name: &str) -> TestConfig { diff --git a/third_party/move/move-compiler/src/interface_generator.rs b/third_party/move/move-compiler/src/interface_generator.rs index 3c9d3ce5e468a..4e0e3fd118b07 100644 --- a/third_party/move/move-compiler/src/interface_generator.rs +++ b/third_party/move/move-compiler/src/interface_generator.rs @@ -201,6 +201,11 @@ fn write_struct_def(ctx: &mut Context, sdef: &StructDefinition) -> String { return out; }, StructFieldInformation::Declared(fields) => fields, + StructFieldInformation::DeclaredVariants(..) => { + // TODO(#13806): consider implement if interface generator will be + // reused once v1 compiler retires + panic!("variants not yet supported by interface generator") + }, }; for field in fields { push_line!( diff --git a/third_party/move/move-core/types/src/value.rs b/third_party/move/move-core/types/src/value.rs index 3f783987e2cac..5588ef91acf86 100644 --- a/third_party/move/move-core/types/src/value.rs +++ b/third_party/move/move-core/types/src/value.rs @@ -10,7 +10,7 @@ use crate::{ }; use anyhow::{anyhow, bail, Result as AResult}; use serde::{ - de::Error as DeError, + de::{Error as DeError, Unexpected}, ser::{SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple}, Deserialize, Serialize, }; @@ -76,8 +76,10 @@ impl MoveFieldLayout { #[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(any(test, feature = "fuzzing"), derive(arbitrary::Arbitrary))] pub enum MoveStructLayout { - /// The representation used by the MoveVM + /// The representation used by the MoveVM for plain structs Runtime(Vec), + /// The representation used by the MoveVM for struct variants. + RuntimeVariants(Vec>), /// A decorated representation with human-readable field names that can be used by clients WithFields(Vec), /// An even more decorated representation with both types and human-readable field names @@ -85,6 +87,8 @@ pub enum MoveStructLayout { type_: StructTag, fields: Vec, }, + // TODO(#13806): implement decorated versions for variants to support debugging in places + // like `std::debug::print`. Currently, those will show in raw representation. } /// Used to distinguish between aggregators ans snapshots. @@ -252,6 +256,7 @@ impl MoveStruct { .collect(), } }, + (v, _) => v, // already decorated } } @@ -291,6 +296,10 @@ impl MoveStructLayout { Self::Runtime(types) } + pub fn new_variants(types: Vec>) -> Self { + Self::RuntimeVariants(types) + } + pub fn with_fields(types: Vec) -> Self { Self::WithFields(types) } @@ -302,6 +311,10 @@ impl MoveStructLayout { pub fn fields(&self) -> &[MoveTypeLayout] { match self { Self::Runtime(vals) => vals, + Self::RuntimeVariants(_) => { + // TODO(#13806): consider implementing this for variants. For now, return empty. + &[] + }, Self::WithFields(_) | Self::WithTypes { .. } => { // It's not possible to implement this without changing the return type, and some // performance-critical VM serialization code uses the Runtime case of this. @@ -314,6 +327,10 @@ impl MoveStructLayout { pub fn into_fields(self) -> Vec { match self { Self::Runtime(vals) => vals, + Self::RuntimeVariants(_) => { + // TODO(#13806): consider implementing this for variants. For now, return empty. + vec![] + }, Self::WithFields(fields) | Self::WithTypes { fields, .. } => { fields.into_iter().map(|f| f.layout).collect() }, @@ -424,6 +441,53 @@ impl<'d, 'a> serde::de::Visitor<'d> for StructFieldVisitor<'a> { } } +struct StructVariantVisitor<'a>(&'a [Vec]); + +impl<'d, 'a> serde::de::Visitor<'d> for StructVariantVisitor<'a> { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("Enum") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'d>, + { + let mut val = Vec::new(); + + // First deserialize the variant tag + let variant_tag = match seq.next_element_seed(&MoveTypeLayout::U16)? { + Some(elem) => { + let variant_tag = if let MoveValue::U16(tag) = elem { + tag as usize + } else { + // This shouldn't happen but be robust and produce an error + return Err(A::Error::invalid_value( + Unexpected::Other("not a valid struct variant tag"), + &self, + )); + }; + if variant_tag >= self.0.len() { + return Err(A::Error::invalid_value(Unexpected::StructVariant, &self)); + } + val.push(elem); + variant_tag + }, + None => return Err(A::Error::invalid_length(0, &self)), + }; + + // Based on the validated variant tag, we know the field types + for (i, field_type) in self.0[variant_tag].iter().enumerate() { + match seq.next_element_seed(field_type)? { + Some(elem) => val.push(elem), + None => return Err(A::Error::invalid_length(i + 1, &self)), + } + } + Ok(val) + } +} + impl<'d> serde::de::DeserializeSeed<'d> for &MoveFieldLayout { type Value = (Identifier, MoveValue); @@ -448,6 +512,10 @@ impl<'d> serde::de::DeserializeSeed<'d> for &MoveStructLayout { deserializer.deserialize_tuple(layout.len(), StructFieldVisitor(layout))?; Ok(MoveStruct::Runtime(fields)) }, + MoveStructLayout::RuntimeVariants(variants) => { + let fields = deserializer.deserialize_seq(StructVariantVisitor(variants))?; + Ok(MoveStruct::Runtime(fields)) + }, MoveStructLayout::WithFields(layout) => { let fields = deserializer .deserialize_tuple(layout.len(), DecoratedStructFieldVisitor(layout))?; @@ -567,6 +635,15 @@ impl fmt::Display for MoveStructLayout { write!(f, "{}: {}, ", i, l)? } }, + Self::RuntimeVariants(variants) => { + for (i, v) in variants.iter().enumerate() { + write!(f, "#{}{{", i)?; + for (i, l) in v.iter().enumerate() { + write!(f, "{}: {}, ", i, l)? + } + write!(f, "}}")?; + } + }, Self::WithFields(layouts) => { for layout in layouts { write!(f, "{}, ", layout)? @@ -614,7 +691,7 @@ impl TryInto for &MoveStructLayout { fn try_into(self) -> Result { use MoveStructLayout::*; match self { - Runtime(..) | WithFields(..) => bail!( + Runtime(..) | RuntimeVariants(..) | WithFields(..) => bail!( "Invalid MoveTypeLayout -> StructTag conversion--needed MoveLayoutType::WithTypes" ), WithTypes { type_, .. } => Ok(type_.clone()), 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 7ff40790aa63d..b0dc6c9d657ff 100644 --- a/third_party/move/move-core/types/src/vm_status.rs +++ b/third_party/move/move-core/types/src/vm_status.rs @@ -723,12 +723,18 @@ pub enum StatusCode { UNSTABLE_BYTECODE_REJECTED = 1125, PROGRAM_TOO_COMPLEX = 1126, USER_DEFINED_NATIVE_NOT_ALLOWED = 1127, - // Reserved error code for future use - RESERVED_VERIFICATION_ERROR_1 = 1128, - RESERVED_VERIFICATION_ERROR_2 = 1129, - RESERVED_VERIFICATION_ERROR_3 = 1130, - RESERVED_VERIFICATION_ERROR_4 = 1131, + // Bound on the number of struct variants per struct exceeded. + MAX_STRUCT_VARIANTS_REACHED = 1128, + // A variant test has wrong argument type + TEST_VARIANT_TYPE_MISMATCH_ERROR = 1129, + // A variant list is empty + ZERO_VARIANTS_ERROR = 1130, + // Reserved error code for future use + RESERVED_VERIFICATION_ERROR_2 = 1131, + RESERVED_VERIFICATION_ERROR_3 = 1132, + RESERVED_VERIFICATION_ERROR_4 = 1133, + RESERVED_VERIFICATION_ERROR_5 = 1134, // These are errors that the VM might raise if a violation of internal // invariants takes place. @@ -838,11 +844,14 @@ pub enum StatusCode { TOO_MANY_DELAYED_FIELDS = 4036, // Dynamic function call errors. RUNTIME_DISPATCH_ERROR = 4037, + // 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, // Reserved error code for future use. Always keep this buffer of well-defined new codes. - RESERVED_RUNTIME_ERROR_1 = 4038, - RESERVED_RUNTIME_ERROR_2 = 4039, - RESERVED_RUNTIME_ERROR_3 = 4040, - RESERVED_RUNTIME_ERROR_4 = 4041, + RESERVED_RUNTIME_ERROR_1 = 4039, + RESERVED_RUNTIME_ERROR_2 = 4040, + RESERVED_RUNTIME_ERROR_3 = 4041, + RESERVED_RUNTIME_ERROR_4 = 4042, // 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-bytecode-source-map/src/source_map.rs b/third_party/move/move-ir-compiler/move-bytecode-source-map/src/source_map.rs index 26466739af6fc..ec101a84c7001 100644 --- a/third_party/move/move-ir-compiler/move-bytecode-source-map/src/source_map.rs +++ b/third_party/move/move-ir-compiler/move-bytecode-source-map/src/source_map.rs @@ -9,7 +9,7 @@ use move_binary_format::{ file_format::{ AbilitySet, CodeOffset, CodeUnit, ConstantPoolIndex, FunctionDefinitionIndex, LocalIndex, MemberCount, ModuleHandleIndex, SignatureIndex, StructDefinition, StructDefinitionIndex, - TableIndex, + TableIndex, VariantIndex, }, }; use move_command_line_common::files::FileHash; @@ -37,8 +37,9 @@ pub struct StructSourceMap { pub type_parameters: Vec, /// Note that fields to a struct source map need to be added in the order of the fields in the - /// struct definition. - pub fields: Vec, + /// struct definition, variant by variant. If the struct has no variant, there is only + /// one outer vector. + pub fields: Vec>, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -108,12 +109,24 @@ impl StructSourceMap { self.type_parameters.get(type_parameter_idx).cloned() } - pub fn add_field_location(&mut self, field_loc: Loc) { - self.fields.push(field_loc) + pub fn add_field_location(&mut self, variant: Option, field_loc: Loc) { + let variant = variant.unwrap_or_default() as usize; + while self.fields.len() <= variant { + self.fields.push(vec![]) + } + self.fields[variant].push(field_loc) } - pub fn get_field_location(&self, field_index: MemberCount) -> Option { - self.fields.get(field_index as usize).cloned() + pub fn get_field_location( + &self, + variant: Option, + field_index: MemberCount, + ) -> Option { + let variant = variant.unwrap_or_default() as usize; + self.fields + .get(variant) + .and_then(|v| v.get(field_index as usize)) + .cloned() } pub fn dummy_struct_map( @@ -125,11 +138,24 @@ impl StructSourceMap { let struct_handle = view.struct_handle_at(struct_def.struct_handle); // Add dummy locations for the fields - match struct_def.declared_field_count() { - Err(_) => (), - Ok(count) => (0..count).for_each(|_| self.fields.push(default_loc)), + let variant_count = struct_def.field_information.variant_count(); + if variant_count > 0 { + for variant in 0..variant_count { + self.fields.push( + (0..struct_def + .field_information + .field_count(Some(variant as VariantIndex))) + .map(|_| default_loc) + .collect(), + ) + } + } else { + self.fields.push( + (0..struct_def.field_information.field_count(None)) + .map(|_| default_loc) + .collect(), + ) } - for i in 0..struct_handle.type_parameters.len() { let name = format!("Ty{}", i); self.add_type_parameter((name, default_loc)) @@ -426,24 +452,26 @@ impl SourceMap { pub fn add_struct_field_mapping( &mut self, struct_def_idx: StructDefinitionIndex, + variant: Option, location: Loc, ) -> Result<()> { let struct_entry = self .struct_map .get_mut(&struct_def_idx.0) .ok_or_else(|| format_err!("Tried to add file mapping to undefined struct index"))?; - struct_entry.add_field_location(location); + struct_entry.add_field_location(variant, location); Ok(()) } pub fn get_struct_field_name( &self, struct_def_idx: StructDefinitionIndex, + variant: Option, field_idx: MemberCount, ) -> Option { self.struct_map .get(&struct_def_idx.0) - .and_then(|struct_source_map| struct_source_map.get_field_location(field_idx)) + .and_then(|struct_source_map| struct_source_map.get_field_location(variant, field_idx)) } pub fn add_struct_type_parameter_mapping( diff --git a/third_party/move/move-ir-compiler/move-ir-to-bytecode/src/compiler.rs b/third_party/move/move-ir-compiler/move-ir-to-bytecode/src/compiler.rs index bb6e33886b4e8..c2be4e3537147 100644 --- a/third_party/move/move-ir-compiler/move-ir-to-bytecode/src/compiler.rs +++ b/third_party/move/move-ir-compiler/move-ir-to-bytecode/src/compiler.rs @@ -46,7 +46,7 @@ macro_rules! record_src_loc { (field: $context:expr, $idx:expr, $field:expr) => {{ $context .source_map - .add_struct_field_mapping($idx, $field.loc)?; + .add_struct_field_mapping($idx, None, $field.loc)?; }}; (function_type_formals: $context:expr, $var:expr) => { for (ty_var, _) in $var.iter() { @@ -485,6 +485,12 @@ pub fn compile_module<'a>( metadata: vec![], struct_defs, function_defs, + // TODO(#13806): Move IR does not support enums and it is likely be retired. Decide + // whether we want to support this here. + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; Ok((module, source_map)) } @@ -564,6 +570,10 @@ fn compile_explicit_dependency_declarations( metadata: vec![], struct_defs: vec![], function_defs: vec![], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; dependencies_acc = compiled_deps; dependencies_acc.insert( diff --git a/third_party/move/move-model/bytecode/src/borrow_analysis.rs b/third_party/move/move-model/bytecode/src/borrow_analysis.rs index 9a61cea49ca27..2ab8c96f9843a 100644 --- a/third_party/move/move-model/bytecode/src/borrow_analysis.rs +++ b/third_party/move/move-model/bytecode/src/borrow_analysis.rs @@ -676,7 +676,11 @@ impl<'a> TransferFunctions for BorrowAnalysis<'a> { state.add_edge( src_node, dest_node, - BorrowEdge::Field(mid.qualified_inst(*sid, inst.to_owned()), *field), + BorrowEdge::Field( + mid.qualified_inst(*sid, inst.to_owned()), + None, + *field, + ), ); }, Function(mid, fid, targs) => { diff --git a/third_party/move/move-model/bytecode/src/stackless_bytecode.rs b/third_party/move/move-model/bytecode/src/stackless_bytecode.rs index 8db626ec634eb..0b9f56e82fd64 100644 --- a/third_party/move/move-model/bytecode/src/stackless_bytecode.rs +++ b/third_party/move/move-model/bytecode/src/stackless_bytecode.rs @@ -161,11 +161,12 @@ pub enum Operation { Exists(ModuleId, StructId, Vec), // Variants - // Below the `Symbol` is the name of the variant. + // Below the `Symbol` is the name of the variant. In the case of `Vec`, + // it is a list of variants the value needs to have TestVariant(ModuleId, StructId, Symbol, Vec), PackVariant(ModuleId, StructId, Symbol, Vec), UnpackVariant(ModuleId, StructId, Symbol, Vec), - BorrowFieldVariant(ModuleId, StructId, Symbol, Vec, usize), + BorrowVariantField(ModuleId, StructId, Vec, Vec, usize), // Borrow BorrowLoc, @@ -263,7 +264,7 @@ impl Operation { Operation::TestVariant(_, _, _, _) => false, Operation::PackVariant(_, _, _, _) => false, Operation::UnpackVariant(_, _, _, _) => true, // aborts if not given variant - Operation::BorrowFieldVariant(_, _, _, _, _) => true, // aborts if not given variant + Operation::BorrowVariantField(_, _, _, _, _) => true, // aborts if not given variant Operation::MoveTo(_, _, _) => true, Operation::MoveFrom(_, _, _) => true, Operation::Exists(_, _, _) => false, @@ -368,7 +369,7 @@ pub enum BorrowEdge { /// Direct borrow. Direct, /// Field borrow with static offset. - Field(QualifiedInstId, usize), + Field(QualifiedInstId, Option, usize), /// Vector borrow with dynamic index. Index(IndexEdgeKind), /// Composed sequence of edges. @@ -386,7 +387,9 @@ impl BorrowEdge { pub fn instantiate(&self, params: &[Type]) -> Self { match self { - Self::Field(qid, offset) => Self::Field(qid.instantiate_ref(params), *offset), + Self::Field(qid, variant, offset) => { + Self::Field(qid.instantiate_ref(params), *variant, *offset) + }, Self::Hyper(edges) => { let new_edges = edges.iter().map(|e| e.instantiate(params)).collect(); Self::Hyper(new_edges) @@ -1156,19 +1159,25 @@ impl<'env> fmt::Display for OperationDisplay<'env> { field_env.get_name().display(struct_env.symbol_pool()) )?; }, - BorrowFieldVariant(mid, sid, variant, targs, offset) => { + BorrowVariantField(mid, sid, variants, targs, offset) => { + assert!(!variants.is_empty()); + let variants_str = variants + .iter() + .map(|v| v.display(self.func_target.symbol_pool())) + .join("|"); write!( f, - "borrow_field_variant<{}::{}>", + "borrow_variant_field<{}::{}>", self.struct_str(*mid, *sid, targs), - variant.display(self.func_target.symbol_pool()) + variants_str, )?; let struct_env = self .func_target .global_env() .get_module(*mid) .into_struct(*sid); - let field_env = struct_env.get_field_by_offset(*offset); + let field_env = + struct_env.get_field_by_offset_optional_variant(Some(variants[0]), *offset); write!( f, ".{}", @@ -1436,9 +1445,9 @@ impl<'a> std::fmt::Display for BorrowEdgeDisplay<'a> { use BorrowEdge::*; let tctx = TypeDisplayContext::new(self.env); match self.edge { - Field(qid, field) => { + Field(qid, variant, field) => { let struct_env = self.env.get_struct(qid.to_qualified_id()); - let field_env = struct_env.get_field_by_offset(*field); + let field_env = struct_env.get_field_by_offset_optional_variant(*variant, *field); let field_type = field_env.get_type().instantiate(&qid.inst); write!( f, 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 20147c91f6a68..7551d41988329 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 @@ -1389,6 +1389,18 @@ impl<'a> StacklessBytecodeGenerator<'a> { None, )) }, + MoveBytecode::PackVariant(_) + | MoveBytecode::PackVariantGeneric(_) + | MoveBytecode::UnpackVariant(_) + | MoveBytecode::UnpackVariantGeneric(_) + | MoveBytecode::TestVariant(_) + | MoveBytecode::TestVariantGeneric(_) + | MoveBytecode::MutBorrowVariantField(_) + | MoveBytecode::MutBorrowVariantFieldGeneric(_) + | MoveBytecode::ImmBorrowVariantField(_) + | MoveBytecode::ImmBorrowVariantFieldGeneric(_) => { + panic!("stackless bytecode generator does not support variant structs yet") + }, } } diff --git a/third_party/move/move-model/src/builder/module_builder.rs b/third_party/move/move-model/src/builder/module_builder.rs index 020999ddf77d5..576e6d2e94ba0 100644 --- a/third_party/move/move-model/src/builder/module_builder.rs +++ b/third_party/move/move-model/src/builder/module_builder.rs @@ -1245,11 +1245,13 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { } }) .collect_vec(); - // Identify common fields if !variant_maps.is_empty() { - let mut common_fields = BTreeSet::new(); + // Identify common fields and compute their offsets. Common fields + // occupy the first N slots in a variant layout, where the order + // is determined by the first variant which declares them. + let mut common_fields: BTreeMap = BTreeMap::new(); let main = &variant_maps[0]; - for field in main.fields.values() { + for field in main.fields.values().sorted_by_key(|f| f.offset) { let mut common = true; for other in &variant_maps[1..variant_maps.len()] { if !other @@ -1262,12 +1264,25 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { } } if common { - common_fields.insert(field.name); + common_fields.insert(field.name, common_fields.len()); } } + // Now adjust the offsets of the fields over all variants. for variant_map in variant_maps.iter_mut() { - for field in variant_map.fields.values_mut() { - field.common_for_variants = common_fields.contains(&field.name) + let mut next_offset = common_fields.len(); + for field in variant_map + .fields + .values_mut() + .sorted_by_key(|v| v.offset) + .collect_vec() + { + if let Some(offset) = common_fields.get(&field.name) { + field.common_for_variants = true; + field.offset = *offset + } else { + field.offset = next_offset; + next_offset += 1 + } } } } @@ -3477,25 +3492,26 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { field_data.extend(fields.values().map(|f| (FieldId::new(f.name), f.clone()))); }, StructLayout::Variants(entry_variants) => { - for variant in entry_variants { + for (order, variant) in entry_variants.iter().enumerate() { variants.insert(variant.name, model::StructVariant { loc: variant.loc.clone(), + order, attributes: variant.attributes.clone(), }); - let pool = self.parent.env.symbol_pool(); - field_data.extend(variant.fields.values().map(|f| { - let variant_field_name = if !f.common_for_variants { + for field in variant.fields.values().sorted_by_key(|f| f.offset).cloned() { + let variant_field_name = if !field.common_for_variants { // If the field is not common between variants, we need to qualify // the name with the variant for a unique id. + let pool = self.parent.env.symbol_pool(); pool.make(&FieldId::make_variant_field_id_str( pool.string(variant.name).as_str(), - pool.string(f.name).as_str(), + pool.string(field.name).as_str(), )) } else { - f.name + field.name }; - (FieldId::new(variant_field_name), f.clone()) - })) + field_data.insert(FieldId::new(variant_field_name), field); + } } }, StructLayout::None => {}, diff --git a/third_party/move/move-model/src/lib.rs b/third_party/move/move-model/src/lib.rs index fbf0bf6a81b28..92533e0515fdb 100644 --- a/third_party/move/move-model/src/lib.rs +++ b/third_party/move/move-model/src/lib.rs @@ -681,6 +681,11 @@ pub fn script_into_module(compiled_script: CompiledScript, name: &str) -> Compil struct_defs: vec![], function_defs: vec![main_def], + + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; BoundsChecker::verify_module(&module).expect("invalid bounds in module"); module diff --git a/third_party/move/move-model/src/model.rs b/third_party/move/move-model/src/model.rs index d810ba6112d63..f2f700114e6ed 100644 --- a/third_party/move/move-model/src/model.rs +++ b/third_party/move/move-model/src/model.rs @@ -52,8 +52,8 @@ use move_binary_format::{ binary_views::BinaryIndexedView, file_format::{ AccessKind, Bytecode, CodeOffset, Constant as VMConstant, ConstantPoolIndex, - FunctionDefinitionIndex, FunctionHandleIndex, SignatureIndex, SignatureToken, - StructDefinitionIndex, + FunctionDefinitionIndex, FunctionHandleIndex, MemberCount, SignatureIndex, SignatureToken, + StructDefinitionIndex, VariantIndex, }, views::{FunctionDefinitionView, FunctionHandleView, StructHandleView}, CompiledModule, @@ -3311,6 +3311,7 @@ pub struct StructData { pub(crate) struct StructVariant { pub(crate) loc: Loc, pub(crate) attributes: Vec, + pub(crate) order: usize, } #[derive(Debug, Clone)] @@ -3465,14 +3466,16 @@ impl<'env> StructEnv<'env> { self.data.variants.is_some() } - /// Returns an iteration of the variant names in the struct. + /// Returns an iteration of the variant names in the struct, in the order they + /// are declared. pub fn get_variants(&self) -> impl Iterator + 'env { self.data .variants .as_ref() .expect("struct has variants") - .keys() - .cloned() + .iter() + .sorted_by(|(_, v1), (_, v2)| v1.order.cmp(&v2.order)) + .map(|(s, _)| *s) } /// Returns the location of the variant. @@ -3484,6 +3487,13 @@ impl<'env> StructEnv<'env> { .expect("variant defined") } + /// Get the index of the variant in the struct. + pub fn get_variant_idx(&self, variant: Symbol) -> Option { + self.get_variants() + .position(|n| variant == n) + .map(|p| p as VariantIndex) + } + /// Returns the attributes of the variant. pub fn get_variant_attributes(&self, variant: Symbol) -> &[Attribute] { self.data @@ -3554,12 +3564,19 @@ impl<'env> StructEnv<'env> { /// Gets a field by its offset. pub fn get_field_by_offset(&'env self, offset: usize) -> FieldEnv<'env> { - for data in self.data.field_data.values() { - if data.offset == offset { - return FieldEnv { - struct_env: self.clone(), - data, - }; + self.get_field_by_offset_optional_variant(None, offset) + } + + /// Gets a field by its offset, in context of an optional variant. + pub fn get_field_by_offset_optional_variant( + &'env self, + variant: Option, + offset: usize, + ) -> FieldEnv<'env> { + // We may speed this up via a cache RefCell> + for field in self.get_fields_optional_variant(variant) { + if field.get_offset() == offset { + return field; } } unreachable!("invalid field lookup") @@ -3669,13 +3686,18 @@ impl<'env> FieldEnv<'env> { self.struct_env.data.def_idx, &self.struct_env.module_env.data.source_map, ) { - if let Ok(smap) = mmap.get_struct_source_map(def_idx) { - let loc = self - .struct_env + if let Some(loc) = mmap.get_struct_source_map(def_idx).ok().and_then(|smap| { + smap.get_field_location( + self.data + .variant + .and_then(|v| self.struct_env.get_variant_idx(v)), + self.data.offset as MemberCount, + ) + }) { + self.struct_env .module_env .env - .to_loc(&smap.fields[self.data.offset]); - self.struct_env.module_env.env.get_doc(&loc) + .get_doc(&self.struct_env.module_env.env.to_loc(&loc)) } else { "" } @@ -3693,6 +3715,16 @@ impl<'env> FieldEnv<'env> { pub fn get_offset(&self) -> usize { self.data.offset } + + /// Gets the variant this field is associated with + pub fn get_variant(&self) -> Option { + self.data.variant + } + + /// Returns true if this field is common between variants + pub fn is_common_variant_field(&self) -> bool { + self.data.common_for_variants + } } // ================================================================================================= diff --git a/third_party/move/move-prover/boogie-backend/src/bytecode_translator.rs b/third_party/move/move-prover/boogie-backend/src/bytecode_translator.rs index e1e3ca0173bd3..87d6f02af9b69 100644 --- a/third_party/move/move-prover/boogie-backend/src/bytecode_translator.rs +++ b/third_party/move/move-prover/boogie-backend/src/bytecode_translator.rs @@ -1122,7 +1122,7 @@ impl<'env> FunctionTranslator<'env> { TestVariant(..) | PackVariant(..) | UnpackVariant(..) - | BorrowFieldVariant(..) => { + | BorrowVariantField(..) => { let loc = self.fun_target.get_bytecode_loc(attr_id); self.parent.env.error(&loc, "variants not yet implemented") }, @@ -1143,7 +1143,7 @@ impl<'env> FunctionTranslator<'env> { .flatten() .into_iter() .filter_map(|e| match e { - BorrowEdge::Field(_, offset) => Some(format!("{}", offset)), + BorrowEdge::Field(_, _, offset) => Some(format!("{}", offset)), BorrowEdge::Index(_) => Some("-1".to_owned()), BorrowEdge::Direct => None, _ => unreachable!(), @@ -2365,10 +2365,11 @@ impl<'env> FunctionTranslator<'env> { BorrowEdge::Direct => { self.translate_write_back_update(mk_dest, get_path_index, src, edges, at + 1) }, - BorrowEdge::Field(memory, offset) => { + BorrowEdge::Field(memory, variant, offset) => { let memory = memory.to_owned().instantiate(self.type_inst); let struct_env = &self.parent.env.get_struct_qid(memory.to_qualified_id()); - let field_env = &struct_env.get_field_by_offset(*offset); + let field_env = + &struct_env.get_field_by_offset_optional_variant(*variant, *offset); let field_sel = boogie_field_sel(field_env); let new_dest = format!("{}->{}", (*mk_dest)(), field_sel); let mut new_dest_needed = false; diff --git a/third_party/move/move-prover/bytecode-pipeline/src/mono_analysis.rs b/third_party/move/move-prover/bytecode-pipeline/src/mono_analysis.rs index beb645e7e1ab1..7f7bda844af1c 100644 --- a/third_party/move/move-prover/bytecode-pipeline/src/mono_analysis.rs +++ b/third_party/move/move-prover/bytecode-pipeline/src/mono_analysis.rs @@ -582,7 +582,7 @@ impl<'a> Analyzer<'a> { fn add_types_in_borrow_edge(&mut self, edge: &BorrowEdge) { match edge { BorrowEdge::Direct | BorrowEdge::Index(_) => (), - BorrowEdge::Field(qid, _) => { + BorrowEdge::Field(qid, _, _) => { self.add_type_root(&qid.to_type()); }, BorrowEdge::Hyper(edges) => { diff --git a/third_party/move/move-vm/integration-tests/src/tests/instantiation_tests.rs b/third_party/move/move-vm/integration-tests/src/tests/instantiation_tests.rs index f67445dc76bcd..ef24344f33ae2 100644 --- a/third_party/move/move-vm/integration-tests/src/tests/instantiation_tests.rs +++ b/third_party/move/move-vm/integration-tests/src/tests/instantiation_tests.rs @@ -97,6 +97,11 @@ fn instantiation_err() { ], }), }], + // TODO(#13806): followup on whether we need specific tests for variants here + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; move_bytecode_verifier::verify_module(&cm).expect("verify failed"); diff --git a/third_party/move/move-vm/integration-tests/src/tests/vm_arguments_tests.rs b/third_party/move/move-vm/integration-tests/src/tests/vm_arguments_tests.rs index 14cbba66b120b..0bfcd2a96fb01 100644 --- a/third_party/move/move-vm/integration-tests/src/tests/vm_arguments_tests.rs +++ b/third_party/move/move-vm/integration-tests/src/tests/vm_arguments_tests.rs @@ -215,6 +215,10 @@ fn make_module_with_function( code: vec![Bytecode::LdU64(0), Bytecode::Abort], }), }], + struct_variant_handles: vec![], + struct_variant_instantiations: vec![], + variant_field_handles: vec![], + variant_field_instantiations: vec![], }; (module, function_name) } diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs index f5c5ed418c189..3b7ec81ffef3c 100644 --- a/third_party/move/move-vm/runtime/src/interpreter.rs +++ b/third_party/move/move-vm/runtime/src/interpreter.rs @@ -17,6 +17,7 @@ use move_binary_format::{ file_format::{ Ability, AbilitySet, AccessKind, Bytecode, FieldInstantiationIndex, FunctionHandleIndex, FunctionInstantiationIndex, LocalIndex, SignatureIndex, StructDefInstantiationIndex, + StructVariantInstantiationIndex, VariantFieldInstantiationIndex, }, }; use move_core_types::{ @@ -1361,11 +1362,18 @@ struct Frame { struct FrameTypeCache { struct_field_type_instantiation: BTreeMap>, + struct_variant_field_type_instantiation: + BTreeMap>, struct_def_instantiation_type: BTreeMap, + struct_variant_instantation_type: + BTreeMap, /// For a given field instantiation, the: /// ((Type of the field, size of the field type) and (Type of its defining struct, size of its defining struct) field_instantiation: BTreeMap, + /// Same as above, bot for variant field instantiations + variant_field_instantiation: + BTreeMap, single_sig_token_type: BTreeMap, } @@ -1414,6 +1422,32 @@ impl FrameTypeCache { Ok(((field_ty, *field_ty_count), (struct_ty, *struct_ty_count))) } + fn get_variant_field_type_and_struct_type( + &mut self, + idx: VariantFieldInstantiationIndex, + resolver: &Resolver, + ty_args: &[Type], + ) -> PartialVMResult<((&Type, NumTypeNodes), (&Type, NumTypeNodes))> { + let ((field_ty, field_ty_count), (struct_ty, struct_ty_count)) = + Self::get_or(&mut self.variant_field_instantiation, idx, |idx| { + let info = resolver.variant_field_instantiation_info_at(idx); + let struct_type = resolver.create_struct_instantiation_ty( + &info.definition_struct_type, + &info.instantiation, + ty_args, + )?; + let struct_ty_count = NumTypeNodes::new(struct_type.num_nodes() as u64); + let field_ty = resolver.instantiate_ty( + &info.uninstantiated_field_ty, + ty_args, + &info.instantiation, + )?; + let field_ty_count = NumTypeNodes::new(field_ty.num_nodes() as u64); + Ok(((field_ty, field_ty_count), (struct_type, struct_ty_count))) + })?; + Ok(((field_ty, *field_ty_count), (struct_ty, *struct_ty_count))) + } + #[inline(always)] fn get_struct_type( &mut self, @@ -1429,6 +1463,27 @@ impl FrameTypeCache { Ok((ty, *ty_count)) } + #[inline(always)] + fn get_struct_variant_type( + &mut self, + idx: StructVariantInstantiationIndex, + resolver: &Resolver, + ty_args: &[Type], + ) -> PartialVMResult<(&Type, NumTypeNodes)> { + let (ty, ty_count) = + Self::get_or(&mut self.struct_variant_instantation_type, idx, |idx| { + let info = resolver.get_struct_variant_instantiation_at(idx); + let ty = resolver.create_struct_instantiation_ty( + &info.definition_struct_type, + &info.instantiation, + ty_args, + )?; + let ty_count = NumTypeNodes::new(ty.num_nodes() as u64); + Ok((ty, ty_count)) + })?; + Ok((ty, *ty_count)) + } + #[inline(always)] fn get_struct_fields_types( &mut self, @@ -1452,6 +1507,29 @@ impl FrameTypeCache { )?) } + #[inline(always)] + fn get_struct_variant_fields_types( + &mut self, + idx: StructVariantInstantiationIndex, + resolver: &Resolver, + ty_args: &[Type], + ) -> PartialVMResult<&[(Type, NumTypeNodes)]> { + Ok(Self::get_or( + &mut self.struct_variant_field_type_instantiation, + idx, + |idx| { + Ok(resolver + .instantiate_generic_struct_variant_fields(idx, ty_args)? + .into_iter() + .map(|ty| { + let num_nodes = NumTypeNodes::new(ty.num_nodes() as u64); + (ty, num_nodes) + }) + .collect::>()) + }, + )?) + } + #[inline(always)] fn get_signature_index_type( &mut self, @@ -1608,6 +1686,18 @@ impl Frame { | Bytecode::VecPopBack(_) | Bytecode::VecUnpack(_, _) | Bytecode::VecSwap(_) => (), + + // Since bytecode version 7 + Bytecode::PackVariant(_) + | Bytecode::PackVariantGeneric(_) + | Bytecode::UnpackVariant(_) + | Bytecode::UnpackVariantGeneric(_) + | Bytecode::TestVariant(_) + | Bytecode::TestVariantGeneric(_) + | Bytecode::MutBorrowVariantField(_) + | Bytecode::MutBorrowVariantFieldGeneric(_) + | Bytecode::ImmBorrowVariantField(_) + | Bytecode::ImmBorrowVariantFieldGeneric(_) => (), }; Ok(()) } @@ -1728,83 +1818,61 @@ impl Frame { let field_mut_ref_ty = ty_builder.create_ref_ty(field_ty, true)?; interpreter.operand_stack.push_ty(field_mut_ref_ty)?; }, + Bytecode::ImmBorrowVariantField(fh_idx) | Bytecode::MutBorrowVariantField(fh_idx) => { + let is_mut = matches!(instruction, Bytecode::MutBorrowVariantField(..)); + let field_info = resolver.variant_field_info_at(*fh_idx); + let ty = interpreter.operand_stack.pop_ty()?; + let expected_ty = resolver.create_struct_ty(&field_info.definition_struct_type); + ty.paranoid_check_ref_eq(&expected_ty, is_mut)?; + let field_ty = &field_info.uninstantiated_field_ty; + let field_ref_ty = ty_builder.create_ref_ty(field_ty, is_mut)?; + interpreter.operand_stack.push_ty(field_ref_ty)?; + }, + Bytecode::ImmBorrowVariantFieldGeneric(idx) + | Bytecode::MutBorrowVariantFieldGeneric(idx) => { + let is_mut = matches!(instruction, Bytecode::MutBorrowVariantFieldGeneric(..)); + let struct_ty = interpreter.operand_stack.pop_ty()?; + let ((field_ty, _), (expected_struct_ty, _)) = + ty_cache.get_variant_field_type_and_struct_type(*idx, resolver, ty_args)?; + struct_ty.paranoid_check_ref_eq(expected_struct_ty, is_mut)?; + let field_ref_ty = ty_builder.create_ref_ty(field_ty, is_mut)?; + interpreter.operand_stack.push_ty(field_ref_ty)?; + }, Bytecode::Pack(idx) => { let field_count = resolver.field_count(*idx); - let args_ty = resolver.get_struct_field_tys(*idx)?; + let args_ty = resolver.get_struct(*idx)?; + let field_tys = args_ty.fields(None)?.iter().map(|(_, ty)| ty); let output_ty = resolver.get_struct_ty(*idx); - let ability = output_ty.abilities()?; - - // If the struct has a key ability, we expect all of its field to have store ability but not key ability. - let field_expected_abilities = if ability.has_key() { - ability - .remove(Ability::Key) - .union(AbilitySet::singleton(Ability::Store)) - } else { - ability - }; - - if field_count as usize != args_ty.field_tys.len() { - return Err( - PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) - .with_message("Args count mismatch".to_string()), - ); - } - - for (ty, expected_ty) in interpreter - .operand_stack - .popn_tys(field_count)? - .into_iter() - .zip(args_ty.field_tys.iter()) - { - // Fields ability should be a subset of the struct ability because abilities can be weakened but not the other direction. - // For example, it is ok to have a struct that doesn't have a copy capability where its field is a struct that has copy capability but not vice versa. - ty.paranoid_check_abilities(field_expected_abilities)?; - ty.paranoid_check_eq(expected_ty)?; - } - - interpreter.operand_stack.push_ty(output_ty)?; + Self::verify_pack(interpreter, field_count, field_tys, output_ty)?; }, Bytecode::PackGeneric(idx) => { let field_count = resolver.field_instantiation_count(*idx); let output_ty = ty_cache.get_struct_type(*idx, resolver, ty_args)?.0.clone(); let args_ty = ty_cache.get_struct_fields_types(*idx, resolver, ty_args)?; - let ability = output_ty.abilities()?; - - // If the struct has a key ability, we expect all of its field to have store ability but not key ability. - let field_expected_abilities = if ability.has_key() { - ability - .remove(Ability::Key) - .union(AbilitySet::singleton(Ability::Store)) - } else { - ability - }; if field_count as usize != args_ty.len() { + // This is an inconsistency between the cache and the actual + // type declaration. We would crash if for some reason this invariant does + // not hold. It seems impossible to hit, but we keep it here for safety + // reasons, as a previous version of this code had this too. return Err( PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) .with_message("Args count mismatch".to_string()), ); } - for (ty, (expected_ty, _)) in interpreter - .operand_stack - .popn_tys(field_count)? - .into_iter() - .zip(args_ty.iter()) - { - // Fields ability should be a subset of the struct ability because abilities can be weakened but not the other direction. - // For example, it is ok to have a struct that doesn't have a copy capability where its field is a struct that has copy capability but not vice versa. - ty.paranoid_check_abilities(field_expected_abilities)?; - ty.paranoid_check_eq(expected_ty)?; - } - - interpreter.operand_stack.push_ty(output_ty)?; + Self::verify_pack( + interpreter, + field_count, + args_ty.iter().map(|(ty, _)| ty), + output_ty, + )?; }, Bytecode::Unpack(idx) => { let struct_ty = interpreter.operand_stack.pop_ty()?; struct_ty.paranoid_check_eq(&resolver.get_struct_ty(*idx))?; - let struct_decl = resolver.get_struct_field_tys(*idx)?; - for ty in struct_decl.field_tys.iter() { + let struct_decl = resolver.get_struct(*idx)?; + for (_name, ty) in struct_decl.fields(None)?.iter() { interpreter.operand_stack.push_ty(ty.clone())?; } }, @@ -1820,6 +1888,72 @@ impl Frame { interpreter.operand_stack.push_ty(ty.clone())?; } }, + Bytecode::PackVariant(idx) => { + let info = resolver.get_struct_variant_at(*idx); + let field_tys = info + .definition_struct_type + .fields(Some(info.variant))? + .iter() + .map(|(_, ty)| ty); + let output_ty = resolver.create_struct_ty(&info.definition_struct_type); + Self::verify_pack(interpreter, info.field_count, field_tys, output_ty)?; + }, + Bytecode::PackVariantGeneric(idx) => { + let info = resolver.get_struct_variant_instantiation_at(*idx); + let output_ty = ty_cache + .get_struct_variant_type(*idx, resolver, ty_args)? + .0 + .clone(); + let args_ty = ty_cache.get_struct_variant_fields_types(*idx, resolver, ty_args)?; + Self::verify_pack( + interpreter, + info.field_count, + args_ty.iter().map(|(ty, _)| ty), + output_ty, + )?; + }, + Bytecode::UnpackVariant(idx) => { + let info = resolver.get_struct_variant_at(*idx); + let expected_struct_ty = resolver.create_struct_ty(&info.definition_struct_type); + let actual_struct_ty = interpreter.operand_stack.pop_ty()?; + actual_struct_ty.paranoid_check_eq(&expected_struct_ty)?; + for (_name, ty) in info + .definition_struct_type + .fields(Some(info.variant))? + .iter() + { + interpreter.operand_stack.push_ty(ty.clone())?; + } + }, + Bytecode::UnpackVariantGeneric(idx) => { + let expected_struct_type = + ty_cache.get_struct_variant_type(*idx, resolver, ty_args)?.0; + let actual_struct_type = interpreter.operand_stack.pop_ty()?; + actual_struct_type.paranoid_check_eq(expected_struct_type)?; + let struct_fields_types = + ty_cache.get_struct_variant_fields_types(*idx, resolver, ty_args)?; + for (ty, _) in struct_fields_types { + interpreter.operand_stack.push_ty(ty.clone())?; + } + }, + Bytecode::TestVariant(idx) => { + let info = resolver.get_struct_variant_at(*idx); + let expected_struct_ty = resolver.create_struct_ty(&info.definition_struct_type); + let actual_struct_ty = interpreter.operand_stack.pop_ty()?; + actual_struct_ty.paranoid_check_ref_eq(&expected_struct_ty, false)?; + interpreter + .operand_stack + .push_ty(ty_builder.create_bool_ty())?; + }, + Bytecode::TestVariantGeneric(idx) => { + let expected_struct_ty = + ty_cache.get_struct_variant_type(*idx, resolver, ty_args)?.0; + let actual_struct_ty = interpreter.operand_stack.pop_ty()?; + actual_struct_ty.paranoid_check_ref_eq(expected_struct_ty, false)?; + interpreter + .operand_stack + .push_ty(ty_builder.create_bool_ty())?; + }, Bytecode::ReadRef => { let ref_ty = interpreter.operand_stack.pop_ty()?; let inner_ty = ref_ty.paranoid_read_ref()?; @@ -2091,6 +2225,37 @@ impl Frame { Ok(()) } + fn verify_pack<'a>( + interpreter: &mut Interpreter, + field_count: u16, + field_tys: impl Iterator, + output_ty: Type, + ) -> PartialVMResult<()> { + let ability = output_ty.abilities()?; + + // If the struct has a key ability, we expect all of its field to have store ability but not key ability. + let field_expected_abilities = if ability.has_key() { + ability + .remove(Ability::Key) + .union(AbilitySet::singleton(Ability::Store)) + } else { + ability + }; + for (ty, expected_ty) in interpreter + .operand_stack + .popn_tys(field_count)? + .into_iter() + .zip(field_tys) + { + // Fields ability should be a subset of the struct ability because abilities can be weakened but not the other direction. + // For example, it is ok to have a struct that doesn't have a copy capability where its field is a struct that has copy capability but not vice versa. + ty.paranoid_check_abilities(field_expected_abilities)?; + ty.paranoid_check_eq(expected_ty)?; + } + + interpreter.operand_stack.push_ty(output_ty) + } + fn execute_code_impl( &mut self, resolver: &Resolver, @@ -2301,9 +2466,10 @@ impl Frame { gas_meter.charge_create_ty(struct_ty_count)?; gas_meter.charge_create_ty(field_ty_count)?; - let instr = match instruction { - Bytecode::MutBorrowFieldGeneric(_) => S::MutBorrowFieldGeneric, - _ => S::ImmBorrowFieldGeneric, + let instr = if matches!(instruction, Bytecode::MutBorrowFieldGeneric(_)) { + S::MutBorrowFieldGeneric + } else { + S::ImmBorrowFieldGeneric }; gas_meter.charge_simple_instr(instr)?; @@ -2313,6 +2479,64 @@ impl Frame { let field_ref = reference.borrow_field(offset)?; interpreter.operand_stack.push(field_ref)?; }, + Bytecode::ImmBorrowVariantField(idx) | Bytecode::MutBorrowVariantField(idx) => { + let instr = if matches!(instruction, Bytecode::MutBorrowVariantField(_)) { + S::MutBorrowVariantField + } else { + S::ImmBorrowVariantField + }; + gas_meter.charge_simple_instr(instr)?; + + let field_info = resolver.variant_field_info_at(*idx); + let reference = interpreter.operand_stack.pop_as::()?; + let field_ref = reference.borrow_variant_field( + &field_info.variants, + field_info.offset, + &|v| { + field_info + .definition_struct_type + .variant_name_for_message(v) + }, + )?; + interpreter.operand_stack.push(field_ref)?; + }, + Bytecode::ImmBorrowVariantFieldGeneric(fi_idx) + | Bytecode::MutBorrowVariantFieldGeneric(fi_idx) => { + // TODO: Even though the types are not needed for execution, we still + // instantiate them for gas metering. + // + // This is a bit wasteful since the newly created types are + // dropped immediately. + let ((_, field_ty_count), (_, struct_ty_count)) = + self.ty_cache.get_variant_field_type_and_struct_type( + *fi_idx, + resolver, + &self.ty_args, + )?; + gas_meter.charge_create_ty(struct_ty_count)?; + gas_meter.charge_create_ty(field_ty_count)?; + + let instr = match instruction { + Bytecode::MutBorrowVariantFieldGeneric(_) => { + S::MutBorrowVariantFieldGeneric + }, + _ => S::ImmBorrowVariantFieldGeneric, + }; + gas_meter.charge_simple_instr(instr)?; + + let field_info = resolver.variant_field_instantiation_info_at(*fi_idx); + let reference = interpreter.operand_stack.pop_as::()?; + let field_ref = reference.borrow_variant_field( + &field_info.variants, + field_info.offset, + &|v| { + field_info + .definition_struct_type + .variant_name_for_message(v) + }, + )?; + interpreter.operand_stack.push(field_ref)?; + }, Bytecode::Pack(sd_idx) => { let field_count = resolver.field_count(*sd_idx); let struct_type = resolver.get_struct_ty(*sd_idx); @@ -2326,6 +2550,21 @@ impl Frame { .operand_stack .push(Value::struct_(Struct::pack(args)))?; }, + Bytecode::PackVariant(idx) => { + let info = resolver.get_struct_variant_at(*idx); + let struct_type = resolver.create_struct_ty(&info.definition_struct_type); + check_depth_of_type(resolver, &struct_type)?; + gas_meter.charge_pack_variant( + false, + interpreter + .operand_stack + .last_n(info.field_count as usize)?, + )?; + let args = interpreter.operand_stack.popn(info.field_count)?; + interpreter + .operand_stack + .push(Value::struct_(Struct::pack_variant(info.variant, args)))?; + }, Bytecode::PackGeneric(si_idx) => { // TODO: Even though the types are not needed for execution, we still // instantiate them for gas metering. @@ -2358,12 +2597,55 @@ impl Frame { .operand_stack .push(Value::struct_(Struct::pack(args)))?; }, + Bytecode::PackVariantGeneric(si_idx) => { + let field_tys = self.ty_cache.get_struct_variant_fields_types( + *si_idx, + resolver, + &self.ty_args, + )?; + + for (_, ty_count) in field_tys { + gas_meter.charge_create_ty(*ty_count)?; + } + + let (ty, ty_count) = self.ty_cache.get_struct_variant_type( + *si_idx, + resolver, + &self.ty_args, + )?; + gas_meter.charge_create_ty(ty_count)?; + check_depth_of_type(resolver, ty)?; + + let info = resolver.get_struct_variant_instantiation_at(*si_idx); + gas_meter.charge_pack_variant( + true, + interpreter + .operand_stack + .last_n(info.field_count as usize)?, + )?; + let args = interpreter.operand_stack.popn(info.field_count)?; + interpreter + .operand_stack + .push(Value::struct_(Struct::pack_variant(info.variant, args)))?; + }, Bytecode::Unpack(_sd_idx) => { - let struct_ = interpreter.operand_stack.pop_as::()?; + let struct_value = interpreter.operand_stack.pop_as::()?; - gas_meter.charge_unpack(false, struct_.field_views())?; + gas_meter.charge_unpack(false, struct_value.field_views())?; - for value in struct_.unpack()? { + for value in struct_value.unpack()? { + interpreter.operand_stack.push(value)?; + } + }, + Bytecode::UnpackVariant(sd_idx) => { + let struct_value = interpreter.operand_stack.pop_as::()?; + + gas_meter.charge_unpack_variant(false, struct_value.field_views())?; + + let info = resolver.get_struct_variant_at(*sd_idx); + for value in struct_value.unpack_variant(info.variant, &|v| { + info.definition_struct_type.variant_name_for_message(v) + })? { interpreter.operand_stack.push(value)?; } }, @@ -2400,6 +2682,64 @@ impl Frame { interpreter.operand_stack.push(value)?; } }, + Bytecode::UnpackVariantGeneric(si_idx) => { + let ty_and_field_counts = self.ty_cache.get_struct_variant_fields_types( + *si_idx, + resolver, + &self.ty_args, + )?; + for (_, ty_count) in ty_and_field_counts { + gas_meter.charge_create_ty(*ty_count)?; + } + + let (ty, ty_count) = self.ty_cache.get_struct_variant_type( + *si_idx, + resolver, + &self.ty_args, + )?; + gas_meter.charge_create_ty(ty_count)?; + + check_depth_of_type(resolver, ty)?; + + let struct_ = interpreter.operand_stack.pop_as::()?; + + gas_meter.charge_unpack_variant(true, struct_.field_views())?; + + let info = resolver.get_struct_variant_instantiation_at(*si_idx); + for value in struct_.unpack_variant(info.variant, &|v| { + info.definition_struct_type.variant_name_for_message(v) + })? { + interpreter.operand_stack.push(value)?; + } + }, + Bytecode::TestVariant(sd_idx) => { + let reference = interpreter.operand_stack.pop_as::()?; + gas_meter.charge_simple_instr(S::TestVariant)?; + let info = resolver.get_struct_variant_at(*sd_idx); + interpreter + .operand_stack + .push(reference.test_variant(info.variant)?)?; + }, + Bytecode::TestVariantGeneric(sd_idx) => { + // TODO: Even though the types are not needed for execution, we still + // instantiate them for gas metering. + // + // This is a bit wasteful since the newly created types are + // dropped immediately. + let (_, struct_ty_count) = self.ty_cache.get_struct_variant_type( + *sd_idx, + resolver, + &self.ty_args, + )?; + gas_meter.charge_create_ty(struct_ty_count)?; + + let reference = interpreter.operand_stack.pop_as::()?; + gas_meter.charge_simple_instr(S::TestVariantGeneric)?; + let info = resolver.get_struct_variant_instantiation_at(*sd_idx); + interpreter + .operand_stack + .push(reference.test_variant(info.variant)?)?; + }, Bytecode::ReadRef => { let reference = interpreter.operand_stack.pop_as::()?; gas_meter.charge_read_ref(reference.value_view())?; diff --git a/third_party/move/move-vm/runtime/src/loader/mod.rs b/third_party/move/move-vm/runtime/src/loader/mod.rs index 2be6e1c54bb99..98b6317ba4bf5 100644 --- a/third_party/move/move-vm/runtime/src/loader/mod.rs +++ b/third_party/move/move-vm/runtime/src/loader/mod.rs @@ -50,10 +50,15 @@ mod modules; mod script; mod type_loader; +use crate::loader::modules::{StructVariantInfo, VariantFieldInfo}; pub use function::LoadedFunction; pub(crate) use function::{Function, FunctionHandle, FunctionInstantiation, Scope}; pub(crate) use modules::{Module, ModuleCache, ModuleStorage, ModuleStorageAdapter}; -use move_vm_types::loaded_data::runtime_types::TypeBuilder; +use move_binary_format::file_format::{ + StructVariantHandleIndex, StructVariantInstantiationIndex, VariantFieldHandleIndex, + VariantFieldInstantiationIndex, VariantIndex, +}; +use move_vm_types::loaded_data::runtime_types::{StructLayout, TypeBuilder}; pub(crate) use script::{Script, ScriptCache}; use type_loader::intern_type; @@ -1219,10 +1224,27 @@ impl<'a> Resolver<'a> { BinaryType::Module(module) => module.struct_at(idx), BinaryType::Script(_) => unreachable!("Scripts cannot have type instructions"), }; + self.create_struct_ty(&struct_ty) + } - self.loader() - .ty_builder() - .create_struct_ty(struct_ty.idx, AbilityInfo::struct_(struct_ty.abilities)) + pub(crate) fn get_struct_variant_at( + &self, + idx: StructVariantHandleIndex, + ) -> &StructVariantInfo { + match &self.binary { + BinaryType::Module(module) => module.struct_variant_at(idx), + BinaryType::Script(_) => unreachable!("Scripts cannot have type instructions"), + } + } + + pub(crate) fn get_struct_variant_instantiation_at( + &self, + idx: StructVariantInstantiationIndex, + ) -> &StructVariantInfo { + match &self.binary { + BinaryType::Module(module) => module.struct_variant_instantiation_at(idx), + BinaryType::Script(_) => unreachable!("Scripts cannot have type instructions"), + } } pub(crate) fn get_generic_struct_ty( @@ -1244,7 +1266,7 @@ impl<'a> Resolver<'a> { match &self.binary { BinaryType::Module(module) => { let handle = &module.field_handles[idx.0 as usize]; - Ok(&handle.definition_struct_type.field_tys[handle.offset]) + Ok(&handle.field_ty) }, BinaryType::Script(_) => unreachable!("Scripts cannot have type instructions"), } @@ -1259,20 +1281,42 @@ impl<'a> Resolver<'a> { BinaryType::Module(module) => &module.field_instantiations[idx.0 as usize], BinaryType::Script(_) => unreachable!("Scripts cannot have type instructions"), }; + let field_ty = &field_instantiation.uninstantiated_field_ty; + self.instantiate_ty(field_ty, ty_args, &field_instantiation.instantiation) + } + pub(crate) fn instantiate_ty( + &self, + ty: &Type, + ty_args: &[Type], + instantiation_tys: &[Type], + ) -> PartialVMResult { let ty_builder = self.loader().ty_builder(); - let instantiation_tys = field_instantiation - .instantiation + let instantiation_tys = instantiation_tys .iter() .map(|inst_ty| ty_builder.create_ty_with_subst(inst_ty, ty_args)) .collect::>>()?; + ty_builder.create_ty_with_subst(ty, &instantiation_tys) + } - let field_ty = - &field_instantiation.definition_struct_type.field_tys[field_instantiation.offset]; - ty_builder.create_ty_with_subst(field_ty, &instantiation_tys) + pub(crate) fn variant_field_info_at(&self, idx: VariantFieldHandleIndex) -> &VariantFieldInfo { + match &self.binary { + BinaryType::Module(module) => module.variant_field_info_at(idx), + BinaryType::Script(_) => unreachable!("Scripts cannot have type instructions"), + } } - pub(crate) fn get_struct_field_tys( + pub(crate) fn variant_field_instantiation_info_at( + &self, + idx: VariantFieldInstantiationIndex, + ) -> &VariantFieldInfo { + match &self.binary { + BinaryType::Module(module) => module.variant_field_instantiation_info_at(idx), + BinaryType::Script(_) => unreachable!("Scripts cannot have type instructions"), + } + } + + pub(crate) fn get_struct( &self, idx: StructDefinitionIndex, ) -> PartialVMResult> { @@ -1292,18 +1336,44 @@ impl<'a> Resolver<'a> { BinaryType::Script(_) => unreachable!("Scripts cannot have type instructions"), }; let struct_ty = &struct_inst.definition_struct_type; + self.instantiate_generic_fields(struct_ty, None, &struct_inst.instantiation, ty_args) + } + + pub(crate) fn instantiate_generic_struct_variant_fields( + &self, + idx: StructVariantInstantiationIndex, + ty_args: &[Type], + ) -> PartialVMResult> { + let struct_inst = match &self.binary { + BinaryType::Module(module) => module.struct_variant_instantiation_at(idx), + BinaryType::Script(_) => unreachable!("Scripts cannot have type instructions"), + }; + let struct_ty = &struct_inst.definition_struct_type; + self.instantiate_generic_fields( + struct_ty, + Some(struct_inst.variant), + &struct_inst.instantiation, + ty_args, + ) + } + pub(crate) fn instantiate_generic_fields( + &self, + struct_ty: &Arc, + variant: Option, + instantiation: &[Type], + ty_args: &[Type], + ) -> PartialVMResult> { let ty_builder = self.loader().ty_builder(); - let instantiation_tys = struct_inst - .instantiation + let instantiation_tys = instantiation .iter() .map(|inst_ty| ty_builder.create_ty_with_subst(inst_ty, ty_args)) .collect::>>()?; struct_ty - .field_tys + .fields(variant)? .iter() - .map(|inst_ty| ty_builder.create_ty_with_subst(inst_ty, &instantiation_tys)) + .map(|(_, inst_ty)| ty_builder.create_ty_with_subst(inst_ty, &instantiation_tys)) .collect::>>() } @@ -1382,15 +1452,29 @@ impl<'a> Resolver<'a> { let field_inst = &module.field_instantiations[idx.0 as usize]; let struct_ty = &field_inst.definition_struct_type; let ty_params = &field_inst.instantiation; - - self.loader() - .ty_builder() - .create_struct_instantiation_ty(struct_ty, ty_params, ty_args) + self.create_struct_instantiation_ty(struct_ty, ty_params, ty_args) }, BinaryType::Script(_) => unreachable!("Scripts cannot have field instructions"), } } + pub(crate) fn create_struct_ty(&self, struct_ty: &Arc) -> Type { + self.loader() + .ty_builder() + .create_struct_ty(struct_ty.idx, AbilityInfo::struct_(struct_ty.abilities)) + } + + pub(crate) fn create_struct_instantiation_ty( + &self, + struct_ty: &Arc, + ty_params: &[Type], + ty_args: &[Type], + ) -> PartialVMResult { + self.loader() + .ty_builder() + .create_struct_instantiation_ty(struct_ty, ty_params, ty_args) + } + pub(crate) fn type_to_type_layout(&self, ty: &Type) -> PartialVMResult { self.loader.type_to_type_layout(ty, self.module_store) } @@ -1411,12 +1495,10 @@ impl<'a> Resolver<'a> { .type_to_fully_annotated_layout(ty, self.module_store) } - // get the loader pub(crate) fn loader(&self) -> &Loader { self.loader } - // get the loader pub(crate) fn module_store(&self) -> &ModuleStorageAdapter { self.module_store } @@ -1606,43 +1688,72 @@ impl Loader { let count_before = *count; let struct_type = module_store.get_struct_type_by_identifier(&name.name, &name.module)?; - // Some types can have fields which are lifted at serialization or deserialization - // times. Right now these are Aggregator and AggregatorSnapshot. - let maybe_mapping = self.get_identifier_mapping_kind(name); + let mut has_identifier_mappings = false; - let field_tys = struct_type - .field_tys - .iter() - .map(|ty| self.ty_builder().create_ty_with_subst(ty, ty_args)) - .collect::>>()?; - let (mut field_layouts, field_has_identifier_mappings): (Vec, Vec) = - field_tys - .iter() - .map(|ty| self.type_to_type_layout_impl(ty, module_store, count, depth)) - .collect::>>()? - .into_iter() - .unzip(); + let layout = match &struct_type.layout { + StructLayout::Single(fields) => { + // Some types can have fields which are lifted at serialization or deserialization + // times. Right now these are Aggregator and AggregatorSnapshot. + let maybe_mapping = self.get_identifier_mapping_kind(name); - let has_identifier_mappings = - maybe_mapping.is_some() || field_has_identifier_mappings.into_iter().any(|b| b); - - let field_node_count = *count - count_before; - let layout = if Some(IdentifierMappingKind::DerivedString) == maybe_mapping { - // For DerivedString, the whole object should be lifted. - MoveTypeLayout::Native( - IdentifierMappingKind::DerivedString, - Box::new(MoveTypeLayout::Struct(MoveStructLayout::new(field_layouts))), - ) - } else { - // For aggregators / snapshots, the first field should be lifted. - if let Some(kind) = &maybe_mapping { - if let Some(l) = field_layouts.first_mut() { - *l = MoveTypeLayout::Native(kind.clone(), Box::new(l.clone())); - } - } - MoveTypeLayout::Struct(MoveStructLayout::new(field_layouts)) + let field_tys = fields + .iter() + .map(|(_, ty)| self.ty_builder().create_ty_with_subst(ty, ty_args)) + .collect::>>()?; + let (mut field_layouts, field_has_identifier_mappings): ( + Vec, + Vec, + ) = field_tys + .iter() + .map(|ty| self.type_to_type_layout_impl(ty, module_store, count, depth)) + .collect::>>()? + .into_iter() + .unzip(); + + has_identifier_mappings = + maybe_mapping.is_some() || field_has_identifier_mappings.into_iter().any(|b| b); + + let layout = if Some(IdentifierMappingKind::DerivedString) == maybe_mapping { + // For DerivedString, the whole object should be lifted. + MoveTypeLayout::Native( + IdentifierMappingKind::DerivedString, + Box::new(MoveTypeLayout::Struct(MoveStructLayout::new(field_layouts))), + ) + } else { + // For aggregators / snapshots, the first field should be lifted. + if let Some(kind) = &maybe_mapping { + if let Some(l) = field_layouts.first_mut() { + *l = MoveTypeLayout::Native(kind.clone(), Box::new(l.clone())); + } + } + MoveTypeLayout::Struct(MoveStructLayout::new(field_layouts)) + }; + layout + }, + StructLayout::Variants(variants) => { + // We do not support variants to have direct identifier mappings, + // but their inner types may. + let variant_layouts = variants + .iter() + .map(|variant| { + variant + .1 + .iter() + .map(|(_, ty)| { + let ty = self.ty_builder().create_ty_with_subst(ty, ty_args)?; + let (ty, has_id_mappings) = + self.type_to_type_layout_impl(&ty, module_store, count, depth)?; + has_identifier_mappings |= has_id_mappings; + Ok(ty) + }) + .collect::>>() + }) + .collect::>>()?; + MoveTypeLayout::Struct(MoveStructLayout::RuntimeVariants(variant_layouts)) + }, }; + let field_node_count = *count - count_before; let mut cache = self.type_cache.write(); let info = cache .structs @@ -1801,15 +1912,13 @@ impl Loader { } let struct_type = module_store.get_struct_type_by_identifier(&name.name, &name.module)?; - if struct_type.field_tys.len() != struct_type.field_names.len() { - return Err( - PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message( - format!( - "Field types did not match the length of field names in loaded struct {}::{}", - &name.module, &name.name - ), - ), - ); + + // TODO(#13806): have annotated layouts for variants. Currently, we just return the raw + // layout for them. + if matches!(struct_type.layout, StructLayout::Variants(_)) { + return self + .struct_name_to_type_layout(struct_idx, module_store, ty_args, count, depth) + .map(|(l, _)| l); } let count_before = *count; @@ -1820,11 +1929,10 @@ impl Loader { cost_per_byte: self.vm_config.type_byte_cost, }; let struct_tag = self.struct_name_to_type_tag(struct_idx, ty_args, &mut gas_context)?; + let fields = struct_type.fields(None)?; - let field_layouts = struct_type - .field_names + let field_layouts = fields .iter() - .zip(&struct_type.field_tys) .map(|(n, ty)| { let ty = self.ty_builder().create_ty_with_subst(ty, ty_args)?; let l = @@ -1920,12 +2028,18 @@ impl Loader { } let struct_type = module_store.get_struct_type_by_identifier(&name.name, &name.module)?; + let formulas = match &struct_type.layout { + StructLayout::Single(fields) => fields + .iter() + .map(|(_, field_ty)| self.calculate_depth_of_type(field_ty, module_store)) + .collect::>>()?, + StructLayout::Variants(variants) => variants + .iter() + .flat_map(|variant| variant.1.iter().map(|(_, ty)| ty)) + .map(|field_ty| self.calculate_depth_of_type(field_ty, module_store)) + .collect::>>()?, + }; - let formulas = struct_type - .field_tys - .iter() - .map(|field_type| self.calculate_depth_of_type(field_type, module_store)) - .collect::>>()?; let formula = DepthFormula::normalize(formulas); let prev = self .type_cache diff --git a/third_party/move/move-vm/runtime/src/loader/modules.rs b/third_party/move/move-vm/runtime/src/loader/modules.rs index 1d75ff5b48ebb..bac71081d6f84 100644 --- a/third_party/move/move-vm/runtime/src/loader/modules.rs +++ b/third_party/move/move-vm/runtime/src/loader/modules.rs @@ -16,9 +16,10 @@ use move_binary_format::{ binary_views::BinaryIndexedView, errors::{Location, PartialVMError, PartialVMResult, VMResult}, file_format::{ - Bytecode, CompiledModule, FieldHandleIndex, FieldInstantiationIndex, + Bytecode, CompiledModule, FieldDefinition, FieldHandleIndex, FieldInstantiationIndex, FunctionDefinitionIndex, SignatureIndex, StructDefinition, StructDefinitionIndex, - StructFieldInformation, TableIndex, + StructFieldInformation, StructVariantHandleIndex, StructVariantInstantiationIndex, + TableIndex, VariantFieldHandleIndex, VariantFieldInstantiationIndex, VariantIndex, }, }; use move_core_types::{ @@ -28,7 +29,7 @@ use move_core_types::{ vm_status::StatusCode, }; use move_vm_types::loaded_data::runtime_types::{ - StructIdentifier, StructNameIndex, StructType, Type, + StructIdentifier, StructLayout, StructNameIndex, StructType, Type, }; use parking_lot::RwLock; use std::{ @@ -209,6 +210,9 @@ pub struct Module { pub(crate) structs: Vec, // materialized instantiations, whether partial or not pub(crate) struct_instantiations: Vec, + // same for struct variants + pub(crate) struct_variant_infos: Vec, + pub(crate) struct_variant_instantiation_infos: Vec, // functions as indexes into the Loader function list // That is effectively an indirection over the ref table: @@ -224,6 +228,9 @@ pub struct Module { pub(crate) field_handles: Vec, // materialized instantiations, whether partial or not pub(crate) field_instantiations: Vec, + // Information about variant fields. + pub(crate) variant_field_infos: Vec, + pub(crate) variant_field_instantiation_infos: Vec, // function name to index into the Loader function list. // This allows a direct access from function name to `Function` @@ -241,23 +248,30 @@ pub struct Module { #[derive(Clone, Debug)] pub(crate) struct StructDef { - // struct field count pub(crate) field_count: u16, pub(crate) definition_struct_type: Arc, } #[derive(Clone, Debug)] pub(crate) struct StructInstantiation { - // struct field count pub(crate) field_count: u16, pub(crate) definition_struct_type: Arc, pub(crate) instantiation: Vec, } +#[derive(Clone, Debug)] +pub(crate) struct StructVariantInfo { + pub(crate) field_count: u16, + pub(crate) variant: VariantIndex, + pub(crate) definition_struct_type: Arc, + pub(crate) instantiation: Vec, +} + // A field handle. The offset is the only used information when operating on a field #[derive(Clone, Debug)] pub(crate) struct FieldHandle { pub(crate) offset: usize, + pub(crate) field_ty: Type, pub(crate) definition_struct_type: Arc, } @@ -265,6 +279,17 @@ pub(crate) struct FieldHandle { #[derive(Clone, Debug)] pub(crate) struct FieldInstantiation { pub(crate) offset: usize, + pub(crate) uninstantiated_field_ty: Type, + pub(crate) definition_struct_type: Arc, + pub(crate) instantiation: Vec, +} + +// Information about to support both generic and non-generic variant fields. +#[derive(Clone, Debug)] +pub(crate) struct VariantFieldInfo { + pub(crate) offset: usize, + pub(crate) uninstantiated_field_ty: Type, + pub(crate) variants: Vec, pub(crate) definition_struct_type: Arc, pub(crate) instantiation: Vec, } @@ -281,11 +306,15 @@ impl Module { let mut structs = vec![]; let mut struct_instantiations = vec![]; + let mut struct_variant_infos = vec![]; + let mut struct_variant_instantiation_infos = vec![]; let mut function_refs = vec![]; let mut function_defs = vec![]; let mut function_instantiations = vec![]; let mut field_handles = vec![]; let mut field_instantiations: Vec = vec![]; + let mut variant_field_infos = vec![]; + let mut variant_field_instantiation_infos = vec![]; let mut function_map = HashMap::new(); let mut struct_map = HashMap::new(); let mut single_signature_token_map = BTreeMap::new(); @@ -330,7 +359,7 @@ impl Module { let definition_struct_type = Arc::new(Self::make_struct_type(&module, struct_def, &struct_idxs)?); structs.push(StructDef { - field_count: definition_struct_type.field_tys.len() as u16, + field_count: definition_struct_type.field_count(None), definition_struct_type, }); let name = @@ -341,14 +370,37 @@ impl Module { for struct_inst in module.struct_instantiations() { let def = struct_inst.def.0 as usize; let struct_def = &structs[def]; - let field_count = struct_def.field_count; struct_instantiations.push(StructInstantiation { - field_count, + field_count: struct_def.definition_struct_type.field_count(None), instantiation: signature_table[struct_inst.type_parameters.0 as usize].clone(), definition_struct_type: struct_def.definition_struct_type.clone(), }); } + for struct_variant in module.struct_variant_handles() { + let definition_struct_type = structs[struct_variant.struct_index.0 as usize] + .definition_struct_type + .clone(); + let variant = struct_variant.variant; + struct_variant_infos.push(StructVariantInfo { + field_count: definition_struct_type.field_count(Some(variant)), + variant, + definition_struct_type, + instantiation: vec![], + }) + } + + for struct_variant_inst in module.struct_variant_instantiations() { + let variant = &struct_variant_infos[struct_variant_inst.handle.0 as usize]; + struct_variant_instantiation_infos.push(StructVariantInfo { + field_count: variant.field_count, + variant: variant.variant, + definition_struct_type: variant.definition_struct_type.clone(), + instantiation: signature_table[struct_variant_inst.type_parameters.0 as usize] + .clone(), + }) + } + for (idx, func) in module.function_defs().iter().enumerate() { let findex = FunctionDefinitionIndex(idx as TableIndex); let function = Function::new( @@ -433,13 +485,15 @@ impl Module { }); } - for func_handle in module.field_handles() { - let def_idx = func_handle.owner; + for field_handle in module.field_handles() { + let def_idx = field_handle.owner; let definition_struct_type = structs[def_idx.0 as usize].definition_struct_type.clone(); - let offset = func_handle.field as usize; + let offset = field_handle.field as usize; + let ty = definition_struct_type.field_at(None, offset)?.1.clone(); field_handles.push(FieldHandle { offset, + field_ty: ty, definition_struct_type, }); } @@ -448,13 +502,60 @@ impl Module { let fh_idx = field_inst.handle; let offset = field_handles[fh_idx.0 as usize].offset; let owner_struct_def = &structs[module.field_handle_at(fh_idx).owner.0 as usize]; + let uninstantiated_ty = owner_struct_def + .definition_struct_type + .field_at(None, offset)? + .1 + .clone(); field_instantiations.push(FieldInstantiation { offset, + uninstantiated_field_ty: uninstantiated_ty, instantiation: signature_table[field_inst.type_parameters.0 as usize].clone(), definition_struct_type: owner_struct_def.definition_struct_type.clone(), }); } + for variant_handle in module.variant_field_handles() { + let def_idx = variant_handle.struct_index; + let definition_struct_type = + structs[def_idx.0 as usize].definition_struct_type.clone(); + let offset = variant_handle.field as usize; + let variants = variant_handle.variants.clone(); + let ty = definition_struct_type + .field_at(Some(variants[0]), offset)? + .1 + .clone(); + variant_field_infos.push(VariantFieldInfo { + offset, + variants, + definition_struct_type, + uninstantiated_field_ty: ty, + instantiation: vec![], + }); + } + + for variant_inst in module.variant_field_instantiations() { + let variant_info = &variant_field_infos[variant_inst.handle.0 as usize]; + let definition_struct_type = variant_info.definition_struct_type.clone(); + let variants = variant_info.variants.clone(); + let offset = variant_info.offset; + let instantiation = + signature_table[variant_inst.type_parameters.0 as usize].clone(); + // We can select one representative variant for finding the field type, all + // must have the same type as the verifier ensured. + let uninstantiated_ty = definition_struct_type + .field_at(Some(variants[0]), offset)? + .1 + .clone(); + variant_field_instantiation_infos.push(VariantFieldInfo { + offset, + uninstantiated_field_ty: uninstantiated_ty, + variants, + definition_struct_type, + instantiation, + }); + } + Ok(()) }; @@ -465,11 +566,15 @@ impl Module { module, structs, struct_instantiations, + struct_variant_infos, + struct_variant_instantiation_infos, function_refs, function_defs, function_instantiations, field_handles, field_instantiations, + variant_field_infos, + variant_field_instantiation_infos, function_map, struct_map, single_signature_token_map, @@ -484,40 +589,42 @@ impl Module { struct_name_table: &[StructNameIndex], ) -> PartialVMResult { let struct_handle = module.struct_handle_at(struct_def.struct_handle); - let field_names = match &struct_def.field_information { - StructFieldInformation::Native => vec![], - StructFieldInformation::Declared(field_info) => field_info - .iter() - .map(|f| module.identifier_at(f.name).to_owned()) - .collect(), - }; let abilities = struct_handle.abilities; let name = module.identifier_at(struct_handle.name).to_owned(); let ty_params = struct_handle.type_parameters.clone(); - let fields = match &struct_def.field_information { + let layout = match &struct_def.field_information { StructFieldInformation::Native => unreachable!("native structs have been removed"), - StructFieldInformation::Declared(fields) => fields, + StructFieldInformation::Declared(fields) => { + let fields: PartialVMResult> = fields + .iter() + .map(|f| Self::make_field(module, f, struct_name_table)) + .collect(); + StructLayout::Single(fields?) + }, + StructFieldInformation::DeclaredVariants(variants) => { + let variants: PartialVMResult)>> = + variants + .iter() + .map(|v| { + let fields: PartialVMResult> = v + .fields + .iter() + .map(|f| Self::make_field(module, f, struct_name_table)) + .collect(); + fields.map(|fields| (module.identifier_at(v.name).to_owned(), fields)) + }) + .collect(); + StructLayout::Variants(variants?) + }, }; - let mut field_tys = vec![]; - for field in fields { - let ty = intern_type( - BinaryIndexedView::Module(module), - &field.signature.0, - struct_name_table, - )?; - debug_assert!(field_tys.len() < usize::max_value()); - field_tys.push(ty); - } - Ok(StructType { - field_tys, + layout, phantom_ty_params_mask: struct_handle .type_parameters .iter() .map(|ty| ty.is_phantom) .collect(), - field_names, abilities, ty_params, idx: struct_name_table[struct_def.struct_handle.0 as usize], @@ -526,6 +633,19 @@ impl Module { }) } + fn make_field( + module: &CompiledModule, + field: &FieldDefinition, + struct_name_table: &[StructNameIndex], + ) -> PartialVMResult<(Identifier, Type)> { + let ty = intern_type( + BinaryIndexedView::Module(module), + &field.signature.0, + struct_name_table, + )?; + Ok((module.identifier_at(field.name).to_owned(), ty)) + } + pub(crate) fn struct_at(&self, idx: StructDefinitionIndex) -> Arc { self.structs[idx.0 as usize].definition_struct_type.clone() } @@ -534,6 +654,17 @@ impl Module { &self.struct_instantiations[idx as usize] } + pub(crate) fn struct_variant_at(&self, idx: StructVariantHandleIndex) -> &StructVariantInfo { + &self.struct_variant_infos[idx.0 as usize] + } + + pub(crate) fn struct_variant_instantiation_at( + &self, + idx: StructVariantInstantiationIndex, + ) -> &StructVariantInfo { + &self.struct_variant_instantiation_infos[idx.0 as usize] + } + pub(crate) fn function_at(&self, idx: u16) -> &FunctionHandle { &self.function_refs[idx as usize] } @@ -566,6 +697,17 @@ impl Module { self.field_instantiations[idx.0 as usize].offset } + pub(crate) fn variant_field_info_at(&self, idx: VariantFieldHandleIndex) -> &VariantFieldInfo { + &self.variant_field_infos[idx.0 as usize] + } + + pub(crate) fn variant_field_instantiation_info_at( + &self, + idx: VariantFieldInstantiationIndex, + ) -> &VariantFieldInfo { + &self.variant_field_instantiation_infos[idx.0 as usize] + } + pub(crate) fn single_type_at(&self, idx: SignatureIndex) -> &Type { self.single_signature_token_map.get(&idx).unwrap() } 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 95489e6f6c126..89500bca8b8c5 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 @@ -12,7 +12,8 @@ use move_binary_format::{ file_format::{ Bytecode, CodeOffset, ConstantPoolIndex, FieldHandleIndex, FieldInstantiationIndex, FunctionHandleIndex, FunctionInstantiationIndex, SignatureIndex, - StructDefInstantiationIndex, StructDefinitionIndex, + StructDefInstantiationIndex, StructDefinitionIndex, StructVariantHandleIndex, + StructVariantInstantiationIndex, VariantFieldHandleIndex, VariantFieldInstantiationIndex, }, file_format_common::{instruction_key, Opcodes}, }; @@ -578,6 +579,22 @@ pub fn zero_cost_instruction_table() -> Vec<(Bytecode, GasCost)> { ImmBorrowFieldGeneric(FieldInstantiationIndex::new(0)), GasCost::new(0, 0), ), + ( + MutBorrowVariantField(VariantFieldHandleIndex::new(0)), + GasCost::new(0, 0), + ), + ( + MutBorrowVariantFieldGeneric(VariantFieldInstantiationIndex::new(0)), + GasCost::new(0, 0), + ), + ( + ImmBorrowVariantField(VariantFieldHandleIndex::new(0)), + GasCost::new(0, 0), + ), + ( + ImmBorrowVariantFieldGeneric(VariantFieldInstantiationIndex::new(0)), + GasCost::new(0, 0), + ), (Add, GasCost::new(0, 0)), (CopyLoc(0), GasCost::new(0, 0)), (StLoc(0), GasCost::new(0, 0)), @@ -611,6 +628,14 @@ pub fn zero_cost_instruction_table() -> Vec<(Bytecode, GasCost)> { UnpackGeneric(StructDefInstantiationIndex::new(0)), GasCost::new(0, 0), ), + ( + UnpackVariant(StructVariantHandleIndex::new(0)), + GasCost::new(0, 0), + ), + ( + UnpackVariantGeneric(StructVariantInstantiationIndex::new(0)), + GasCost::new(0, 0), + ), (Or, GasCost::new(0, 0)), (LdFalse, GasCost::new(0, 0)), (LdTrue, GasCost::new(0, 0)), @@ -647,6 +672,22 @@ pub fn zero_cost_instruction_table() -> Vec<(Bytecode, GasCost)> { PackGeneric(StructDefInstantiationIndex::new(0)), GasCost::new(0, 0), ), + ( + PackVariant(StructVariantHandleIndex::new(0)), + GasCost::new(0, 0), + ), + ( + PackVariantGeneric(StructVariantInstantiationIndex::new(0)), + GasCost::new(0, 0), + ), + ( + TestVariant(StructVariantHandleIndex::new(0)), + GasCost::new(0, 0), + ), + ( + TestVariantGeneric(StructVariantInstantiationIndex::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)), @@ -711,6 +752,22 @@ pub fn bytecode_instruction_costs() -> Vec<(Bytecode, GasCost)> { ImmBorrowFieldGeneric(FieldInstantiationIndex::new(0)), GasCost::new(1, 1), ), + ( + MutBorrowVariantField(VariantFieldHandleIndex::new(0)), + GasCost::new(1, 1), + ), + ( + MutBorrowVariantFieldGeneric(VariantFieldInstantiationIndex::new(0)), + GasCost::new(1, 1), + ), + ( + ImmBorrowVariantField(VariantFieldHandleIndex::new(0)), + GasCost::new(1, 1), + ), + ( + ImmBorrowVariantFieldGeneric(VariantFieldInstantiationIndex::new(0)), + GasCost::new(1, 1), + ), (Add, GasCost::new(1, 1)), (CopyLoc(0), GasCost::new(1, 1)), (StLoc(0), GasCost::new(1, 1)), @@ -744,6 +801,14 @@ pub fn bytecode_instruction_costs() -> Vec<(Bytecode, GasCost)> { UnpackGeneric(StructDefInstantiationIndex::new(0)), GasCost::new(2, 1), ), + ( + UnpackVariant(StructVariantHandleIndex::new(0)), + GasCost::new(2, 1), + ), + ( + UnpackVariantGeneric(StructVariantInstantiationIndex::new(0)), + GasCost::new(2, 1), + ), (Or, GasCost::new(2, 1)), (LdFalse, GasCost::new(1, 1)), (LdTrue, GasCost::new(1, 1)), @@ -780,6 +845,22 @@ pub fn bytecode_instruction_costs() -> Vec<(Bytecode, GasCost)> { PackGeneric(StructDefInstantiationIndex::new(0)), GasCost::new(2, 1), ), + ( + PackVariant(StructVariantHandleIndex::new(0)), + GasCost::new(2, 1), + ), + ( + PackVariantGeneric(StructVariantInstantiationIndex::new(0)), + GasCost::new(2, 1), + ), + ( + TestVariant(StructVariantHandleIndex::new(0)), + GasCost::new(2, 1), + ), + ( + TestVariantGeneric(StructVariantInstantiationIndex::new(0)), + GasCost::new(2, 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/gas.rs b/third_party/move/move-vm/types/src/gas.rs index 9c408422f377b..5765754854e18 100644 --- a/third_party/move/move-vm/types/src/gas.rs +++ b/third_party/move/move-vm/types/src/gas.rs @@ -31,6 +31,12 @@ pub enum SimpleInstruction { MutBorrowField, ImmBorrowFieldGeneric, MutBorrowFieldGeneric, + ImmBorrowVariantField, + MutBorrowVariantField, + ImmBorrowVariantFieldGeneric, + MutBorrowVariantFieldGeneric, + TestVariant, + TestVariantGeneric, CastU8, CastU64, @@ -89,6 +95,12 @@ impl SimpleInstruction { MutBorrowField => MUT_BORROW_FIELD, ImmBorrowFieldGeneric => IMM_BORROW_FIELD_GENERIC, MutBorrowFieldGeneric => MUT_BORROW_FIELD_GENERIC, + ImmBorrowVariantField => IMM_BORROW_VARIANT_FIELD, + MutBorrowVariantField => MUT_BORROW_VARIANT_FIELD, + ImmBorrowVariantFieldGeneric => IMM_BORROW_VARIANT_FIELD_GENERIC, + MutBorrowVariantFieldGeneric => MUT_BORROW_VARIANT_FIELD_GENERIC, + TestVariant => TEST_VARIANT, + TestVariantGeneric => TEST_VARIANT_GENERIC, CastU8 => CAST_U8, CastU64 => CAST_U64, @@ -176,12 +188,30 @@ pub trait GasMeter { args: impl ExactSizeIterator + Clone, ) -> PartialVMResult<()>; + fn charge_pack_variant( + &mut self, + is_generic: bool, + args: impl ExactSizeIterator + Clone, + ) -> PartialVMResult<()> { + // Currently mapped to pack, can be specialized if needed + self.charge_pack(is_generic, args) + } + fn charge_unpack( &mut self, is_generic: bool, args: impl ExactSizeIterator + Clone, ) -> PartialVMResult<()>; + fn charge_unpack_variant( + &mut self, + is_generic: bool, + args: impl ExactSizeIterator + Clone, + ) -> PartialVMResult<()> { + // Currently mapped to pack, can be specialized if needed + self.charge_unpack(is_generic, args) + } + fn charge_read_ref(&mut self, val: impl ValueView) -> PartialVMResult<()>; fn charge_write_ref( diff --git a/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs b/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs index 0d03824fa07b4..d96d01a23161a 100644 --- a/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs +++ b/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs @@ -10,6 +10,7 @@ use move_binary_format::{ errors::{Location, PartialVMError, PartialVMResult, VMResult}, file_format::{ Ability, AbilitySet, SignatureToken, StructHandle, StructTypeParameter, TypeParameterIndex, + VariantIndex, }, }; #[cfg(test)] @@ -128,8 +129,7 @@ impl DepthFormula { #[derive(Debug, Clone, Eq, Hash, PartialEq)] pub struct StructType { pub idx: StructNameIndex, - pub field_tys: Vec, - pub field_names: Vec, + pub layout: StructLayout, pub phantom_ty_params_mask: SmallBitVec, pub abilities: AbilitySet, pub ty_params: Vec, @@ -137,7 +137,83 @@ pub struct StructType { pub module: ModuleId, } +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub enum StructLayout { + Single(Vec<(Identifier, Type)>), + Variants(Vec<(Identifier, Vec<(Identifier, Type)>)>), +} + impl StructType { + /// Get the fields from this struct type. If this is a proper struct, the `variant` + /// must be None. Otherwise if its a variant struct, the variant for which the fields + /// are requested must be given. For non-matching parameters, the function returns + /// an empty list. + pub fn fields(&self, variant: Option) -> PartialVMResult<&[(Identifier, Type)]> { + match (&self.layout, variant) { + (StructLayout::Single(fields), None) => Ok(fields.as_slice()), + (StructLayout::Variants(variants), Some(variant)) + if (variant as usize) < variants.len() => + { + Ok(variants[variant as usize].1.as_slice()) + }, + _ => Err( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message( + "inconsistent struct field query: not a variant struct, or variant index out bounds" + .to_string(), + ), + ), + } + } + + /// Selects the field information from this struct type at the given offset. Returns + /// error if field is not defined. + pub fn field_at( + &self, + variant: Option, + offset: usize, + ) -> PartialVMResult<&(Identifier, Type)> { + let slice = self.fields(variant)?; + if offset < slice.len() { + Ok(&slice[offset]) + } else { + Err( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message( + format!( + "field offset out of bounds -- len {} got {}", + slice.len(), + offset + ), + ), + ) + } + } + + /// Same as `struct_type.fields(variant_opt).len()` + pub fn field_count(&self, variant: Option) -> u16 { + match (&self.layout, variant) { + (StructLayout::Single(fields), None) => fields.len() as u16, + (StructLayout::Variants(variants), Some(variant)) + if (variant as usize) < variants.len() => + { + variants[variant as usize].1.len() as u16 + }, + _ => 0, + } + } + + /// Returns a string for the variant for error messages. If this is + /// not a type with this variant, returns a string anyway indicating + /// its undefined. + pub fn variant_name_for_message(&self, variant: VariantIndex) -> String { + let variant = variant as usize; + match &self.layout { + StructLayout::Variants(variants) if variant < variants.len() => { + variants[variant].0.to_string() + }, + _ => "".to_string(), + } + } + pub fn ty_param_constraints(&self) -> impl ExactSizeIterator { self.ty_params.iter().map(|param| ¶m.constraints) } @@ -174,8 +250,7 @@ impl StructType { pub fn for_test() -> StructType { Self { idx: StructNameIndex(0), - field_tys: vec![], - field_names: vec![], + layout: StructLayout::Single(vec![]), phantom_ty_params_mask: SmallBitVec::new(), abilities: AbilitySet::EMPTY, ty_params: vec![], 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 c16ed09b541a4..86e2572a660f7 100644 --- a/third_party/move/move-vm/types/src/values/values_impl.rs +++ b/third_party/move/move-vm/types/src/values/values_impl.rs @@ -8,6 +8,7 @@ use crate::{ loaded_data::runtime_types::Type, views::{ValueView, ValueVisitor}, }; +use itertools::Itertools; use move_binary_format::{ errors::*, file_format::{Constant, SignatureToken}, @@ -981,6 +982,37 @@ impl StructRef { pub fn borrow_field(&self, idx: usize) -> PartialVMResult { Ok(Value(self.0.borrow_elem(idx)?)) } + + pub fn borrow_variant_field( + &self, + allowed: &[VariantIndex], + idx: usize, + variant_to_str: &impl Fn(VariantIndex) -> String, + ) -> PartialVMResult { + let tag = self.get_variant_tag()?; + if allowed.contains(&tag) { + Ok(Value(self.0.borrow_elem(idx + 1)?)) + } else { + Err( + PartialVMError::new(StatusCode::STRUCT_VARIANT_MISMATCH).with_message(format!( + "expected struct variant {}, found `{}`", + allowed.iter().cloned().map(variant_to_str).join(" or "), + variant_to_str(tag) + )), + ) + } + } + + pub fn test_variant(&self, variant: VariantIndex) -> PartialVMResult { + let tag = self.get_variant_tag()?; + Ok(Value::bool(variant == tag)) + } + + fn get_variant_tag(&self) -> PartialVMResult { + let tag_ref = Value(self.0.borrow_elem(0)?).value_as::()?; + let tag_value = tag_ref.read_ref()?; + tag_value.value_as::() + } } impl Locals { @@ -2489,6 +2521,43 @@ impl Struct { pub fn unpack(self) -> PartialVMResult> { Ok(self.fields.into_iter().map(Value)) } + + pub fn pack_variant>(variant: VariantIndex, vals: I) -> Self { + Self { + fields: iter::once(Value::u16(variant)) + .chain(vals) + .map(|v| v.0) + .collect(), + } + } + + pub fn unpack_variant( + self, + variant: VariantIndex, + variant_to_str: impl Fn(VariantIndex) -> String, + ) -> PartialVMResult> { + let Self { fields } = self; + if fields.is_empty() { + return Err( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message("invalid empty struct variant".to_string()), + ); + } + let mut values = fields.into_iter(); + let tag_value = Value(values.next().unwrap()); + let tag = tag_value.value_as::()?; + if tag == variant { + Ok(values.map(Value)) + } else { + Err( + PartialVMError::new(StatusCode::STRUCT_VARIANT_MISMATCH).with_message(format!( + "expected struct variant {}, found {}", + variant_to_str(variant), + variant_to_str(tag) + )), + ) + } + } } /*************************************************************************************** @@ -3028,6 +3097,7 @@ pub mod debug { * **************************************************************************************/ use crate::value_serde::{CustomDeserializer, CustomSerializer, RelaxedCustomSerDe}; +use move_binary_format::file_format::VariantIndex; use serde::{ de::Error as DeError, ser::{Error as SerError, SerializeSeq, SerializeTuple}, diff --git a/third_party/move/scripts/move_pr.sh b/third_party/move/scripts/move_pr.sh index d5caa8952ec93..01305adc705cd 100755 --- a/third_party/move/scripts/move_pr.sh +++ b/third_party/move/scripts/move_pr.sh @@ -107,6 +107,7 @@ MOVE_CRATES="\ # MOVE_COMPILER_V2 env var. MOVE_CRATES_V2_ENV_DEPENDENT="\ -p aptos-transactional-test-harness \ + -p bytecode-verifier-tests \ -p bytecode-verifier-transactional-tests \ -p move-async-vm \ -p move-cli \ diff --git a/third_party/move/testing-infra/test-generation/src/bytecode_generator.rs b/third_party/move/testing-infra/test-generation/src/bytecode_generator.rs index f83987fad606c..c0856d4debdc8 100644 --- a/third_party/move/testing-infra/test-generation/src/bytecode_generator.rs +++ b/third_party/move/testing-infra/test-generation/src/bytecode_generator.rs @@ -930,6 +930,10 @@ impl<'a> BytecodeGenerator<'a> { .struct_def_at(StructDefinitionIndex(struct_def_idx as TableIndex)); let fields = match &struct_def.field_information { StructFieldInformation::Native => panic!("Can't inhabit native structs"), + StructFieldInformation::DeclaredVariants(..) => { + // TODO(#13806): consider implementing for variants + panic!("Can't work with struct variants") + }, StructFieldInformation::Declared(fields) => fields.clone(), }; let mut bytecodes: Vec = fields @@ -958,6 +962,10 @@ impl<'a> BytecodeGenerator<'a> { .struct_def_at(StructDefinitionIndex(struct_def_idx as TableIndex)); let fields = match &struct_def.field_information { StructFieldInformation::Native => panic!("Can't inhabit native structs"), + StructFieldInformation::DeclaredVariants(..) => { + // TODO(#13806): consider adding support for variants + panic!("Can't work with struct variants") + }, StructFieldInformation::Declared(fields) => fields.clone(), }; let mut bytecodes: Vec = fields diff --git a/third_party/move/testing-infra/test-generation/src/summaries.rs b/third_party/move/testing-infra/test-generation/src/summaries.rs index 2376e65764cd3..9716d372ffdfe 100644 --- a/third_party/move/testing-infra/test-generation/src/summaries.rs +++ b/third_party/move/testing-infra/test-generation/src/summaries.rs @@ -617,5 +617,18 @@ pub fn instruction_summary(instruction: Bytecode, exact: bool) -> Summary { | Bytecode::VecPopBack(_) | Bytecode::VecUnpack(..) | Bytecode::VecSwap(_) => unimplemented!("Vector bytecode not supported yet"), + Bytecode::TestVariant(_) + | Bytecode::TestVariantGeneric(_) + | Bytecode::PackVariant(_) + | Bytecode::PackVariantGeneric(_) + | Bytecode::UnpackVariant(_) + | Bytecode::UnpackVariantGeneric(_) + | Bytecode::MutBorrowVariantField(_) + | Bytecode::MutBorrowVariantFieldGeneric(_) + | Bytecode::ImmBorrowVariantField(_) + | Bytecode::ImmBorrowVariantFieldGeneric(_) => { + // TODO(#13806): consider implementing for struct variants + unimplemented!("Struct variant bytecode not supported yet") + }, } } diff --git a/third_party/move/testing-infra/test-generation/src/transitions.rs b/third_party/move/testing-infra/test-generation/src/transitions.rs index 43706bd362b13..bf9586f23ee4e 100644 --- a/third_party/move/testing-infra/test-generation/src/transitions.rs +++ b/third_party/move/testing-infra/test-generation/src/transitions.rs @@ -837,6 +837,10 @@ pub fn stack_struct_borrow_field( StructFieldInformation::Native => { return Err(VMError::new("Borrow field on a native struct".to_string())); }, + StructFieldInformation::DeclaredVariants(..) => { + // TODO(#13806): consider implementing for struct variants + return Err(VMError::new("Variants not yet supported".to_string())); + }, StructFieldInformation::Declared(fields) => { let field_def = &fields[field_handle.field as usize]; &field_def.signature.0 diff --git a/third_party/move/testing-infra/test-generation/tests/struct_instructions.rs b/third_party/move/testing-infra/test-generation/tests/struct_instructions.rs index 13dd66777ccbf..c5717f2533355 100644 --- a/third_party/move/testing-infra/test-generation/tests/struct_instructions.rs +++ b/third_party/move/testing-infra/test-generation/tests/struct_instructions.rs @@ -89,6 +89,10 @@ fn get_field_signature<'a>(module: &'a CompiledModule, handle: &FieldHandle) -> match &struct_def.field_information { StructFieldInformation::Native => panic!("borrow field on a native struct"), StructFieldInformation::Declared(fields) => &fields[handle.field as usize].signature.0, + StructFieldInformation::DeclaredVariants(..) => { + // TODO(#13806): consider implementing for struct variants + panic!("struct variants not yet implemented") + }, } } 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 d649a8587b356..4f303af13d2a1 100644 --- a/third_party/move/tools/move-bytecode-utils/src/layout.rs +++ b/third_party/move/tools/move-bytecode-utils/src/layout.rs @@ -533,6 +533,9 @@ impl StructLayoutBuilder { }, }) }, + StructFieldInformation::DeclaredVariants(..) => { + bail!("struct variants not yet supported by layouts") + }, } } diff --git a/third_party/move/tools/move-disassembler/src/disassembler.rs b/third_party/move/tools/move-disassembler/src/disassembler.rs index 819512bcd7cf2..f48b58cde1b7a 100644 --- a/third_party/move/tools/move-disassembler/src/disassembler.rs +++ b/third_party/move/tools/move-disassembler/src/disassembler.rs @@ -2,22 +2,23 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use anyhow::{bail, format_err, Error, Result}; +use anyhow::{anyhow, bail, format_err, Error, Result}; use clap::Parser; use colored::*; use move_binary_format::{ binary_views::BinaryIndexedView, control_flow_graph::{ControlFlowGraph, VMControlFlowGraph}, file_format::{ - Ability, AbilitySet, Bytecode, CodeUnit, FieldHandleIndex, FunctionDefinition, + Ability, AbilitySet, Bytecode, CodeUnit, FieldDefinition, FunctionDefinition, FunctionDefinitionIndex, FunctionHandle, ModuleHandle, Signature, SignatureIndex, SignatureToken, StructDefinition, StructDefinitionIndex, StructFieldInformation, - StructTypeParameter, TableIndex, TypeSignature, Visibility, + StructTypeParameter, StructVariantHandleIndex, TableIndex, VariantIndex, Visibility, }, + views::FieldOrVariantIndex, }; use move_bytecode_source_map::{ mapping::SourceMapping, - source_map::{FunctionSourceMap, SourceName}, + source_map::{FunctionSourceMap, SourceName, StructSourceMap}, }; use move_compiler::compiled_unit::{CompiledUnit, NamedCompiledModule, NamedCompiledScript}; use move_core_types::{ident_str, identifier::IdentStr, language_storage::ModuleId}; @@ -294,57 +295,130 @@ impl<'a> Disassembler<'a> { // Formatting Helpers //*************************************************************************** - fn name_for_field(&self, field_idx: FieldHandleIndex) -> Result { - let field_handle = self.source_mapper.bytecode.field_handle_at(field_idx)?; - let struct_def = self - .source_mapper - .bytecode - .struct_def_at(field_handle.owner)?; - let field_def = match &struct_def.field_information { - StructFieldInformation::Native => { - return Err(format_err!("Attempt to access field on a native struct")); + fn name_for_struct(&self, idx: StructDefinitionIndex) -> Result { + let code = self.source_mapper.bytecode; + let struct_def = code.struct_def_at(idx)?; + Ok(code + .identifier_at(code.struct_handle_at(struct_def.struct_handle).name) + .to_string()) + } + + fn name_for_struct_variant(&self, idx: StructVariantHandleIndex) -> Result { + let code = self.source_mapper.bytecode; + let struct_variant_handle = code.struct_variant_handle_at(idx)?; + let struct_name = self.name_for_struct(struct_variant_handle.struct_index)?; + let variant_name = self.name_for_variant( + struct_variant_handle.struct_index, + struct_variant_handle.variant, + )?; + Ok(format!("{}/{}", struct_name, variant_name)) + } + + fn name_for_variant( + &self, + idx: StructDefinitionIndex, + variant: VariantIndex, + ) -> Result { + let code = self.source_mapper.bytecode; + let struct_def = code.struct_def_at(idx)?; + let variant_name = struct_def + .field_information + .variants() + .get(variant as usize) + .ok_or_else(|| anyhow!("Inconsistent variant offset"))? + .name; + Ok(format!("{}", code.identifier_at(variant_name))) + } + + fn name_for_field(&self, field_idx: FieldOrVariantIndex) -> Result { + let code = self.source_mapper.bytecode; + match field_idx { + FieldOrVariantIndex::FieldIndex(idx) => { + let field_handle = code.field_handle_at(idx)?; + let struct_name = self.name_for_struct(field_handle.owner)?; + let struct_def = code.struct_def_at(field_handle.owner)?; + let field_name = struct_def + .field_information + .fields(None) + .get(field_handle.field as usize) + .ok_or_else(|| anyhow!("Inconsistent field offset"))? + .name; + Ok(format!( + "{}.{}", + struct_name, + code.identifier_at(field_name) + )) }, - StructFieldInformation::Declared(fields) => fields - .get(field_handle.field as usize) - .ok_or_else(|| format_err!("Bad field index"))?, - }; - let field_name = self - .source_mapper - .bytecode - .identifier_at(field_def.name) - .to_string(); - let struct_handle = self - .source_mapper - .bytecode - .struct_handle_at(struct_def.struct_handle); - let struct_name = self - .source_mapper - .bytecode - .identifier_at(struct_handle.name) - .to_string(); - Ok(format!("{}.{}", struct_name, field_name)) + FieldOrVariantIndex::VariantFieldIndex(idx) => { + let field_handle = code.variant_field_handle_at(idx)?; + let struct_def = code.struct_def_at(field_handle.struct_index)?; + Ok(field_handle + .variants + .iter() + .map(|v| { + let variant_name = self.name_for_variant(field_handle.struct_index, *v)?; + let field_name = struct_def + .field_information + .fields(Some(*v)) + .get(field_handle.field as usize) + .map(|f| { + self.source_mapper + .bytecode + .identifier_at(f.name) + .to_string() + }) + .ok_or_else(|| anyhow!("Inconsistent field offset"))?; + Ok(format!("{}.{}", variant_name, field_name)) + }) + .collect::>>()? + .join("|")) + }, + } } - fn type_for_field(&self, field_idx: FieldHandleIndex) -> Result { - let field_handle = self.source_mapper.bytecode.field_handle_at(field_idx)?; - let struct_def = self - .source_mapper - .bytecode - .struct_def_at(field_handle.owner)?; - let field_def = match &struct_def.field_information { - StructFieldInformation::Native => { - return Err(format_err!("Attempt to access field on a native struct")); + fn type_for_field(&self, field_idx: FieldOrVariantIndex) -> Result { + let code = self.source_mapper.bytecode; + let (field_ty, struct_def_idx) = match field_idx { + FieldOrVariantIndex::FieldIndex(idx) => { + let field_handle = code.field_handle_at(idx)?; + let struct_def = code.struct_def_at(field_handle.owner)?; + ( + struct_def + .field_information + .fields(None) + .get(field_handle.field as usize) + .ok_or_else(|| anyhow!("Inconsistent field offset"))? + .signature + .0 + .clone(), + field_handle.owner, + ) + }, + FieldOrVariantIndex::VariantFieldIndex(idx) => { + let field_handle = code.variant_field_handle_at(idx)?; + // We can take any representative for verified code. + let Some(variant) = field_handle.variants.first().cloned() else { + bail!("Inconsistent empty variant field list") + }; + let struct_def = code.struct_def_at(field_handle.struct_index)?; + ( + struct_def + .field_information + .fields(Some(variant)) + .get(field_handle.field as usize) + .ok_or_else(|| anyhow!("Inconsistent field offset"))? + .signature + .0 + .clone(), + field_handle.struct_index, + ) }, - StructFieldInformation::Declared(fields) => fields - .get(field_handle.field as usize) - .ok_or_else(|| format_err!("Bad field index"))?, }; let struct_source_info = self .source_mapper .source_map - .get_struct_source_map(field_handle.owner)?; - let field_type_sig = field_def.signature.0.clone(); - let ty = self.disassemble_sig_tok(field_type_sig, &struct_source_info.type_parameters)?; + .get_struct_source_map(struct_def_idx)?; + let ty = self.disassemble_sig_tok(field_ty, &struct_source_info.type_parameters)?; Ok(ty) } @@ -354,22 +428,27 @@ impl<'a> Disassembler<'a> { signature: &Signature, type_param_context: &[SourceName], ) -> Result<(String, String)> { - let struct_definition = self.get_struct_def(struct_idx)?; + let name = self.name_for_struct(struct_idx)?; let type_arguments = signature .0 .iter() .map(|sig_tok| self.disassemble_sig_tok(sig_tok.clone(), type_param_context)) .collect::>>()?; + Ok((name, Self::format_type_params(&type_arguments))) + } - let struct_handle = self - .source_mapper - .bytecode - .struct_handle_at(struct_definition.struct_handle); - let name = self - .source_mapper - .bytecode - .identifier_at(struct_handle.name) - .to_string(); + fn variant_struct_type_info( + &self, + struct_variant_idx: StructVariantHandleIndex, + signature: &Signature, + type_param_context: &[SourceName], + ) -> Result<(String, String)> { + let name = self.name_for_struct_variant(struct_variant_idx)?; + let type_arguments = signature + .0 + .iter() + .map(|sig_tok| self.disassemble_sig_tok(sig_tok.clone(), type_param_context)) + .collect::>>()?; Ok((name, Self::format_type_params(&type_arguments))) } @@ -609,8 +688,9 @@ impl<'a> Disassembler<'a> { Ok(format!("ImmBorrowLoc[{}]({}: {})", local_idx, name, ty)) }, Bytecode::MutBorrowField(field_idx) => { - let name = self.name_for_field(*field_idx)?; - let ty = self.type_for_field(*field_idx)?; + let idx = FieldOrVariantIndex::FieldIndex(*field_idx); + let name = self.name_for_field(idx)?; + let ty = self.type_for_field(idx)?; Ok(format!("MutBorrowField[{}]({}: {})", field_idx, name, ty)) }, Bytecode::MutBorrowFieldGeneric(field_idx) => { @@ -618,16 +698,18 @@ impl<'a> Disassembler<'a> { .source_mapper .bytecode .field_instantiation_at(*field_idx)?; - let name = self.name_for_field(field_inst.handle)?; - let ty = self.type_for_field(field_inst.handle)?; + let idx = FieldOrVariantIndex::FieldIndex(field_inst.handle); + let name = self.name_for_field(idx)?; + let ty = self.type_for_field(idx)?; Ok(format!( "MutBorrowFieldGeneric[{}]({}: {})", field_idx, name, ty )) }, Bytecode::ImmBorrowField(field_idx) => { - let name = self.name_for_field(*field_idx)?; - let ty = self.type_for_field(*field_idx)?; + let idx = FieldOrVariantIndex::FieldIndex(*field_idx); + let name = self.name_for_field(idx)?; + let ty = self.type_for_field(idx)?; Ok(format!("ImmBorrowField[{}]({}: {})", field_idx, name, ty)) }, Bytecode::ImmBorrowFieldGeneric(field_idx) => { @@ -635,13 +717,58 @@ impl<'a> Disassembler<'a> { .source_mapper .bytecode .field_instantiation_at(*field_idx)?; - let name = self.name_for_field(field_inst.handle)?; - let ty = self.type_for_field(field_inst.handle)?; + let idx = FieldOrVariantIndex::FieldIndex(field_inst.handle); + let name = self.name_for_field(idx)?; + let ty = self.type_for_field(idx)?; Ok(format!( "ImmBorrowFieldGeneric[{}]({}: {})", field_idx, name, ty )) }, + Bytecode::MutBorrowVariantField(field_idx) => { + let idx = FieldOrVariantIndex::VariantFieldIndex(*field_idx); + let name = self.name_for_field(idx)?; + let ty = self.type_for_field(idx)?; + Ok(format!( + "MutBorrowVariantField[{}]({}: {})", + field_idx, name, ty + )) + }, + Bytecode::MutBorrowVariantFieldGeneric(field_idx) => { + let field_inst = self + .source_mapper + .bytecode + .variant_field_instantiation_at(*field_idx)?; + let idx = FieldOrVariantIndex::VariantFieldIndex(field_inst.handle); + let name = self.name_for_field(idx)?; + let ty = self.type_for_field(idx)?; + Ok(format!( + "MutBorrowVariantFieldGeneric[{}]({}: {})", + field_idx, name, ty + )) + }, + Bytecode::ImmBorrowVariantField(field_idx) => { + let idx = FieldOrVariantIndex::VariantFieldIndex(*field_idx); + let name = self.name_for_field(idx)?; + let ty = self.type_for_field(idx)?; + Ok(format!( + "ImmBorrowVariantField[{}]({}: {})", + field_idx, name, ty + )) + }, + Bytecode::ImmBorrowVariantFieldGeneric(field_idx) => { + let field_inst = self + .source_mapper + .bytecode + .variant_field_instantiation_at(*field_idx)?; + let idx = FieldOrVariantIndex::VariantFieldIndex(field_inst.handle); + let name = self.name_for_field(idx)?; + let ty = self.type_for_field(idx)?; + Ok(format!( + "ImmBorrowVariantFieldGeneric[{}]({}: {})", + field_idx, name, ty + )) + }, Bytecode::Pack(struct_idx) => { let (name, ty_params) = self.struct_type_info( *struct_idx, @@ -651,14 +778,9 @@ impl<'a> Disassembler<'a> { Ok(format!("Pack[{}]({}{})", struct_idx, name, ty_params)) }, Bytecode::PackGeneric(struct_idx) => { - let struct_inst = self - .source_mapper - .bytecode - .struct_instantiation_at(*struct_idx)?; - let type_params = self - .source_mapper - .bytecode - .signature_at(struct_inst.type_parameters); + let code = self.source_mapper.bytecode; + let struct_inst = code.struct_instantiation_at(*struct_idx)?; + let type_params = code.signature_at(struct_inst.type_parameters); let (name, ty_params) = self.struct_type_info( struct_inst.def, type_params, @@ -678,14 +800,9 @@ impl<'a> Disassembler<'a> { Ok(format!("Unpack[{}]({}{})", struct_idx, name, ty_params)) }, Bytecode::UnpackGeneric(struct_idx) => { - let struct_inst = self - .source_mapper - .bytecode - .struct_instantiation_at(*struct_idx)?; - let type_params = self - .source_mapper - .bytecode - .signature_at(struct_inst.type_parameters); + let code = self.source_mapper.bytecode; + let struct_inst = code.struct_instantiation_at(*struct_idx)?; + let type_params = code.signature_at(struct_inst.type_parameters); let (name, ty_params) = self.struct_type_info( struct_inst.def, type_params, @@ -696,6 +813,81 @@ impl<'a> Disassembler<'a> { struct_idx, name, ty_params )) }, + Bytecode::PackVariant(struct_idx) => { + let (name, ty_params) = self.variant_struct_type_info( + *struct_idx, + &Signature(vec![]), + &function_source_map.type_parameters, + )?; + Ok(format!( + "PackVariant[{}]({}{})", + struct_idx, name, ty_params + )) + }, + Bytecode::PackVariantGeneric(struct_idx) => { + let code = self.source_mapper.bytecode; + let struct_inst = code.struct_variant_instantiation_at(*struct_idx)?; + let type_params = code.signature_at(struct_inst.type_parameters); + let (name, ty_params) = self.variant_struct_type_info( + struct_inst.handle, + type_params, + &function_source_map.type_parameters, + )?; + Ok(format!( + "PackVariantGeneric[{}]({}{})", + struct_idx, name, ty_params + )) + }, + Bytecode::UnpackVariant(struct_idx) => { + let (name, ty_params) = self.variant_struct_type_info( + *struct_idx, + &Signature(vec![]), + &function_source_map.type_parameters, + )?; + Ok(format!( + "UnpackVariant[{}]({}{})", + struct_idx, name, ty_params + )) + }, + Bytecode::UnpackVariantGeneric(struct_idx) => { + let code = self.source_mapper.bytecode; + let struct_inst = code.struct_variant_instantiation_at(*struct_idx)?; + let type_params = code.signature_at(struct_inst.type_parameters); + let (name, ty_params) = self.variant_struct_type_info( + struct_inst.handle, + type_params, + &function_source_map.type_parameters, + )?; + Ok(format!( + "UnpackVariantGeneric[{}]({}{})", + struct_idx, name, ty_params + )) + }, + Bytecode::TestVariant(struct_idx) => { + let (name, ty_params) = self.variant_struct_type_info( + *struct_idx, + &Signature(vec![]), + &function_source_map.type_parameters, + )?; + Ok(format!( + "TestVariant[{}]({}{})", + struct_idx, name, ty_params + )) + }, + Bytecode::TestVariantGeneric(struct_idx) => { + let code = self.source_mapper.bytecode; + let struct_inst = code.struct_variant_instantiation_at(*struct_idx)?; + let type_params = code.signature_at(struct_inst.type_parameters); + let (name, ty_params) = self.variant_struct_type_info( + struct_inst.handle, + type_params, + &function_source_map.type_parameters, + )?; + Ok(format!( + "TestVariantGeneric[{}]({}{})", + struct_idx, name, ty_params + )) + }, Bytecode::Exists(struct_idx) => { let (name, ty_params) = self.struct_type_info( *struct_idx, @@ -1175,76 +1367,79 @@ impl<'a> Disassembler<'a> { .source_mapper .source_map .get_struct_source_map(struct_def_idx)?; - - let field_info: Option> = - match &struct_definition.field_information { - StructFieldInformation::Native => None, - StructFieldInformation::Declared(fields) => Some( - fields - .iter() - .map(|field_definition| { - let type_sig = &field_definition.signature; - let field_name = self - .source_mapper - .bytecode - .identifier_at(field_definition.name); - (field_name, type_sig) - }) - .collect(), - ), - }; - - let native = if field_info.is_none() { "native " } else { "" }; - - let abilities = if struct_handle.abilities == AbilitySet::EMPTY { - String::new() - } else { - let ability_vec: Vec<_> = struct_handle - .abilities - .into_iter() - .map(Self::format_ability) - .collect(); - format!(" has {}", ability_vec.join(", ")) - }; - let name = self .source_mapper .bytecode .identifier_at(struct_handle.name) .to_string(); - let ty_params = Self::disassemble_struct_type_formals( &struct_source_map.type_parameters, &struct_handle.type_parameters, ); - let mut fields = match field_info { - None => vec![], - Some(field_info) => field_info - .iter() - .map(|(name, ty)| { - let ty_str = - self.disassemble_sig_tok(ty.0.clone(), &struct_source_map.type_parameters)?; - Ok(format!("{}: {}", name, ty_str)) - }) - .collect::>>()?, + let abilities = if struct_handle.abilities == AbilitySet::EMPTY { + String::new() + } else { + let ability_vec: Vec<_> = struct_handle + .abilities + .into_iter() + .map(Self::format_ability) + .collect(); + format!(" has {}", ability_vec.join(", ")) }; - if let Some(first_elem) = fields.first_mut() { - first_elem.insert_str(0, "{\n\t"); - } - - if let Some(last_elem) = fields.last_mut() { - last_elem.push_str("\n}"); + match &struct_definition.field_information { + StructFieldInformation::Native => { + Ok(format!("native struct {}{}{}", name, ty_params, abilities)) + }, + StructFieldInformation::Declared(fields) => Ok(format!( + "struct {}{}{} {{\n{}\n}}", + name, + ty_params, + abilities, + self.print_fields(struct_source_map, fields.iter()) + )), + StructFieldInformation::DeclaredVariants(variants) => { + let variant_strs = variants + .iter() + .map(|v| { + let name_str = self.source_mapper.bytecode.identifier_at(v.name); + format!( + " {}{{\n{}\n }}", + name_str, + self.print_fields(struct_source_map, v.fields.iter()) + ) + }) + .collect::>() + .join(",\n"); + Ok(format!( + "enum {}{}{} {{\n{}\n}}", + name, ty_params, abilities, variant_strs + )) + }, } + } - Ok(format!( - "{native}struct {name}{ty_params}{abilities} {fields}", - native = native, - name = name, - ty_params = ty_params, - abilities = abilities, - fields = &fields.join(",\n\t"), - )) + fn print_fields<'l>( + &self, + source_map: &StructSourceMap, + fields: impl Iterator, + ) -> String { + fields + .map(|field_definition| { + let field_name = self + .source_mapper + .bytecode + .identifier_at(field_definition.name); + let ty_str = self + .disassemble_sig_tok( + field_definition.signature.0.clone(), + &source_map.type_parameters, + ) + .unwrap_or_else(|_| "??".to_string()); + format!("\t{}: {}", field_name, ty_str) + }) + .collect::>() + .join(",\n") } pub fn disassemble(&self) -> Result { 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 3c8983fae9311..79b5d5cb2d3a5 100644 --- a/third_party/move/tools/move-resource-viewer/src/lib.rs +++ b/third_party/move/tools/move-resource-viewer/src/lib.rs @@ -284,6 +284,9 @@ impl MoveValueAnnotator { .map(|field_def| self.resolve_signature(module, &field_def.signature.0, limit)) .collect::>()?, }), + StructFieldInformation::DeclaredVariants(..) => Err(anyhow!( + "Struct variants not yet supported by resource viewer" + )), } } @@ -425,6 +428,9 @@ impl MoveValueAnnotator { .iter() .map(|field_def| module.identifier_at(field_def.name).to_owned()) .collect()), + StructFieldInformation::DeclaredVariants(..) => Err(anyhow!( + "Struct variants not yet supported by resource viewer" + )), } } diff --git a/types/src/vm/configs.rs b/types/src/vm/configs.rs index 41db8af9f60e0..73f4d74650b60 100644 --- a/types/src/vm/configs.rs +++ b/types/src/vm/configs.rs @@ -54,6 +54,7 @@ pub fn aptos_prod_verifier_config(features: &Features) -> VerifierConfig { max_dependency_depth: Some(256), max_push_size: Some(10000), max_struct_definitions: None, + max_struct_variants: None, max_fields_in_struct: None, max_function_definitions: None, max_back_edges_per_function: None,