use crate::{
metadata::{
cilassemblyview::CilAssemblyView,
tables::{
GenericParamAttributes, GenericParamConstraintRaw, GenericParamRaw, TableId,
TypeDefRaw, TypeRefRaw, TypeSpecRaw,
},
validation::{
context::{RawValidationContext, ValidationContext},
traits::RawValidator,
},
},
Result,
};
pub struct RawGenericConstraintValidator;
impl RawGenericConstraintValidator {
#[must_use]
pub fn new() -> Self {
Self
}
fn validate_generic_parameters(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let Some(generic_param_table) = tables.table::<GenericParamRaw>() {
for generic_param in generic_param_table {
if generic_param.flags > 0xFFFF {
return Err(malformed_error!(
"GenericParam RID {} has invalid flags value {} exceeding maximum",
generic_param.rid,
generic_param.flags
));
}
if generic_param.owner.row == 0 {
return Err(malformed_error!(
"GenericParam RID {} has null owner reference",
generic_param.rid
));
}
if generic_param.name == 0 {
return Err(malformed_error!(
"GenericParam RID {} has null name reference",
generic_param.rid
));
}
}
}
Ok(())
}
fn validate_parameter_constraints(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let Some(constraint_table) = tables.table::<GenericParamConstraintRaw>() {
let generic_param_table = tables.table::<GenericParamRaw>();
for constraint in constraint_table {
if constraint.owner == 0 {
return Err(malformed_error!(
"GenericParamConstraint RID {} has null owner reference",
constraint.rid
));
}
if constraint.constraint.row == 0 {
return Err(malformed_error!(
"GenericParamConstraint RID {} has null constraint reference",
constraint.rid
));
}
if let Some(param_table) = generic_param_table {
if constraint.owner > param_table.row_count {
return Err(malformed_error!(
"GenericParamConstraint RID {} references GenericParam RID {} but table only has {} rows",
constraint.rid,
constraint.owner,
param_table.row_count
));
}
}
}
}
Ok(())
}
fn validate_constraint_inheritance(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let (Some(generic_param_table), Some(constraint_table)) = (
tables.table::<GenericParamRaw>(),
tables.table::<GenericParamConstraintRaw>(),
) {
for constraint in constraint_table {
let param_found = generic_param_table
.iter()
.any(|param| param.rid == constraint.owner);
if !param_found {
return Err(malformed_error!(
"GenericParamConstraint RID {} references non-existent GenericParam RID {}",
constraint.rid,
constraint.owner
));
}
}
}
Ok(())
}
fn validate_constraint_types(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let Some(constraint_table) = tables.table::<GenericParamConstraintRaw>() {
for constraint in constraint_table {
let constraint_tables = constraint.constraint.ci_type.tables();
let constraint_table_type = if constraint_tables.len() == 1 {
constraint_tables[0]
} else {
continue;
};
let constraint_row = constraint.constraint.row;
match constraint_table_type {
TableId::TypeDef => {
if let Some(typedef_table) = tables.table::<TypeDefRaw>() {
if constraint_row > typedef_table.row_count {
return Err(malformed_error!(
"GenericParamConstraint RID {} references TypeDef RID {} but table only has {} rows",
constraint.rid,
constraint_row,
typedef_table.row_count
));
}
} else {
return Err(malformed_error!(
"GenericParamConstraint RID {} references TypeDef but TypeDef table is missing",
constraint.rid
));
}
}
TableId::TypeRef => {
if let Some(typeref_table) = tables.table::<TypeRefRaw>() {
if constraint_row > typeref_table.row_count {
return Err(malformed_error!(
"GenericParamConstraint RID {} references TypeRef RID {} but table only has {} rows",
constraint.rid,
constraint_row,
typeref_table.row_count
));
}
} else {
return Err(malformed_error!(
"GenericParamConstraint RID {} references TypeRef but TypeRef table is missing",
constraint.rid
));
}
}
TableId::TypeSpec => {
if let Some(typespec_table) = tables.table::<TypeSpecRaw>() {
if constraint_row > typespec_table.row_count {
return Err(malformed_error!(
"GenericParamConstraint RID {} references TypeSpec RID {} but table only has {} rows",
constraint.rid,
constraint_row,
typespec_table.row_count
));
}
} else {
return Err(malformed_error!(
"GenericParamConstraint RID {} references TypeSpec but TypeSpec table is missing",
constraint.rid
));
}
}
_ => {
return Err(malformed_error!(
"GenericParamConstraint RID {} has invalid constraint type targeting unsupported table {:?}",
constraint.rid,
constraint_table_type
));
}
}
}
}
Ok(())
}
fn validate_parameter_flags(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let Some(generic_param_table) = tables.table::<GenericParamRaw>() {
for generic_param in generic_param_table {
let flags = generic_param.flags;
if (flags & GenericParamAttributes::COVARIANT) != 0
&& (flags & GenericParamAttributes::CONTRAVARIANT) != 0
{
return Err(malformed_error!(
"GenericParam RID {} has both covariant and contravariant flags set",
generic_param.rid
));
}
if (flags & GenericParamAttributes::RESERVED_MASK) != 0 {
return Err(malformed_error!(
"GenericParam RID {} has reserved flag bits set: 0x{:04X}",
generic_param.rid,
flags & GenericParamAttributes::RESERVED_MASK
));
}
if (flags & GenericParamAttributes::REFERENCE_TYPE_CONSTRAINT) != 0
&& (flags & GenericParamAttributes::NOT_NULLABLE_VALUE_TYPE_CONSTRAINT) != 0
{
return Err(malformed_error!(
"GenericParam RID {} has conflicting reference type and value type constraints",
generic_param.rid
));
}
}
}
Ok(())
}
}
impl RawValidator for RawGenericConstraintValidator {
fn validate_raw(&self, context: &RawValidationContext) -> Result<()> {
let assembly_view = context.assembly_view();
Self::validate_generic_parameters(assembly_view)?;
Self::validate_parameter_constraints(assembly_view)?;
Self::validate_constraint_inheritance(assembly_view)?;
Self::validate_constraint_types(assembly_view)?;
Self::validate_parameter_flags(assembly_view)?;
Ok(())
}
fn name(&self) -> &'static str {
"RawGenericConstraintValidator"
}
fn priority(&self) -> u32 {
130
}
fn should_run(&self, context: &RawValidationContext) -> bool {
context.config().enable_constraint_validation
}
}
impl Default for RawGenericConstraintValidator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
metadata::validation::ValidationConfig,
test::{
factories::validation::raw_constraints_generic::raw_generic_constraint_validator_file_factory,
validator_test,
},
};
#[test]
fn test_raw_generic_constraint_validator() -> Result<()> {
let validator = RawGenericConstraintValidator::new();
let config = ValidationConfig {
enable_constraint_validation: true,
..Default::default()
};
validator_test(
raw_generic_constraint_validator_file_factory,
"RawGenericConstraintValidator",
"Malformed",
config,
|context| validator.validate_raw(context),
)
}
}