Skip to content

Commit

Permalink
[bytecode] Implement phantom type parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
Nico Lehmann committed Jul 6, 2021
1 parent bcb136a commit 1660f39
Show file tree
Hide file tree
Showing 102 changed files with 564 additions and 188 deletions.
Expand Up @@ -46,7 +46,10 @@ fn make_module() -> CompiledModuleMut {
module: ModuleHandleIndex(0),
name: IdentifierIndex(2),
abilities: AbilitySet::PRIMITIVES,
type_parameters: vec![AbilitySet::PRIMITIVES],
type_parameters: vec![StructTypeParameter {
constraints: AbilitySet::PRIMITIVES,
is_phantom: false,
}],
},
StructHandle {
module: ModuleHandleIndex(0),
Expand All @@ -58,7 +61,10 @@ fn make_module() -> CompiledModuleMut {
module: ModuleHandleIndex(0),
name: IdentifierIndex(4),
abilities: AbilitySet::EMPTY | Ability::Key,
type_parameters: vec![AbilitySet::PRIMITIVES],
type_parameters: vec![StructTypeParameter {
constraints: AbilitySet::PRIMITIVES,
is_phantom: false,
}],
},
],
struct_defs: vec![
Expand Down
Expand Up @@ -37,7 +37,7 @@ fn verify_module_impl(module: &CompiledModule) -> PartialVMResult<()> {
.map(|_| AbilitySet::ALL)
.collect::<Vec<_>>();
for field in fields {
let field_abilities = view.abilities(&field.signature.0, &type_parameter_abilities);
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,
Expand Down
63 changes: 53 additions & 10 deletions language/bytecode-verifier/src/dependencies.rs
Expand Up @@ -8,8 +8,8 @@ use move_binary_format::{
errors::{verification_error, Location, PartialVMError, PartialVMResult, VMResult},
file_format::{
AbilitySet, Bytecode, CodeOffset, CompiledModule, CompiledScript, FunctionDefinitionIndex,
FunctionHandleIndex, ModuleHandleIndex, SignatureToken, StructHandleIndex, TableIndex,
Visibility,
FunctionHandleIndex, ModuleHandleIndex, SignatureToken, StructHandleIndex,
StructTypeParameter, TableIndex, Visibility,
},
IndexKind,
};
Expand Down Expand Up @@ -210,7 +210,7 @@ fn verify_imported_structs(context: &Context) -> PartialVMResult<()> {
Some(def_idx) => {
let def_handle = owner_module.struct_handle_at(*def_idx);
if !compatible_struct_abilities(struct_handle.abilities, def_handle.abilities)
|| !compatible_type_parameters(
|| !compatible_struct_type_parameters(
&struct_handle.type_parameters,
&def_handle.type_parameters,
)
Expand Down Expand Up @@ -253,7 +253,7 @@ fn verify_imported_functions(context: &Context) -> PartialVMResult<()> {
Some(def_idx) => {
let def_handle = owner_module.function_handle_at(*def_idx);
// compatible type parameter constraints
if !compatible_type_parameters(
if !compatible_fun_type_parameters(
&function_handle.type_parameters,
&def_handle.type_parameters,
) {
Expand Down Expand Up @@ -330,10 +330,8 @@ fn compatible_struct_abilities(
}

// - The number of type parameters must be the same
// - For each type parameter, the local view must be a superset of (or equal to) the defined
// constraints. Conceptually, the local view can be more constrained than the defined one as the
// local context is only limiting usage, and cannot take advantage of the additional constraints.
fn compatible_type_parameters(
// - Each pair of parameters must satisfy [`compatible_type_parameter_constraints`]
fn compatible_fun_type_parameters(
local_type_parameters_declaration: &[AbilitySet],
defined_type_parameters: &[AbilitySet],
) -> bool {
Expand All @@ -346,12 +344,57 @@ fn compatible_type_parameters(
local_type_parameter_constraints_declaration,
defined_type_parameter_constraints,
)| {
(*defined_type_parameter_constraints)
.is_subset(*local_type_parameter_constraints_declaration)
compatible_type_parameter_constraints(
*local_type_parameter_constraints_declaration,
*defined_type_parameter_constraints,
)
},
)
}

// - The number of type parameters must be the same
// - Each pair of parameters must satisfy [`compatible_type_parameter_constraints`] and [`compatible_type_parameter_phantom_decl`]
fn compatible_struct_type_parameters(
local_type_parameters_declaration: &[StructTypeParameter],
defined_type_parameters: &[StructTypeParameter],
) -> bool {
local_type_parameters_declaration.len() == defined_type_parameters.len()
&& local_type_parameters_declaration
.iter()
.zip(defined_type_parameters)
.all(
|(local_type_parameter_declaration, defined_type_parameter)| {
compatible_type_parameter_phantom_decl(
local_type_parameter_declaration,
defined_type_parameter,
) && compatible_type_parameter_constraints(
local_type_parameter_declaration.constraints,
defined_type_parameter.constraints,
)
},
)
}

// The local view of a type parameter must be a superset of (or equal to) the defined
// constraints. Conceptually, the local view can be more constrained than the defined one as the
// local context is only limiting usage, and cannot take advantage of the additional constraints.
fn compatible_type_parameter_constraints(
local_type_parameter_constraints_declaration: AbilitySet,
defined_type_parameter_constraints: AbilitySet,
) -> bool {
defined_type_parameter_constraints.is_subset(local_type_parameter_constraints_declaration)
}

// Adding phantom declarations relaxes the requirements for clients, thus, the local view may
// lack a phantom declaration present in the definition.
fn compatible_type_parameter_phantom_decl(
local_type_parameter_declaration: &StructTypeParameter,
defined_type_parameter: &StructTypeParameter,
) -> bool {
// local_type_parameter_declaration.is_phantom => defined_type_parameter.is_phantom
!local_type_parameter_declaration.is_phantom || defined_type_parameter.is_phantom
}

fn compare_cross_module_signatures(
context: &Context,
handle_sig: &[SignatureToken],
Expand Down
13 changes: 8 additions & 5 deletions language/bytecode-verifier/src/locals_safety/abstract_state.rs
Expand Up @@ -7,7 +7,7 @@ use crate::absint::{AbstractDomain, JoinResult};
use mirai_annotations::{checked_precondition, checked_verify};
use move_binary_format::{
binary_views::{BinaryIndexedView, FunctionView},
errors::PartialVMError,
errors::{PartialVMError, PartialVMResult},
file_format::{AbilitySet, CodeOffset, FunctionDefinitionIndex, LocalIndex},
};
use move_core_types::vm_status::StatusCode;
Expand All @@ -34,7 +34,10 @@ pub(crate) struct AbstractState {

impl AbstractState {
/// create a new abstract state
pub fn new(resolver: &BinaryIndexedView, function_view: &FunctionView) -> Self {
pub fn new(
resolver: &BinaryIndexedView,
function_view: &FunctionView,
) -> PartialVMResult<Self> {
let num_args = function_view.parameters().len();
let num_locals = num_args + function_view.locals().len();
let local_states = (0..num_locals)
Expand All @@ -47,13 +50,13 @@ impl AbstractState {
.iter()
.chain(function_view.locals().0.iter())
.map(|st| resolver.abilities(st, &function_view.type_parameters()))
.collect();
.collect::<PartialVMResult<Vec<_>>>()?;

Self {
Ok(Self {
current_function: function_view.index(),
local_states,
all_local_abilities,
}
})
}

pub fn local_abilities(&self, idx: LocalIndex) -> AbilitySet {
Expand Down
2 changes: 1 addition & 1 deletion language/bytecode-verifier/src/locals_safety/mod.rs
Expand Up @@ -22,7 +22,7 @@ pub(crate) fn verify<'a>(
resolver: &BinaryIndexedView,
function_view: &'a FunctionView<'a>,
) -> PartialVMResult<()> {
let initial_state = AbstractState::new(resolver, function_view);
let initial_state = AbstractState::new(resolver, function_view)?;
let inv_map = LocalsSafetyAnalysis().analyze_function(initial_state, &function_view);
// Report all the join failures
for (_block_id, BlockInvariant { post, .. }) in inv_map {
Expand Down
73 changes: 62 additions & 11 deletions language/bytecode-verifier/src/signature.rs
Expand Up @@ -11,7 +11,7 @@ use move_binary_format::{
file_format::{
AbilitySet, Bytecode, CodeUnit, CompiledModule, CompiledScript, FunctionDefinition,
FunctionHandle, Signature, SignatureIndex, SignatureToken, StructDefinition,
StructFieldInformation, TableIndex,
StructFieldInformation, StructTypeParameter, TableIndex,
},
IndexKind,
};
Expand Down Expand Up @@ -93,8 +93,14 @@ impl<'a> SignatureChecker<'a> {
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))?;
self.check_type_instantiation(
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))?;
Expand Down Expand Up @@ -135,7 +141,7 @@ impl<'a> SignatureChecker<'a> {
self.check_signature_tokens(type_arguments)?;
self.check_generic_instance(
type_arguments,
&func_handle.type_parameters,
func_handle.type_parameters.iter().copied(),
type_parameters,
)
}
Expand All @@ -153,7 +159,7 @@ impl<'a> SignatureChecker<'a> {
self.check_signature_tokens(type_arguments)?;
self.check_generic_instance(
type_arguments,
&struct_handle.type_parameters,
struct_handle.type_param_constraints(),
type_parameters,
)
}
Expand All @@ -166,7 +172,7 @@ impl<'a> SignatureChecker<'a> {
self.check_signature_tokens(type_arguments)?;
self.check_generic_instance(
type_arguments,
&struct_handle.type_parameters,
struct_handle.type_param_constraints(),
type_parameters,
)
}
Expand All @@ -188,6 +194,49 @@ impl<'a> SignatureChecker<'a> {
Ok(())
}

/// Checks that phantom type parameters are only used in phantom position.
fn check_phantom_params(
&self,
ty: &SignatureToken,
is_phantom_pos: bool,
type_parameters: &[StructTypeParameter],
) -> PartialVMResult<()> {
match ty {
SignatureToken::Vector(ty) => self.check_phantom_params(ty, false, type_parameters)?,
SignatureToken::StructInstantiation(idx, type_arguments) => {
let sh = self.resolver.struct_handle_at(*idx);
for (i, ty) in type_arguments.iter().enumerate() {
self.check_phantom_params(
ty,
sh.type_parameters[i].is_phantom,
type_parameters,
)?;
}
}
SignatureToken::TypeParameter(idx) => {
if type_parameters[*idx as usize].is_phantom && !is_phantom_pos {
return Err(PartialVMError::new(
StatusCode::INVALID_PHANTOM_TYPE_PARAM_POSITION,
)
.with_message(
"phantom type parameter cannot be used in non-phantom position".to_string(),
));
}
}

SignatureToken::Struct(_)
| SignatureToken::Reference(_)
| SignatureToken::MutableReference(_)
| SignatureToken::Bool
| SignatureToken::U8
| SignatureToken::U64
| SignatureToken::U128
| SignatureToken::Address
| SignatureToken::Signer => {}
}
Ok(())
}

/// Checks if the given type is well defined in the given context.
/// References are only permitted at the top level.
fn check_signature(&self, idx: SignatureIndex) -> PartialVMResult<()> {
Expand Down Expand Up @@ -251,7 +300,11 @@ impl<'a> SignatureChecker<'a> {
// i.e. it cannot be checked unless we are inside some module member. The only case
// where that happens is when checking the signature pool itself
let sh = self.resolver.struct_handle_at(*idx);
self.check_generic_instance(type_arguments, &sh.type_parameters, type_parameters)
self.check_generic_instance(
type_arguments,
sh.type_param_constraints(),
type_parameters,
)
}
_ => Ok(()),
}
Expand All @@ -261,13 +314,11 @@ impl<'a> SignatureChecker<'a> {
fn check_generic_instance(
&self,
type_arguments: &[SignatureToken],
constraints: &[AbilitySet],
constraints: impl IntoIterator<Item = AbilitySet>,
global_abilities: &[AbilitySet],
) -> PartialVMResult<()> {
let abilities = type_arguments
.iter()
.map(|ty| self.resolver.abilities(ty, global_abilities));
for ((constraint, given), ty) in constraints.iter().zip(abilities).zip(type_arguments) {
for (constraint, ty) in constraints.into_iter().zip(type_arguments) {
let given = self.resolver.abilities(ty, global_abilities)?;
if !constraint.is_subset(given) {
return Err(PartialVMError::new(StatusCode::CONSTRAINT_NOT_SATISFIED)
.with_message(format!(
Expand Down

0 comments on commit 1660f39

Please sign in to comment.