diff --git a/external-crates/move/crates/move-compiler/src/shared/program_info.rs b/external-crates/move/crates/move-compiler/src/shared/program_info.rs index 2ceadbb461cce..1c2f2ce588fc7 100644 --- a/external-crates/move/crates/move-compiler/src/shared/program_info.rs +++ b/external-crates/move/crates/move-compiler/src/shared/program_info.rs @@ -3,9 +3,7 @@ use std::{collections::BTreeMap, fmt::Display, sync::Arc}; -use move_ir_types::location::Loc; -use move_symbol_pool::Symbol; - +use self::known_attributes::AttributePosition; use crate::{ expansion::ast::{AbilitySet, Attributes, ModuleIdent, TargetKind, Visibility}, naming::ast::{ @@ -15,11 +13,12 @@ use crate::{ parser::ast::{ConstantName, DatatypeName, Field, FunctionName, VariantName}, shared::unique_map::UniqueMap, shared::*, + sui_mode::info::SuiInfo, typing::ast::{self as T}, FullyCompiledProgram, }; - -use self::known_attributes::AttributePosition; +use move_ir_types::location::Loc; +use move_symbol_pool::Symbol; #[derive(Debug, Clone)] pub struct FunctionInfo { @@ -52,6 +51,14 @@ pub struct ModuleInfo { pub constants: UniqueMap, } +#[derive(Debug, Clone)] +pub struct ProgramInfo { + pub modules: UniqueMap, + pub sui_flavor_info: Option, +} +pub type NamingProgramInfo = ProgramInfo; +pub type TypingProgramInfo = ProgramInfo; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum DatatypeKind { Struct, @@ -66,13 +73,6 @@ pub enum NamedMemberKind { Constant, } -#[derive(Debug, Clone)] -pub struct ProgramInfo { - pub modules: UniqueMap, -} -pub type NamingProgramInfo = ProgramInfo; -pub type TypingProgramInfo = ProgramInfo; - macro_rules! program_info { ($pre_compiled_lib:ident, $prog:ident, $pass:ident, $module_use_funs:ident) => {{ let all_modules = $prog.modules.key_cloned_iter(); @@ -118,12 +118,16 @@ macro_rules! program_info { } } } - ProgramInfo { modules } + ProgramInfo { + modules, + sui_flavor_info: None, + } }}; } impl TypingProgramInfo { pub fn new( + env: &CompilationEnv, pre_compiled_lib: Option>, modules: &UniqueMap, mut module_use_funs: BTreeMap, @@ -133,7 +137,18 @@ impl TypingProgramInfo { } let mut module_use_funs = Some(&mut module_use_funs); let prog = Prog { modules }; - program_info!(pre_compiled_lib, prog, typing, module_use_funs) + let pcl = pre_compiled_lib.clone(); + let mut info = program_info!(pcl, prog, typing, module_use_funs); + // TODO we should really have an idea of root package flavor here + // but this feels roughly equivalent + if env + .package_configs() + .any(|(_, config)| config.flavor == Flavor::Sui) + { + let sui_flavor_info = SuiInfo::new(pre_compiled_lib, modules, &info); + info.sui_flavor_info = Some(sui_flavor_info); + }; + info } } diff --git a/external-crates/move/crates/move-compiler/src/sui_mode/info.rs b/external-crates/move/crates/move-compiler/src/sui_mode/info.rs new file mode 100644 index 0000000000000..2bfaedeafef51 --- /dev/null +++ b/external-crates/move/crates/move-compiler/src/sui_mode/info.rs @@ -0,0 +1,308 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +//! ProgramInfo extension for Sui Flavor +//! Contains information that may be expensive to compute and is needed only for Sui + +use std::{ + collections::{BTreeMap, BTreeSet}, + sync::Arc, +}; + +use crate::{ + expansion::ast::{Fields, ModuleIdent}, + naming::ast as N, + parser::ast::{Ability_, DatatypeName, Field}, + shared::{ + program_info::{DatatypeKind, TypingProgramInfo}, + unique_map::UniqueMap, + }, + sui_mode::{ + OBJECT_MODULE_NAME, SUI_ADDR_NAME, TRANSFER_FUNCTION_NAME, TRANSFER_MODULE_NAME, + UID_TYPE_NAME, + }, + typing::{ast as T, visitor::TypingVisitorContext}, + FullyCompiledProgram, +}; +use move_ir_types::location::Loc; +use move_proc_macros::growing_stack; + +#[derive(Debug, Clone, Copy)] +pub enum UIDHolder { + /// is `sui::object::UID`` + IsUID, + /// holds UID directly as one of the fields + Direct { field: Field, ty: Loc }, + /// holds a type which in turn `Direct`ly or `Indirect`ly holds UID + Indirect { field: Field, ty: Loc, uid: Loc }, +} + +#[derive(Debug, Clone, Copy)] +pub enum TransferKind { + /// The object has store + PublicTransfer(Loc), + /// transferred within the module to an address vis `sui::transfer::transfer` + PrivateTransfer(Loc), +} + +#[derive(Debug, Clone)] +pub struct SuiInfo { + /// All types that contain a UID, directly or indirectly + /// This requires a DFS traversal of type declarations + pub uid_holders: BTreeMap<(ModuleIdent, DatatypeName), UIDHolder>, + /// All types that either have store or are transferred privately + pub transferred: BTreeMap<(ModuleIdent, DatatypeName), TransferKind>, +} + +impl SuiInfo { + pub fn new( + pre_compiled_lib: Option>, + modules: &UniqueMap, + info: &TypingProgramInfo, + ) -> Self { + assert!(info.sui_flavor_info.is_none()); + let uid_holders = all_uid_holders(info); + let transferred = all_transferred(pre_compiled_lib, modules, info); + Self { + uid_holders, + transferred, + } + } +} + +/// DFS traversal to find all UID holders +fn all_uid_holders(info: &TypingProgramInfo) -> BTreeMap<(ModuleIdent, DatatypeName), UIDHolder> { + fn merge_uid_holder(u1: UIDHolder, u2: UIDHolder) -> UIDHolder { + match (u1, u2) { + (u @ UIDHolder::IsUID, _) | (_, u @ UIDHolder::IsUID) => u, + (d @ UIDHolder::Direct { .. }, _) | (_, d @ UIDHolder::Direct { .. }) => d, + (u1, _) => u1, + } + } + + fn merge_uid_holder_opt( + u1_opt: Option, + u2_opt: Option, + ) -> Option { + match (u1_opt, u2_opt) { + (Some(u1), Some(u2)) => Some(merge_uid_holder(u1, u2)), + (o1, o2) => o1.or(o2), + } + } + + // returns true if the type at the given position is a phantom type + fn phantom_positions( + info: &TypingProgramInfo, + sp!(_, tn_): &N::TypeName, + ) -> Vec { + match tn_ { + N::TypeName_::Multiple(n) => vec![false; *n], + N::TypeName_::Builtin(sp!(_, b_)) => b_ + .tparam_constraints(Loc::invalid()) + .into_iter() + .map(|_| false) + .collect(), + N::TypeName_::ModuleType(m, n) => { + let ty_params = match info.datatype_kind(m, n) { + DatatypeKind::Struct => &info.struct_definition(m, n).type_parameters, + DatatypeKind::Enum => &info.enum_definition(m, n).type_parameters, + }; + ty_params.iter().map(|tp| tp.is_phantom).collect() + } + } + } + + #[growing_stack] + fn visit_ty( + info: &TypingProgramInfo, + visited: &mut BTreeSet<(ModuleIdent, DatatypeName)>, + uid_holders: &mut BTreeMap<(ModuleIdent, DatatypeName), UIDHolder>, + sp!(_, ty_): &N::Type, + ) -> Option { + match ty_ { + N::Type_::Unit + | N::Type_::Param(_) + | N::Type_::Var(_) + | N::Type_::Fun(_, _) + | N::Type_::Anything + | N::Type_::UnresolvedError => None, + + N::Type_::Ref(_, inner) => visit_ty(info, visited, uid_holders, inner), + + N::Type_::Apply(_, sp!(_, tn_), _) + if tn_.is(SUI_ADDR_NAME, OBJECT_MODULE_NAME, UID_TYPE_NAME) => + { + Some(UIDHolder::IsUID) + } + + N::Type_::Apply(_, tn, tys) => { + let phantom_positions = phantom_positions(info, tn); + let ty_args_holder = tys + .iter() + .zip(phantom_positions) + .filter(|(_t, is_phantom)| *is_phantom) + .map(|(t, _is_phantom)| visit_ty(info, visited, uid_holders, t)) + .fold(None, merge_uid_holder_opt); + let tn_holder = if let N::TypeName_::ModuleType(m, n) = tn.value { + visit_decl(info, visited, uid_holders, m, n); + uid_holders.get(&(m, n)).copied() + } else { + None + }; + merge_uid_holder_opt(ty_args_holder, tn_holder) + } + } + } + + #[growing_stack] + fn visit_fields( + info: &TypingProgramInfo, + visited: &mut BTreeSet<(ModuleIdent, DatatypeName)>, + uid_holders: &mut BTreeMap<(ModuleIdent, DatatypeName), UIDHolder>, + fields: &Fields, + ) -> Option { + fields + .key_cloned_iter() + .map(|(field, (_, ty))| { + Some(match visit_ty(info, visited, uid_holders, ty)? { + UIDHolder::IsUID => UIDHolder::Direct { field, ty: ty.loc }, + UIDHolder::Direct { field, ty: uid } + | UIDHolder::Indirect { field, uid, ty: _ } => UIDHolder::Indirect { + field, + ty: ty.loc, + uid, + }, + }) + }) + .fold(None, merge_uid_holder_opt) + } + + #[growing_stack] + fn visit_decl( + info: &TypingProgramInfo, + visited: &mut BTreeSet<(ModuleIdent, DatatypeName)>, + uid_holders: &mut BTreeMap<(ModuleIdent, DatatypeName), UIDHolder>, + mident: ModuleIdent, + tn: DatatypeName, + ) { + if visited.contains(&(mident, tn)) { + return; + } + visited.insert((mident, tn)); + + let uid_holder_opt = match info.datatype_kind(&mident, &tn) { + DatatypeKind::Struct => match &info.struct_definition(&mident, &tn).fields { + N::StructFields::Defined(_, fields) => { + visit_fields(info, visited, uid_holders, fields) + } + N::StructFields::Native(_) => None, + }, + DatatypeKind::Enum => info + .enum_definition(&mident, &tn) + .variants + .iter() + .filter_map(|(_, _, v)| match &v.fields { + N::VariantFields::Defined(_, fields) => Some(fields), + N::VariantFields::Empty => None, + }) + .map(|fields| visit_fields(info, visited, uid_holders, fields)) + .fold(None, merge_uid_holder_opt), + }; + if let Some(uid_holder) = uid_holder_opt { + uid_holders.insert((mident, tn), uid_holder); + } + } + + // iterate over all struct/enum declarations + let visited = &mut BTreeSet::new(); + let mut uid_holders = BTreeMap::new(); + for (mident, mdef) in info.modules.key_cloned_iter() { + let datatypes = mdef + .structs + .key_cloned_iter() + .map(|(n, _)| n) + .chain(mdef.enums.key_cloned_iter().map(|(n, _)| n)); + for tn in datatypes { + visit_decl(info, visited, &mut uid_holders, mident, tn) + } + } + uid_holders +} + +fn all_transferred( + pre_compiled_lib: Option>, + modules: &UniqueMap, + info: &TypingProgramInfo, +) -> BTreeMap<(ModuleIdent, DatatypeName), TransferKind> { + let mut transferred = BTreeMap::new(); + for (mident, minfo) in info.modules.key_cloned_iter() { + for (s, sdef) in minfo.structs.key_cloned_iter() { + if !sdef.abilities.has_ability_(Ability_::Key) { + continue; + } + let Some(store_loc) = sdef.abilities.ability_loc_(Ability_::Store) else { + continue; + }; + transferred.insert((mident, s), TransferKind::PublicTransfer(store_loc)); + } + + let mdef = match modules.get(&mident) { + Some(mdef) => mdef, + None => pre_compiled_lib + .as_ref() + .unwrap() + .typing + .modules + .get(&mident) + .unwrap(), + }; + for (_, _, fdef) in &mdef.functions { + add_private_transfers(&mut transferred, fdef); + } + } + transferred +} + +fn add_private_transfers( + transferred: &mut BTreeMap<(ModuleIdent, DatatypeName), TransferKind>, + fdef: &T::Function, +) { + struct TransferVisitor<'a> { + transferred: &'a mut BTreeMap<(ModuleIdent, DatatypeName), TransferKind>, + } + impl<'a> TypingVisitorContext for TransferVisitor<'a> { + fn add_warning_filter_scope(&mut self, _: crate::diagnostics::WarningFilters) { + unreachable!("no warning filters in function bodies") + } + + fn pop_warning_filter_scope(&mut self) { + unreachable!("no warning filters in function bodies") + } + + fn visit_exp_custom(&mut self, e: &T::Exp) -> bool { + use T::UnannotatedExp_ as E; + let E::ModuleCall(call) = &e.exp.value else { + return false; + }; + if !call.is(SUI_ADDR_NAME, TRANSFER_MODULE_NAME, TRANSFER_FUNCTION_NAME) { + return false; + } + let [sp!(_, ty)] = call.type_arguments.as_slice() else { + return false; + }; + let Some(n) = ty.type_name().and_then(|t| t.value.datatype_name()) else { + return false; + }; + self.transferred + .entry(n) + .or_insert_with(|| TransferKind::PrivateTransfer(e.exp.loc)); + false + } + } + + let mut visitor = TransferVisitor { transferred }; + match &fdef.body.value { + T::FunctionBody_::Native | &T::FunctionBody_::Macro => (), + T::FunctionBody_::Defined(seq) => visitor.visit_seq(seq), + } +} diff --git a/external-crates/move/crates/move-compiler/src/sui_mode/mod.rs b/external-crates/move/crates/move-compiler/src/sui_mode/mod.rs index f0a3c3587f461..d803ec83b83dd 100644 --- a/external-crates/move/crates/move-compiler/src/sui_mode/mod.rs +++ b/external-crates/move/crates/move-compiler/src/sui_mode/mod.rs @@ -6,6 +6,7 @@ use move_symbol_pool::Symbol; use crate::diagnostics::codes::{custom, DiagnosticInfo, Severity}; pub mod id_leak; +pub mod info; pub mod linters; pub mod typing; diff --git a/external-crates/move/crates/move-compiler/src/typing/translate.rs b/external-crates/move/crates/move-compiler/src/typing/translate.rs index dd39bddd9d8bb..97de9f86f69b0 100644 --- a/external-crates/move/crates/move-compiler/src/typing/translate.rs +++ b/external-crates/move/crates/move-compiler/src/typing/translate.rs @@ -81,7 +81,8 @@ pub fn program( .into_iter() .map(|(mident, minfo)| (mident, minfo.use_funs)) .collect(); - let module_info = TypingProgramInfo::new(pre_compiled_lib, &modules, module_use_funs); + let module_info = + TypingProgramInfo::new(compilation_env, pre_compiled_lib, &modules, module_use_funs); let prog = T::Program { modules, info: Arc::new(module_info),