use crate::{
dispatch_table_type,
metadata::{
cilassemblyview::CilAssemblyView,
cilobject::CilObject,
tables::{
ClassLayoutRaw, ConstantRaw, CustomAttributeRaw, FieldLayoutRaw, FieldMarshalRaw,
GenericParamConstraintRaw, GenericParamRaw, InterfaceImplRaw, MemberRefRaw,
MethodImplRaw, NestedClassRaw, TableId, TypeDefRaw, TypeRefRaw,
},
token::Token,
},
Blob, Error, Guid, Result, Strings, UserStrings,
};
use rustc_hash::{FxHashMap, FxHashSet};
pub struct ReferenceScanner {
forward_references: FxHashMap<Token, FxHashSet<Token>>,
backward_references: FxHashMap<Token, FxHashSet<Token>>,
valid_tokens: FxHashSet<Token>,
table_row_counts: FxHashMap<TableId, u32>,
heap_sizes: HeapSizes,
nested_class_map: FxHashMap<Token, FxHashSet<Token>>,
}
#[derive(Debug, Clone, Default)]
pub struct HeapSizes {
pub strings: u32,
pub blobs: u32,
pub guids: u32,
pub userstrings: u32,
}
impl ReferenceScanner {
pub fn from_view(view: &CilAssemblyView) -> Result<Self> {
let mut scanner = Self {
forward_references: FxHashMap::default(),
backward_references: FxHashMap::default(),
valid_tokens: FxHashSet::default(),
table_row_counts: FxHashMap::default(),
heap_sizes: HeapSizes::default(),
nested_class_map: FxHashMap::default(),
};
scanner.analyze_assembly(view)?;
Ok(scanner)
}
pub fn from_object(object: &CilObject) -> Result<Self> {
let mut scanner = Self {
forward_references: FxHashMap::default(),
backward_references: FxHashMap::default(),
valid_tokens: FxHashSet::default(),
table_row_counts: FxHashMap::default(),
heap_sizes: HeapSizes::default(),
nested_class_map: FxHashMap::default(),
};
scanner.analyze_object(object)?;
Ok(scanner)
}
fn analyze_object(&mut self, object: &CilObject) -> Result<()> {
self.analyze_heaps(
object.strings(),
object.blob(),
object.guids(),
object.userstrings(),
)?;
if let Some(tables) = object.tables() {
self.analyze_tables(tables);
}
Ok(())
}
fn analyze_assembly(&mut self, view: &CilAssemblyView) -> Result<()> {
self.analyze_heaps(
view.strings(),
view.blobs(),
view.guids(),
view.userstrings(),
)?;
if let Some(tables) = view.tables() {
self.analyze_tables(tables);
}
Ok(())
}
fn analyze_heaps(
&mut self,
strings: Option<&Strings>,
blobs: Option<&Blob>,
guids: Option<&Guid>,
userstrings: Option<&UserStrings>,
) -> Result<()> {
if let Some(strings) = strings {
self.heap_sizes.strings = u32::try_from(strings.data().len())
.map_err(|_| malformed_error!("String heap size exceeds u32 range"))?;
}
if let Some(blobs) = blobs {
self.heap_sizes.blobs = u32::try_from(blobs.data().len())
.map_err(|_| malformed_error!("Blob heap size exceeds u32 range"))?;
}
if let Some(guids) = guids {
self.heap_sizes.guids = u32::try_from(guids.data().len())
.map_err(|_| malformed_error!("GUID heap size exceeds u32 range"))?;
}
if let Some(userstrings) = userstrings {
self.heap_sizes.userstrings = u32::try_from(userstrings.data().len())
.map_err(|_| malformed_error!("UserString heap size exceeds u32 range"))?;
}
Ok(())
}
fn analyze_tables(&mut self, tables: &crate::TablesHeader) {
self.collect_valid_tokens(tables);
self.analyze_references(tables);
}
fn collect_valid_tokens(&mut self, tables: &crate::TablesHeader) {
for table_id in tables.present_tables() {
let row_count = tables.table_row_count(table_id);
if row_count == 0 {
continue;
}
self.table_row_counts.insert(table_id, row_count);
let table_token_base = u32::from(table_id.token_type()) << 24;
dispatch_table_type!(table_id, |RawType| {
if let Some(table) = tables.table::<RawType>() {
for row in table {
let token = Token::new(table_token_base | row.rid);
self.valid_tokens.insert(token);
}
}
});
}
}
fn analyze_references(&mut self, tables: &crate::TablesHeader) {
for table_id in tables.present_tables() {
dispatch_table_type!(table_id, |RawType| {
if let Some(table) = tables.table::<RawType>() {
let token_base = u32::from(table_id.token_type()) << 24;
for row in table {
let from_token = Token::new(token_base | row.rid);
self.extract_row_references(table_id, from_token, &row);
}
}
});
}
}
#[allow(clippy::too_many_lines)]
fn extract_row_references<T>(&mut self, table_id: TableId, from_token: Token, row: &T)
where
T: std::any::Any,
{
let row_any = row as &dyn std::any::Any;
match table_id {
TableId::TypeDef => {
if let Some(typedef) = row_any.downcast_ref::<TypeDefRaw>() {
if typedef.extends.row != 0 {
self.add_reference(from_token, typedef.extends.token);
}
}
}
TableId::TypeRef => {
if let Some(typeref) = row_any.downcast_ref::<TypeRefRaw>() {
if typeref.resolution_scope.row != 0 {
self.add_reference(from_token, typeref.resolution_scope.token);
}
}
}
TableId::InterfaceImpl => {
if let Some(impl_row) = row_any.downcast_ref::<InterfaceImplRaw>() {
let class_token = Token::new(0x0200_0000 | impl_row.class);
self.add_reference(from_token, class_token);
if impl_row.interface.row != 0 {
self.add_reference(from_token, impl_row.interface.token);
}
}
}
TableId::MemberRef => {
if let Some(memberref) = row_any.downcast_ref::<MemberRefRaw>() {
if memberref.class.row != 0 {
self.add_reference(from_token, memberref.class.token);
}
}
}
TableId::CustomAttribute => {
if let Some(attr) = row_any.downcast_ref::<CustomAttributeRaw>() {
if attr.parent.row != 0 {
self.add_reference(from_token, attr.parent.token);
}
if attr.constructor.row != 0 {
self.add_reference(from_token, attr.constructor.token);
}
}
}
TableId::GenericParam => {
if let Some(param) = row_any.downcast_ref::<GenericParamRaw>() {
if param.owner.row != 0 {
self.add_reference(from_token, param.owner.token);
}
}
}
TableId::GenericParamConstraint => {
if let Some(constraint) = row_any.downcast_ref::<GenericParamConstraintRaw>() {
let param_token = Token::new(0x2A00_0000 | constraint.owner);
self.add_reference(from_token, param_token);
if constraint.constraint.row != 0 {
self.add_reference(from_token, constraint.constraint.token);
}
}
}
TableId::NestedClass => {
if let Some(nested) = row_any.downcast_ref::<NestedClassRaw>() {
let nested_token = Token::new(0x0200_0000 | nested.nested_class);
self.add_reference(from_token, nested_token);
let enclosing_token = Token::new(0x0200_0000 | nested.enclosing_class);
self.add_reference(from_token, enclosing_token);
self.nested_class_map
.entry(enclosing_token)
.or_default()
.insert(nested_token);
}
}
TableId::MethodImpl => {
if let Some(impl_row) = row_any.downcast_ref::<MethodImplRaw>() {
let class_token = Token::new(0x0200_0000 | impl_row.class);
self.add_reference(from_token, class_token);
if impl_row.method_body.row != 0 {
self.add_reference(from_token, impl_row.method_body.token);
}
if impl_row.method_declaration.row != 0 {
self.add_reference(from_token, impl_row.method_declaration.token);
}
}
}
TableId::FieldLayout => {
if let Some(layout) = row_any.downcast_ref::<FieldLayoutRaw>() {
let field_token = Token::new(0x0400_0000 | layout.field);
self.add_reference(from_token, field_token);
}
}
TableId::ClassLayout => {
if let Some(layout) = row_any.downcast_ref::<ClassLayoutRaw>() {
let parent_token = Token::new(0x0200_0000 | layout.parent);
self.add_reference(from_token, parent_token);
}
}
TableId::Constant => {
if let Some(constant) = row_any.downcast_ref::<ConstantRaw>() {
if constant.parent.row != 0 {
self.add_reference(from_token, constant.parent.token);
}
}
}
TableId::FieldMarshal => {
if let Some(marshal) = row_any.downcast_ref::<FieldMarshalRaw>() {
if marshal.parent.row != 0 {
self.add_reference(from_token, marshal.parent.token);
}
}
}
TableId::MethodDef
| TableId::Field
| TableId::StandAloneSig
| TableId::TypeSpec
| TableId::Module
| TableId::Param
| TableId::Assembly
| TableId::AssemblyRef
| TableId::ModuleRef
| TableId::File
| TableId::ManifestResource
| TableId::ExportedType
| TableId::Event
| TableId::EventMap
| TableId::Property
| TableId::PropertyMap
| TableId::MethodSemantics
| TableId::DeclSecurity
| TableId::ImplMap
| TableId::FieldRVA
| TableId::MethodSpec
| TableId::AssemblyProcessor
| TableId::AssemblyOS
| TableId::AssemblyRefProcessor
| TableId::AssemblyRefOS
| TableId::FieldPtr
| TableId::MethodPtr
| TableId::ParamPtr
| TableId::EventPtr
| TableId::PropertyPtr
| TableId::EncLog
| TableId::EncMap
| TableId::Document
| TableId::MethodDebugInformation
| TableId::LocalScope
| TableId::LocalVariable
| TableId::LocalConstant
| TableId::ImportScope
| TableId::StateMachineMethod
| TableId::CustomDebugInformation => {
}
}
}
fn add_reference(&mut self, from_token: Token, to_token: Token) {
if from_token == to_token {
return;
}
if from_token.value() == 0 || to_token.value() == 0 {
return;
}
self.forward_references
.entry(to_token)
.or_default()
.insert(from_token);
self.backward_references
.entry(from_token)
.or_default()
.insert(to_token);
}
#[must_use]
pub fn token_exists(&self, token: Token) -> bool {
self.valid_tokens.contains(&token)
}
#[must_use]
pub fn table_row_count(&self, table_id: TableId) -> u32 {
self.table_row_counts.get(&table_id).copied().unwrap_or(0)
}
pub fn validate_token_bounds(&self, token: Token) -> Result<()> {
let table_value = token.table();
let rid = token.row();
let table_id = TableId::from_token_type(table_value).ok_or(Error::InvalidRid {
table: TableId::Module,
rid,
})?;
if rid == 0 {
return Err(Error::InvalidRid {
table: table_id,
rid,
});
}
let max_rid = self.table_row_count(table_id);
if rid > max_rid {
return Err(Error::InvalidRid {
table: table_id,
rid,
});
}
Ok(())
}
#[must_use]
pub fn references_to(&self, token: Token) -> Option<&FxHashSet<Token>> {
self.forward_references.get(&token)
}
#[must_use]
pub fn references_from(&self, token: Token) -> Option<&FxHashSet<Token>> {
self.backward_references.get(&token)
}
#[must_use]
pub fn has_references_to(&self, token: Token) -> bool {
self.forward_references
.get(&token)
.is_some_and(|set| !set.is_empty())
}
#[must_use]
pub fn has_references_from(&self, token: Token) -> bool {
self.backward_references
.get(&token)
.is_some_and(|set| !set.is_empty())
}
#[must_use]
pub fn can_delete_token(&self, token: Token) -> bool {
!self.has_references_to(token)
}
#[must_use]
pub fn heap_sizes(&self) -> &HeapSizes {
&self.heap_sizes
}
#[must_use]
pub fn nested_classes_of(&self, enclosing_token: Token) -> Option<&FxHashSet<Token>> {
self.nested_class_map.get(&enclosing_token)
}
#[must_use]
pub fn is_nested_within(&self, potential_enclosing: Token, potential_nested: Token) -> bool {
let mut visited = FxHashSet::default();
self.is_nested_within_recursive(potential_enclosing, potential_nested, &mut visited)
}
fn is_nested_within_recursive(
&self,
enclosing: Token,
target: Token,
visited: &mut FxHashSet<Token>,
) -> bool {
if !visited.insert(enclosing) {
return false;
}
if let Some(nested_classes) = self.nested_class_map.get(&enclosing) {
if nested_classes.contains(&target) {
return true;
}
for &nested in nested_classes {
if self.is_nested_within_recursive(nested, target, visited) {
return true;
}
}
}
false
}
pub fn validate_heap_index(&self, heap_type: &str, index: u32) -> Result<()> {
let max_size = match heap_type {
"strings" => self.heap_sizes.strings,
"blobs" => self.heap_sizes.blobs,
"guids" => self.heap_sizes.guids,
"userstrings" => self.heap_sizes.userstrings,
_ => {
return Err(Error::HeapBoundsError {
heap: heap_type.to_string(),
index,
})
}
};
if index >= max_size {
return Err(Error::HeapBoundsError {
heap: heap_type.to_string(),
index,
});
}
Ok(())
}
#[must_use]
pub fn statistics(&self) -> ScannerStatistics {
ScannerStatistics {
total_tokens: self.valid_tokens.len(),
total_tables: self.table_row_counts.len(),
total_references: self.forward_references.values().map(FxHashSet::len).sum(),
heap_sizes: self.heap_sizes.clone(),
}
}
#[must_use]
pub fn count_non_empty_tables(&self) -> usize {
self.table_row_counts.len()
}
#[must_use]
pub fn count_total_rows(&self) -> u32 {
self.table_row_counts.values().sum()
}
}
#[derive(Debug, Clone)]
pub struct ScannerStatistics {
pub total_tokens: usize,
pub total_tables: usize,
pub total_references: usize,
pub heap_sizes: HeapSizes,
}
impl std::fmt::Display for ScannerStatistics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Scanner Statistics: {} tokens, {} tables, {} references",
self.total_tokens, self.total_tables, self.total_references
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::cilassemblyview::CilAssemblyView;
use std::path::PathBuf;
#[test]
fn test_reference_scanner_creation() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let scanner = ReferenceScanner::from_view(&view);
assert!(scanner.is_ok(), "Scanner creation should succeed");
let scanner = scanner.unwrap();
let stats = scanner.statistics();
assert!(stats.total_tokens > 0, "Should have found some tokens");
assert!(stats.total_tables > 0, "Should have found some tables");
}
}
#[test]
fn test_token_bounds_validation() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let invalid_token = Token::new(0x02000000); assert!(scanner.validate_token_bounds(invalid_token).is_err());
if scanner.table_row_count(TableId::TypeDef) > 0 {
let valid_token = Token::new(0x02000001); assert!(scanner.validate_token_bounds(valid_token).is_ok());
}
let max_rid = scanner.table_row_count(TableId::TypeDef);
if max_rid > 0 {
let out_of_bounds_token = Token::new(0x02000000 | (max_rid + 1));
assert!(scanner.validate_token_bounds(out_of_bounds_token).is_err());
}
}
}
}
#[test]
fn test_heap_size_analysis() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let heap_sizes = scanner.heap_sizes();
if view.strings().is_some() {
assert!(
heap_sizes.strings > 0,
"String heap should have been analyzed"
);
}
}
}
}
#[test]
fn test_scanner_statistics() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let stats = scanner.statistics();
let stats_string = stats.to_string();
assert!(stats_string.contains("tokens"));
assert!(stats_string.contains("tables"));
assert!(stats_string.contains("references"));
}
}
}
#[test]
fn test_reference_analysis_basic_functionality() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let stats = scanner.statistics();
assert!(
stats.total_references > 0,
"Should find references in WindowsBase.dll"
);
assert!(
!scanner.forward_references.is_empty()
|| !scanner.backward_references.is_empty(),
"Reference maps should be populated"
);
}
}
}
#[test]
fn test_typedef_inheritance_references() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let mut _inheritance_found = false;
for typedef_token in scanner.valid_tokens.iter() {
if typedef_token.is_table(TableId::TypeDef) {
if let Some(references) = scanner.references_from(*typedef_token) {
if !references.is_empty() {
_inheritance_found = true;
for ref_token in references {
assert!(
scanner.token_exists(*ref_token),
"Referenced token should exist in metadata"
);
}
}
}
}
}
if scanner.table_row_count(TableId::TypeDef) > 0 {
}
}
}
}
#[test]
fn test_interface_implementation_references() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let interface_impl_count = scanner.table_row_count(TableId::InterfaceImpl);
if interface_impl_count > 0 {
let mut impl_references_found = false;
for token in scanner.valid_tokens.iter() {
if token.is_table(TableId::InterfaceImpl) {
if let Some(references) = scanner.references_from(*token) {
if !references.is_empty() {
impl_references_found = true;
assert!(!references.is_empty(),
"InterfaceImpl should reference at least the implementing class");
for ref_token in references {
assert!(
scanner.token_exists(*ref_token),
"Referenced token should exist in metadata"
);
}
}
}
}
}
assert!(impl_references_found,
"Should find interface implementation references when InterfaceImpl table exists");
}
}
}
}
#[test]
fn test_memberref_class_references() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let memberref_count = scanner.table_row_count(TableId::MemberRef);
if memberref_count > 0 {
let mut memberref_references_found = false;
for token in scanner.valid_tokens.iter() {
if token.is_table(TableId::MemberRef) {
if let Some(references) = scanner.references_from(*token) {
if !references.is_empty() {
memberref_references_found = true;
for ref_token in references {
assert!(
scanner.token_exists(*ref_token),
"Referenced token should exist in metadata"
);
}
}
}
}
}
assert!(
memberref_references_found,
"Should find member reference relationships when MemberRef table exists"
);
}
}
}
}
#[test]
fn test_customattribute_references() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let attr_count = scanner.table_row_count(TableId::CustomAttribute);
if attr_count > 0 {
let mut attr_references_found = false;
for token in scanner.valid_tokens.iter() {
if token.is_table(TableId::CustomAttribute) {
if let Some(references) = scanner.references_from(*token) {
if !references.is_empty() {
attr_references_found = true;
for ref_token in references {
assert!(
scanner.token_exists(*ref_token),
"Referenced token should exist in metadata"
);
}
}
}
}
}
assert!(
attr_references_found,
"Should find custom attribute references when CustomAttribute table exists"
);
}
}
}
}
#[test]
fn test_nested_class_references() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let nested_count = scanner.table_row_count(TableId::NestedClass);
if nested_count > 0 {
let mut nested_references_found = false;
for token in scanner.valid_tokens.iter() {
if token.is_table(TableId::NestedClass) {
if let Some(references) = scanner.references_from(*token) {
if !references.is_empty() {
nested_references_found = true;
assert!(
references.len() >= 2,
"NestedClass should reference both nested and enclosing types"
);
for ref_token in references {
assert!(
scanner.token_exists(*ref_token),
"Referenced token should exist in metadata"
);
assert!(
ref_token.is_table(TableId::TypeDef),
"NestedClass should only reference TypeDef tokens"
);
}
}
}
}
}
assert!(
nested_references_found,
"Should find nested class references when NestedClass table exists"
);
}
}
}
}
#[test]
fn test_generic_parameter_references() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let generic_param_count = scanner.table_row_count(TableId::GenericParam);
if generic_param_count > 0 {
let mut generic_references_found = false;
for token in scanner.valid_tokens.iter() {
if token.is_table(TableId::GenericParam) {
if let Some(references) = scanner.references_from(*token) {
if !references.is_empty() {
generic_references_found = true;
for ref_token in references {
assert!(
scanner.token_exists(*ref_token),
"Referenced token should exist in metadata"
);
assert!(
ref_token.is_table(TableId::TypeDef)
|| ref_token.is_table(TableId::MethodDef),
"GenericParam should reference TypeDef or MethodDef"
);
}
}
}
}
}
if generic_param_count > 0 {
assert!(generic_references_found,
"Should find generic parameter references when GenericParam table exists");
}
}
}
}
}
#[test]
fn test_reference_bidirectionality() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
for (to_token, from_tokens) in &scanner.forward_references {
for from_token in from_tokens {
let backward_refs = scanner.references_from(*from_token);
assert!(
backward_refs.is_some_and(|refs| refs.contains(to_token)),
"Forward reference should have corresponding backward reference"
);
}
}
for (from_token, to_tokens) in &scanner.backward_references {
for to_token in to_tokens {
let forward_refs = scanner.references_to(*to_token);
assert!(
forward_refs.is_some_and(|refs| refs.contains(from_token)),
"Backward reference should have corresponding forward reference"
);
}
}
}
}
}
#[test]
fn test_can_delete_token_functionality() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let stats = scanner.statistics();
if stats.total_references > 0 {
let mut found_non_deletable = false;
let mut found_deletable = false;
for token in scanner.valid_tokens.iter().take(100) {
let can_delete = scanner.can_delete_token(*token);
let has_incoming_refs = scanner.has_references_to(*token);
if has_incoming_refs {
assert!(
!can_delete,
"Token with incoming references should not be deletable"
);
found_non_deletable = true;
} else {
assert!(
can_delete,
"Token with no incoming references should be deletable"
);
found_deletable = true;
}
}
assert!(found_deletable, "Should find some deletable tokens");
assert!(found_non_deletable, "Should find some non-deletable tokens");
}
}
}
}
#[test]
fn test_reference_validation_prevents_invalid_references() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(mut scanner) = ReferenceScanner::from_view(&view) {
let initial_ref_count = scanner.statistics().total_references;
let test_token = Token::new(0x02000001);
scanner.add_reference(test_token, test_token);
scanner.add_reference(Token::new(0), test_token);
scanner.add_reference(test_token, Token::new(0));
let final_ref_count = scanner.statistics().total_references;
assert_eq!(
initial_ref_count, final_ref_count,
"Invalid references should be prevented"
);
}
}
}
#[test]
fn test_comprehensive_reference_coverage() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
if let Ok(scanner) = ReferenceScanner::from_view(&view) {
let stats = scanner.statistics();
println!("Reference analysis results:");
println!(" Total tokens: {}", stats.total_tokens);
println!(" Total tables: {}", stats.total_tables);
println!(" Total references: {}", stats.total_references);
assert!(
stats.total_tokens > 1000,
"WindowsBase.dll should have many tokens"
);
assert!(
stats.total_tables > 10,
"WindowsBase.dll should have many tables"
);
if stats.total_references == 0 {
println!("Warning: No references found - implementation may need debugging");
}
}
}
}
}