From 05489dfd24c6872d95c12e18e48eb32b71d22d77 Mon Sep 17 00:00:00 2001 From: beer-1 <147697694+beer-1@users.noreply.github.com> Date: Wed, 1 May 2024 23:39:50 +0900 Subject: [PATCH] add cyclic module dependency checking --- crates/types/src/module.rs | 34 ++++++++++++++++++++++++++-------- crates/vm/src/move_vm.rs | 5 +++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/crates/types/src/module.rs b/crates/types/src/module.rs index 3c52eb7b..2cc39db8 100644 --- a/crates/types/src/module.rs +++ b/crates/types/src/module.rs @@ -2,11 +2,13 @@ // SPDX-License-Identifier: BUSL-1.1 use move_binary_format::access::ModuleAccess; +use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_binary_format::CompiledModule; use move_core_types::language_storage::ModuleId; +use move_core_types::vm_status::StatusCode; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::fmt; #[derive(Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] @@ -74,7 +76,7 @@ impl ModuleBundle { pub fn sorted_code_and_modules( self, compiled_modules: &[CompiledModule], - ) -> (Self, Vec, Vec<&CompiledModule>) { + ) -> PartialVMResult<(Self, Vec, Vec<&CompiledModule>)> { let mut map: BTreeMap, &CompiledModule)> = BTreeMap::new(); let ModuleBundle { codes } = self; @@ -84,7 +86,7 @@ impl ModuleBundle { let mut order = vec![]; for id in map.keys() { - sort_by_deps(&map, &mut order, id.clone()); + sort_by_deps(&map, &mut order, id.clone(), &mut BTreeSet::new())?; } let mut codes = vec![]; @@ -97,7 +99,7 @@ impl ModuleBundle { module_ids.push(id.short_str_lossless()); } - (Self::new(codes), module_ids, compiled_modules) + Ok((Self::new(codes), module_ids, compiled_modules)) } } @@ -105,21 +107,37 @@ pub fn sort_by_deps( map: &BTreeMap, &CompiledModule)>, order: &mut Vec, id: ModuleId, -) { + seen_modules: &mut BTreeSet, +) -> PartialVMResult<()> { if order.contains(&id) { - return; + return Ok(()); } + // check for circular dependencies + if seen_modules.contains(&id) { + return Err(PartialVMError::new(StatusCode::CYCLIC_MODULE_DEPENDENCY) + .with_message(format!("Circular dependency detected for module {}", id))); + } + + // mark as seen + seen_modules.insert(id.clone()); + let compiled = map.get(&id).unwrap().1; for dep in compiled.immediate_dependencies() { // Only consider deps which are actually in this package. Deps for outside // packages are considered fine because of package deployment order. Note // that because of this detail, we can't use existing topsort from Move utils. if map.contains_key(&dep) { - sort_by_deps(map, order, dep); + sort_by_deps(map, order, dep, seen_modules)?; } } - order.push(id) + + // remove from seen + seen_modules.remove(&id); + + order.push(id); + + Ok(()) } impl fmt::Debug for ModuleBundle { diff --git a/crates/vm/src/move_vm.rs b/crates/vm/src/move_vm.rs index 8eec3383..1e405cf3 100644 --- a/crates/vm/src/move_vm.rs +++ b/crates/vm/src/move_vm.rs @@ -528,8 +528,9 @@ impl MoveVM { } // sort the modules by dependencies - let (sorted_module_bundle, published_module_ids, sorted_compiled_modules) = - module_bundle.sorted_code_and_modules(modules); + let (sorted_module_bundle, published_module_ids, sorted_compiled_modules) = module_bundle + .sorted_code_and_modules(modules) + .map_err(|e| e.finish(Location::Undefined))?; // publish and cache the modules on loader cache. session.publish_module_bundle_with_compat_config(