Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Call edge detection for Supra container #128

Draft
wants to merge 3 commits into
base: vm_upgrade_branch
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions third_party/move/move-bytecode-verifier/src/call_edge_detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) The Diem Core Contributors
// Copyright (c) The Move Contributors
// SPDX-License-Identifier: Apache-2.0

//! This module implements a checker for verifying that each vector in a CompiledModule contains
//! distinct values. Successful verification implies that an index in vector can be used to
//! uniquely name the entry at that index. Additionally, the checker also verifies the
//! following:
//! - struct and field definitions are consistent
//! - the handles in struct and function definitions point to the self module index
//! - all struct and function handles pointing to the self module index have a definition
use move_binary_format::{
access::{ModuleAccess},
errors::{Location, PartialVMResult, VMResult},
file_format::{
CompiledModule
},
};
use move_binary_format::file_format::Bytecode;

pub struct CallEdgeDetector<'a> {
module: &'a CompiledModule,
}

impl<'a> CallEdgeDetector<'a> {
pub fn verify_module(module: &'a CompiledModule) -> VMResult<()> {
Self::verify_module_impl(module).map_err(|e| e.finish(Location::Module(module.self_id())))
}

fn verify_module_impl(module: &'a CompiledModule) -> PartialVMResult<()> {
Self::print_module_addresses(module);
Self::call_edges_print(module);
Ok(())
}

pub fn print_module_addresses(module: &CompiledModule) {
println!("Module address: {:?}", module.self_id().address());

// Print the addresses of all the module's dependencies
for dep in module.immediate_dependencies() {
println!("Dependency address: {:?}", dep.address());
}

// Print the addresses of all the module's friends
for friend in module.immediate_friends() {
println!("Friend address: {:?}", friend.address());
}
}

// Print the function calls and module address from and to in the module
pub fn call_edges_print(module: &CompiledModule) {
// Iterate over all the functions in the module
for function_def in module.function_defs().iter() {
let function_handle = &module.function_handle_at(function_def.function);
let function_name = module.identifier_at(function_handle.name);
println!("Function: {}", function_name);
// Iterate over all the bytecodes that represent function calls in the function
if let Some(code) = &function_def.code {
for bytecode in &code.code {
// Case 1: Call instruction; Case 2: CallGeneric instruction; Case 3: Ret instruction
match bytecode {
Bytecode::Call(handle_index) => {
let called_function_handle = module.function_handle_at(*handle_index);
let called_function_name = module.identifier_at(called_function_handle.name);
let module_id = module.self_id();
let source_module = module_id.address();
let target_module = module.address_identifiers()[called_function_handle.module.0 as usize];
println!(
" Calls: {} from module: {:x} to module: {:x}",
called_function_name, source_module, target_module
);
}
Bytecode::CallGeneric(inst_index) => {
let inst = module.function_instantiation_at(*inst_index);
let called_function_handle = module.function_handle_at(inst.handle);
let called_function_name = module.identifier_at(called_function_handle.name);
let module_id = module.self_id();
let source_module = module_id.address();
let target_module = module.address_identifiers()[called_function_handle.module.0 as usize];
println!(
" Calls: {} from module: {:x} to module: {:x}",
called_function_name, source_module, target_module
);
}
Bytecode::Ret => {
let module_id = module.self_id();
let source_module = module_id.address();
println!(
" Returns to module: {:x}",
source_module
);
}
_ => {}
}
}
}
}
}
//TODO return edge, where is the after the function call finish
//TODO how to add gas metering for distinguishing cross container and in container function call?
//TODO how the gas should be calculated for cross container function call?
}
2 changes: 2 additions & 0 deletions third_party/move/move-bytecode-verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ mod reference_safety;
mod regression_tests;
mod stack_usage_verifier;
mod type_safety;

mod call_edge_detection;
2 changes: 2 additions & 0 deletions third_party/move/move-bytecode-verifier/src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use move_binary_format::{
use move_core_types::{state::VMState, vm_status::StatusCode};
use serde::Serialize;
use std::time::Instant;
use crate::call_edge_detection::CallEdgeDetector;

#[derive(Debug, Clone, Serialize)]
pub struct VerifierConfig {
Expand Down Expand Up @@ -104,6 +105,7 @@ pub fn verify_module_with_config_for_test_with_version(
pub fn verify_module_with_config(config: &VerifierConfig, module: &CompiledModule) -> VMResult<()> {
let prev_state = move_core_types::state::set_state(VMState::VERIFIER);
let result = std::panic::catch_unwind(|| {
CallEdgeDetector::verify_module(module)?;
// Always needs to run bound checker first as subsequent passes depend on it
BoundsChecker::verify_module(module).map_err(|e| {
// We can't point the error at the module, because if bounds-checking
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,60 @@ fn script_large_ty() {

assert_eq!(res.major_status(), StatusCode::TOO_MANY_TYPE_NODES);
}

#[test]
fn test_module_call_edge() {
let test_str = r#"
module 0x42::ModuleA {
use 0x58::ModuleB;
public fun function_a() {
ModuleB::function_b();
ModuleB::function_b();
ModuleB::function_b();
ModuleB::function_b();
ModuleB::function_b();
ModuleB::function_b();
ModuleB::function_b();
ModuleB::function_b();
}
}

module 0x58::ModuleB {
public fun function_b() {
// Function body
}
}
"#;

let mut units = compile_units_with_stdlib(test_str).unwrap();

let decompiled_module_a = as_module(units.pop().unwrap());
let decompiled_module_b = as_module(units.pop().unwrap());

let verifier_config = VerifierConfig {
max_loop_depth: Some(5),
max_generic_instantiation_length: Some(32),
max_function_parameters: Some(128),
max_basic_blocks: Some(1024),
max_value_stack_size: 1024,
max_type_nodes: Some(256),
max_push_size: Some(10000),
max_struct_definitions: Some(200),
max_fields_in_struct: Some(30),
max_function_definitions: Some(1000),
..Default::default()
};

move_bytecode_verifier::verify_module_with_config(&verifier_config, &decompiled_module_a)
.unwrap();
move_bytecode_verifier::verify_module_with_config(&verifier_config, &decompiled_module_b)
.unwrap();

let mut module_a = vec![];
decompiled_module_a.serialize(&mut module_a).unwrap();
CompiledModule::deserialize(&module_a).unwrap();

let mut module_b = vec![];
decompiled_module_b.serialize(&mut module_b).unwrap();
CompiledModule::deserialize(&module_b).unwrap();
}