use std::{collections::HashSet, sync::Arc};
use crate::{
metadata::{
tables::TypeAttributes,
typesystem::CilType,
validation::{
context::{OwnedValidationContext, ValidationContext},
traits::OwnedValidator,
},
},
Error, Result,
};
pub struct OwnedOwnershipValidator;
impl OwnedOwnershipValidator {
#[must_use]
pub fn new() -> Self {
Self
}
fn validate_type_member_ownership(&self, context: &OwnedValidationContext) -> Result<()> {
let methods = context.object().methods();
for type_entry in context.target_assembly_types() {
if type_entry.get_external().is_some() {
continue;
}
for (_idx, method_ref) in type_entry.methods.iter() {
if let Some(method_token) = method_ref.token() {
if let Some(method) = methods.get(&method_token) {
let method_value = method.value();
if method_value.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' owns method with empty name (token 0x{:08X})",
type_entry.name,
method_token.value()
),
});
}
let method_access_flags = method_value.flags_access.bits();
self.validate_method_accessibility(
&type_entry.name,
type_entry.flags,
&method_value.name,
method_access_flags,
)?;
if method_value.name.starts_with('.') {
let method_modifier_flags = method_value.flags_modifiers.bits();
self.validate_special_method_ownership(
&type_entry.name,
&method_value.name,
method_modifier_flags,
)?;
}
} else {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' claims ownership of non-existent method token 0x{:08X}",
type_entry.name,
method_token.value()
),
});
}
} else {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' has method reference without valid token",
type_entry.name
),
});
}
}
for (_, field) in type_entry.fields.iter() {
if field.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Type '{}' owns field with empty name", type_entry.name),
});
}
self.validate_field_accessibility_ownership(
&type_entry.name,
type_entry.flags,
&field.name,
field.flags,
)?;
}
for (_, property) in type_entry.properties.iter() {
if property.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' owns property with empty name",
type_entry.name
),
});
}
}
for (_, event) in type_entry.events.iter() {
if event.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Type '{}' owns event with empty name", type_entry.name),
});
}
}
}
Ok(())
}
fn validate_method_accessibility(
&self,
type_name: &str,
type_flags: u32,
method_name: &str,
method_flags: u32,
) -> Result<()> {
let type_visibility = type_flags & TypeAttributes::VISIBILITY_MASK;
let method_visibility = method_flags & 0x0007;
if type_visibility != TypeAttributes::PUBLIC && method_visibility == 6
{
}
if method_visibility > 6 {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Method '{method_name}' in type '{type_name}' has invalid visibility value: 0x{method_visibility:02X}"
),
});
}
Ok(())
}
fn validate_special_method_ownership(
&self,
type_name: &str,
method_name: &str,
method_flags: u32,
) -> Result<()> {
match method_name {
".ctor" => {
if method_flags & 0x0010 != 0 {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Instance constructor '.ctor' in type '{type_name}' cannot be static"
),
});
}
}
".cctor" => {
if method_flags & 0x0010 == 0 {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Static constructor '.cctor' in type '{type_name}' must be static"
),
});
}
}
_ => {
}
}
Ok(())
}
fn validate_field_accessibility_ownership(
&self,
type_name: &str,
_type_flags: u32,
field_name: &str,
field_flags: u32,
) -> Result<()> {
let field_visibility = field_flags & 0x0007;
if field_visibility > 6 {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Field '{field_name}' in type '{type_name}' has invalid visibility value: 0x{field_visibility:02X}"
),
});
}
Ok(())
}
fn validate_nested_class_ownership_rules(
&self,
context: &OwnedValidationContext,
) -> Result<()> {
let target_types = context.target_assembly_types();
let target_type_pointers: HashSet<*const CilType> = target_types
.iter()
.map(|t| std::ptr::from_ref::<CilType>(t.as_ref()))
.collect();
for type_entry in target_types {
for (_, nested_ref) in type_entry.nested_types.iter() {
if let Some(nested_type) = nested_ref.upgrade() {
let nested_type_ptr = nested_type.as_ref() as *const CilType;
let is_target_assembly_type = target_type_pointers.contains(&nested_type_ptr);
if is_target_assembly_type {
self.validate_nested_type_accessibility_ownership(
&type_entry.name,
type_entry.flags,
&nested_type.name,
nested_type.flags,
)?;
}
}
}
}
Ok(())
}
fn validate_nested_type_circularity_deep(
&self,
current_type: &Arc<CilType>,
recursion_stack: &mut HashSet<*const CilType>,
depth: usize,
) -> Result<()> {
const MAX_RECURSION_DEPTH: usize = 100;
if depth > MAX_RECURSION_DEPTH {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Maximum recursion depth ({}) exceeded for nested type validation starting with type '{}' (token 0x{:08X})",
MAX_RECURSION_DEPTH,
current_type.name,
current_type.token.value()
),
});
}
let type_ptr = current_type.as_ref() as *const CilType;
if recursion_stack.contains(&type_ptr) {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Circular nested type dependency detected involving type '{}' with token 0x{:08X} at depth {}",
current_type.name,
current_type.token.value(),
depth
),
});
}
recursion_stack.insert(type_ptr);
for (_, nested_ref) in current_type.nested_types.iter() {
if let Some(nested_type) = nested_ref.upgrade() {
self.validate_nested_type_circularity_deep(
&nested_type,
recursion_stack,
depth + 1,
)?;
}
}
recursion_stack.remove(&type_ptr);
Ok(())
}
fn validate_nested_type_accessibility_ownership(
&self,
container_name: &str,
container_flags: u32,
nested_name: &str,
nested_flags: u32,
) -> Result<()> {
let _ = container_flags & TypeAttributes::VISIBILITY_MASK;
let nested_visibility = nested_flags & TypeAttributes::VISIBILITY_MASK;
if !matches!(
nested_visibility,
TypeAttributes::NESTED_PUBLIC
| TypeAttributes::NESTED_PRIVATE
| TypeAttributes::NESTED_FAMILY
| TypeAttributes::NESTED_ASSEMBLY
| TypeAttributes::NESTED_FAM_AND_ASSEM
| TypeAttributes::NESTED_FAM_OR_ASSEM
) {
if nested_visibility != 0 && nested_visibility <= 7 {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Nested type '{nested_name}' in container '{container_name}' uses top-level visibility instead of nested visibility: 0x{nested_visibility:02X}"
),
});
}
}
Ok(())
}
}
impl OwnedValidator for OwnedOwnershipValidator {
fn validate_owned(&self, context: &OwnedValidationContext) -> Result<()> {
self.validate_type_member_ownership(context)?;
self.validate_nested_class_ownership_rules(context)?;
Ok(())
}
fn name(&self) -> &'static str {
"OwnedOwnershipValidator"
}
fn priority(&self) -> u32 {
160
}
fn should_run(&self, context: &OwnedValidationContext) -> bool {
context.config().enable_cross_table_validation
}
}
impl Default for OwnedOwnershipValidator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[cfg_attr(feature = "skip-expensive-tests", allow(unused_imports))]
mod tests {
use super::*;
use crate::{
metadata::validation::ValidationConfig,
test::{
factories::validation::ownership::owned_ownership_validator_file_factory,
owned_validator_test,
},
};
#[test]
#[cfg(not(feature = "skip-expensive-tests"))]
fn test_owned_ownership_validator() -> Result<()> {
let validator = OwnedOwnershipValidator::new();
let config = ValidationConfig {
enable_cross_table_validation: true,
..Default::default()
};
owned_validator_test(
owned_ownership_validator_file_factory,
"OwnedOwnershipValidator",
"ValidationOwnedFailed",
config,
|context| validator.validate_owned(context),
)
}
}