use crate::{
dispatch_table_type,
metadata::{
cilassemblyview::CilAssemblyView,
tables::{AssemblyRaw, FieldRaw, MethodDefRaw, ModuleRaw, TableId, TypeDefRaw},
validation::{
context::{RawValidationContext, ValidationContext},
traits::RawValidator,
},
},
Result,
};
use strum::IntoEnumIterator;
pub struct RawTableValidator;
impl RawTableValidator {
#[must_use]
pub fn new() -> Self {
Self
}
fn validate_required_tables(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
let module_table = tables.table::<ModuleRaw>().ok_or_else(|| {
malformed_error!("Module table is required but not present in assembly")
})?;
if module_table.row_count == 0 {
return Err(malformed_error!(
"Module table is present but contains no rows - at least one Module row is required"
));
}
if let Some(assembly_table) = tables.table::<AssemblyRaw>() {
if assembly_table.row_count > 1 {
return Err(malformed_error!(
"Assembly table contains {} rows but can contain at most 1 row",
assembly_table.row_count
));
}
}
Ok(())
}
fn validate_table_structures(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
for table_id in TableId::iter() {
dispatch_table_type!(table_id, |RawType| {
if let Some(table) = tables.table::<RawType>() {
let row_count = table.row_count;
if row_count > 0x00FF_FFFF {
return Err(malformed_error!(
"{:?} table contains {} rows, exceeding maximum of {} rows",
table_id,
row_count,
0x00FF_FFFF
));
}
}
});
}
Ok(())
}
fn validate_table_dependencies(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let (Some(typedef_table), Some(field_table)) =
(tables.table::<TypeDefRaw>(), tables.table::<FieldRaw>())
{
for typedef_row in typedef_table {
if typedef_row.field_list != 0 && typedef_row.field_list > field_table.row_count + 1
{
return Err(malformed_error!(
"TypeDef RID {} references field list starting at RID {} but Field table only has {} rows",
typedef_row.rid,
typedef_row.field_list,
field_table.row_count
));
}
}
}
if let (Some(typedef_table), Some(method_table)) =
(tables.table::<TypeDefRaw>(), tables.table::<MethodDefRaw>())
{
for typedef_row in typedef_table {
if typedef_row.method_list != 0
&& typedef_row.method_list > method_table.row_count + 1
{
return Err(malformed_error!(
"TypeDef RID {} references method list starting at RID {} but MethodDef table only has {} rows",
typedef_row.rid,
typedef_row.method_list,
method_table.row_count
));
}
}
}
Ok(())
}
}
impl RawValidator for RawTableValidator {
fn validate_raw(&self, context: &RawValidationContext) -> Result<()> {
let assembly_view = context.assembly_view();
Self::validate_required_tables(assembly_view)?;
Self::validate_table_structures(assembly_view)?;
Self::validate_table_dependencies(assembly_view)?;
Ok(())
}
fn name(&self) -> &'static str {
"RawTableValidator"
}
fn priority(&self) -> u32 {
190
}
fn should_run(&self, context: &RawValidationContext) -> bool {
context.config().enable_structural_validation
}
}
impl Default for RawTableValidator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
metadata::validation::ValidationConfig,
prelude::*,
test::{
factories::validation::raw_structure_table::*, get_testfile_wb, validator_test,
TestAssembly,
},
};
#[test]
fn test_raw_table_validator() -> Result<()> {
let validator = RawTableValidator::new();
let config = ValidationConfig {
enable_structural_validation: true,
..Default::default()
};
validator_test(
raw_table_validator_file_factory,
"RawTableValidator",
"Malformed",
config,
|context| validator.validate_raw(context),
)
}
#[test]
fn test_raw_table_validator_configuration() -> Result<()> {
let validator = RawTableValidator::new();
fn clean_only_factory() -> Result<Vec<TestAssembly>> {
let Some(clean_testfile) = get_testfile_wb() else {
return Err(Error::Other("WindowsBase.dll not available".to_string()));
};
Ok(vec![TestAssembly::new(&clean_testfile, true)])
}
let result_disabled = validator_test(
clean_only_factory,
"RawTableValidator",
"Malformed",
ValidationConfig {
enable_structural_validation: false,
..Default::default()
},
|context| {
if validator.should_run(context) {
validator.validate_raw(context)
} else {
Ok(())
}
},
);
assert!(
result_disabled.is_ok(),
"Configuration test failed: validator should not run when disabled"
);
let result_enabled = validator_test(
clean_only_factory,
"RawTableValidator",
"Malformed",
ValidationConfig {
enable_structural_validation: true,
..Default::default()
},
|context| validator.validate_raw(context),
);
assert!(
result_enabled.is_ok(),
"Configuration test failed: validator should run when enabled"
);
Ok(())
}
#[test]
fn test_raw_table_validator_metadata() {
let validator = RawTableValidator::new();
assert_eq!(validator.name(), "RawTableValidator");
assert_eq!(validator.priority(), 190);
let _config_enabled = ValidationConfig {
enable_structural_validation: true,
..Default::default()
};
let _config_disabled = ValidationConfig {
enable_structural_validation: false,
..Default::default()
};
}
}