use crate::{
metadata::{
method::{
MethodAccessFlags, MethodImplCodeType, MethodImplOptions, MethodModifiers,
MethodVtableFlags,
},
validation::{
context::{OwnedValidationContext, ValidationContext},
traits::OwnedValidator,
},
},
Error, Result,
};
pub struct OwnedMethodValidator;
impl OwnedMethodValidator {
#[must_use]
pub fn new() -> Self {
Self
}
fn validate_method_signatures(&self, context: &OwnedValidationContext) -> Result<()> {
let methods = context.object().methods();
for entry in methods {
let method = entry.value();
if method.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Method with token 0x{:08X} has empty name",
entry.key().value()
),
});
}
for (index, (_, param)) in method.params.iter().enumerate() {
if let Some(base_type_ref) = param.base.get() {
if let Some(base_type) = base_type_ref.upgrade() {
if base_type.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Method '{}' parameter {} has unresolved type",
method.name, index
),
});
}
} else {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Method '{}' parameter {} has unresolved type",
method.name, index
),
});
}
} else {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Method '{}' parameter {} has unresolved type",
method.name, index
),
});
}
}
if let crate::metadata::signatures::TypeSignature::Unknown =
&method.signature.return_type.base
{
let method_name = &method.name;
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Method '{method_name}' has unresolved return type"),
});
}
for (index, (_, local)) in method.local_vars.iter().enumerate() {
if let Some(local_type) = local.base.upgrade() {
if local_type.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Method '{}' local variable {} has unresolved type",
method.name, index
),
});
}
} else {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Method '{}' local variable {} has unresolved type",
method.name, index
),
});
}
}
}
Ok(())
}
fn validate_virtual_inheritance(&self, context: &OwnedValidationContext) -> Result<()> {
let methods = context.object().methods();
for entry in methods {
let method = entry.value();
if method.is_abstract() && !method.is_virtual() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Abstract method '{}' must also be virtual", method.name),
});
}
if method.is_static() {
if method.is_virtual() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Static method '{}' cannot be virtual", method.name),
});
}
if method.is_abstract() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Static method '{}' cannot be abstract", method.name),
});
}
if method.flags_modifiers.contains(MethodModifiers::FINAL) {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Static method '{}' cannot be final", method.name),
});
}
}
if method.flags_modifiers.contains(MethodModifiers::FINAL) && !method.is_virtual() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Final method '{}' must also be virtual", method.name),
});
}
if method.flags_vtable.contains(MethodVtableFlags::NEW_SLOT)
&& !method.is_virtual()
&& !method
.flags_modifiers
.contains(MethodModifiers::RTSPECIAL_NAME)
{
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Method '{}' uses NEW_SLOT but is not virtual or runtime special",
method.name
),
});
}
}
Ok(())
}
fn validate_constructors(&self, context: &OwnedValidationContext) -> Result<()> {
let methods = context.object().methods();
for entry in methods {
let method = entry.value();
if method.is_ctor() {
if !method
.flags_modifiers
.contains(MethodModifiers::RTSPECIAL_NAME)
{
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Instance constructor '{}' must have RTSPECIAL_NAME flag",
method.name
),
});
}
if !method
.flags_modifiers
.contains(MethodModifiers::SPECIAL_NAME)
{
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Instance constructor '{}' must have SPECIAL_NAME flag",
method.name
),
});
}
if method.is_static() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Instance constructor '{}' cannot be static", method.name),
});
}
if method.is_virtual() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Instance constructor '{}' cannot be virtual",
method.name
),
});
}
}
if method.is_cctor() {
if !method.is_static() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Static constructor '{}' must be static", method.name),
});
}
if !method
.flags_modifiers
.contains(MethodModifiers::RTSPECIAL_NAME)
{
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Static constructor '{}' must have RTSPECIAL_NAME flag",
method.name
),
});
}
if !method
.flags_modifiers
.contains(MethodModifiers::SPECIAL_NAME)
{
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Static constructor '{}' must have SPECIAL_NAME flag",
method.name
),
});
}
if method.flags_access != MethodAccessFlags::PRIVATE {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!("Static constructor '{}' should be private", method.name),
});
}
}
if method.name.starts_with("op_")
&& method.is_static()
&& (method.is_public()
|| method.flags_access == MethodAccessFlags::FAMILY_OR_ASSEMBLY)
&& !method
.flags_modifiers
.contains(MethodModifiers::SPECIAL_NAME)
&& !method
.impl_code_type
.intersects(MethodImplCodeType::RUNTIME)
&& !method
.impl_options
.contains(MethodImplOptions::INTERNAL_CALL)
{
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Operator overload '{}' should have SPECIAL_NAME flag",
method.name
),
});
}
}
Ok(())
}
fn validate_method_bodies(&self, context: &OwnedValidationContext) -> Result<()> {
let methods = context.object().methods();
for type_rc in context.target_assembly_types() {
for (_, method_ref) in type_rc.methods.iter() {
if let Some(method_token) = method_ref.token() {
if let Some(method_entry) = methods.get(&method_token) {
let method = method_entry.value();
if method.is_abstract() && method.rva.is_some() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Abstract method '{}' should not have implementation (RVA)",
method.name
),
});
}
if method
.flags_modifiers
.contains(MethodModifiers::PINVOKE_IMPL)
&& method.rva.is_some()
{
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"P/Invoke method '{}' should not have implementation (RVA)",
method.name
),
});
}
if method
.impl_code_type
.intersects(MethodImplCodeType::RUNTIME)
&& method.rva.is_some()
{
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Runtime method '{}' should not have implementation (RVA)",
method.name
),
});
}
if !method.is_abstract()
&& !method
.flags_modifiers
.contains(MethodModifiers::PINVOKE_IMPL)
&& !method
.impl_code_type
.intersects(MethodImplCodeType::RUNTIME)
&& !method
.impl_options
.contains(MethodImplOptions::INTERNAL_CALL)
&& method.rva.is_none()
{
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Concrete method '{}' must have implementation (RVA)",
method.name
),
});
}
}
}
}
}
Ok(())
}
}
impl OwnedValidator for OwnedMethodValidator {
fn validate_owned(&self, context: &OwnedValidationContext) -> Result<()> {
self.validate_method_signatures(context)?;
self.validate_virtual_inheritance(context)?;
self.validate_constructors(context)?;
self.validate_method_bodies(context)?;
Ok(())
}
fn name(&self) -> &'static str {
"OwnedMethodValidator"
}
fn priority(&self) -> u32 {
160
}
fn should_run(&self, context: &OwnedValidationContext) -> bool {
context.config().enable_method_validation
}
}
impl Default for OwnedMethodValidator {
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::members_method::owned_method_validator_file_factory,
owned_validator_test,
},
};
#[test]
#[cfg(not(feature = "skip-expensive-tests"))]
fn test_owned_method_validator() -> Result<()> {
let validator = OwnedMethodValidator::new();
let config = ValidationConfig {
enable_method_validation: true,
..Default::default()
};
owned_validator_test(
owned_method_validator_file_factory,
"OwnedMethodValidator",
"ValidationOwnedFailed",
config,
|context| validator.validate_owned(context),
)
}
}