dotscope 0.6.0

A high-performance, cross-platform framework for analyzing and reverse engineering .NET PE executables
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
//! Raw assembly view for editing and modification operations.
//!
//! This module provides the [`crate::metadata::cilassemblyview::CilAssemblyView`] struct, which offers a read-only
//! representation of .NET assemblies that maintains a 1:1 mapping with the underlying
//! file structure. Unlike [`crate::CilObject`] which provides a fully processed and
//! resolved view optimized for analysis, [`crate::metadata::cilassemblyview::CilAssemblyView`] preserves the raw metadata
//! structure to enable future editing and modification operations.
//!
//! # Architecture
//!
//! The module is built around a self-referencing pattern that enables efficient access to
//! file data while maintaining memory safety. The architecture provides:
//!
//! - **Raw Structure Access**: Direct access to metadata tables and streams without resolution
//! - **Immutable View**: Read-only operations to ensure data integrity during analysis
//! - **Editing Foundation**: Structured to support future writable operations
//! - **Memory Efficient**: Self-referencing pattern avoids data duplication
//! - **No Validation**: Pure parsing without format validation or compliance checks
//!
//! # Key Components
//!
//! ## Core Types
//! - [`crate::metadata::cilassemblyview::CilAssemblyView`] - Main assembly view struct with file-mapped data
//! - [`crate::metadata::cilassemblyview::CilAssemblyViewData`] - Internal data structure holding raw metadata
//!
//! ## Access Methods
//! - [`crate::metadata::cilassemblyview::CilAssemblyView::tables`] - Raw metadata tables without semantic resolution
//! - [`crate::metadata::cilassemblyview::CilAssemblyView::strings`] - Direct access to strings heap (#Strings)
//! - [`crate::metadata::cilassemblyview::CilAssemblyView::userstrings`] - Direct access to user strings heap (#US)
//! - [`crate::metadata::cilassemblyview::CilAssemblyView::guids`] - Direct access to GUID heap (#GUID)
//! - [`crate::metadata::cilassemblyview::CilAssemblyView::blobs`] - Direct access to blob heap (#Blob)
//!
//! ## Conversion Methods
//! - [`crate::metadata::cilassemblyview::CilAssemblyView::to_owned`] - Convert to mutable [`crate::CilAssembly`] for editing
//!
//! # Usage Examples
//!
//! ## Basic Raw Metadata Access
//!
//! ```rust,no_run
//! use dotscope::CilAssemblyView;
//! use std::path::Path;
//!
//! // Load assembly for potential editing operations
//! let view = CilAssemblyView::from_path(Path::new("assembly.dll"))?;
//!
//! // Access raw metadata structures
//! if let Some(tables) = view.tables() {
//!     println!("Schema version: {}.{}", tables.major_version, tables.minor_version);
//! }
//!
//! // Access string heaps directly
//! if let Some(strings) = view.strings() {
//!     if let Ok(name) = strings.get(0x123) {
//!         println!("Raw string: {}", name);
//!     }
//! }
//! # Ok::<(), dotscope::Error>(())
//! ```
//!
//! ## Converting to Mutable Assembly
//!
//! ```rust,no_run
//! use dotscope::{CilAssemblyView, CilAssembly};
//! use std::path::Path;
//!
//! // Load raw view
//! let view = CilAssemblyView::from_path(Path::new("assembly.dll"))?;
//!
//! // Convert to mutable assembly for editing
//! let mut assembly = view.to_owned();
//!
//! // Now you can perform editing operations
//! let string_index = assembly.string_add("New String")?;
//! # Ok::<(), dotscope::Error>(())
//! ```
//!
//! ## Analyzing Raw Structures
//!
//! ```rust,no_run
//! use dotscope::CilAssemblyView;
//! use std::path::Path;
//!
//! let view = CilAssemblyView::from_path(Path::new("assembly.dll"))?;
//!
//! // Direct access to CLR header
//! let cor20 = view.cor20header();
//! println!("Runtime version: {}.{}", cor20.major_runtime_version, cor20.minor_runtime_version);
//!
//! // Raw metadata root access
//! let root = view.metadata_root();
//! println!("Metadata signature: {:?}", root.signature);
//! # Ok::<(), dotscope::Error>(())
//! ```
//!
//! # Thread Safety
//!
//! [`crate::metadata::cilassemblyview::CilAssemblyView`] is [`std::marker::Send`] and [`std::marker::Sync`] as it provides read-only access
//! to immutable file data. Multiple threads can safely access the same view concurrently
//! without additional synchronization.
//!
//! # Integration
//!
//! This module integrates with:
//! - [`crate::CilAssembly`] - Provides conversion to mutable assembly for editing operations
//! - [`crate::metadata::streams`] - Uses stream types for direct heap access
//! - [`crate::metadata::cor20header`] - Provides CLR header information
//! - File I/O abstraction for memory-mapped or in-memory access

use log::{debug, warn};
use ouroboros::self_referencing;
use std::{path::Path, sync::Arc};

use crate::{
    cilassembly::CilAssembly,
    file::File,
    metadata::{
        cor20header::Cor20Header,
        diagnostics::{DiagnosticCategory, Diagnostics},
        identity::{AssemblyIdentity, AssemblyVersion, Identity, ProcessorArchitecture},
        root::Root,
        streams::{Blob, Guid, StreamHeader, Strings, TablesHeader, UserStrings},
        tables::{AssemblyProcessorRaw, AssemblyRaw, AssemblyRefRaw, ModuleRaw},
        validation::ValidationEngine,
    },
    Error, Result, ValidationConfig,
};

/// Raw assembly view data holding references to file structures.
///
/// `CilAssemblyViewData` manages the parsed metadata structures while maintaining
/// direct references to the underlying file data. This structure is designed to
/// preserve the raw layout of metadata streams and tables as they appear in the
/// PE file, enabling future editing operations.
///
/// # Layout Preservation
///
/// Unlike `CilObjectData` which creates resolved and cross-referenced structures,
/// `CilAssemblyViewData` maintains:
/// - Raw metadata table data without resolution
/// - Direct stream references without semantic processing
/// - Original file offsets and layout information
/// - Unprocessed blob and signature data
pub struct CilAssemblyViewData<'a> {
    /// Reference to the owning File structure
    pub file: Arc<File>,

    /// Raw file data slice
    pub data: &'a [u8],

    /// COR20 header containing .NET-specific PE information
    pub cor20header: Cor20Header,

    /// Metadata root header with stream directory
    pub metadata_root: Root,

    /// Raw metadata tables header from #~ or #- stream
    pub metadata_tables: Option<TablesHeader<'a>>,

    /// Strings heap from #Strings stream
    pub strings: Option<Strings<'a>>,

    /// User strings heap from #US stream
    pub userstrings: Option<UserStrings<'a>>,

    /// GUID heap from #GUID stream
    pub guids: Option<Guid<'a>>,

    /// Blob heap from #Blob stream
    pub blobs: Option<Blob<'a>>,

    /// Diagnostics collected during loading (duplicate heaps, structural issues, etc.)
    pub diagnostics: Arc<Diagnostics>,
}

impl<'a> CilAssemblyViewData<'a> {
    /// Creates a new `CilAssemblyViewData` from file data.
    ///
    /// This method parses the essential .NET metadata structures while preserving
    /// their raw form. Unlike `CilObjectData::from_file`, this method:
    /// - Does not resolve cross-references between tables
    /// - Does not create semantic object representations
    /// - Preserves original file layout information
    /// - Focuses on structural metadata access
    /// - Performs no validation or compliance checking
    ///
    /// # Arguments
    ///
    /// * `file` - The File containing PE data
    /// * `data` - Raw file data slice
    ///
    /// # Returns
    ///
    /// Returns the parsed `CilAssemblyViewData` structure or an error if
    /// essential structures cannot be located (e.g., missing CLR header).
    ///
    /// # Errors
    ///
    /// Returns [`crate::Error::NotSupported`] if the file is not a .NET assembly (missing CLR header).
    /// Returns [`crate::Error::OutOfBounds`] if the file data is truncated or corrupted.
    pub fn from_dotscope_file(file: Arc<File>, data: &'a [u8]) -> Result<Self> {
        let (clr_rva, clr_size) = file.clr().ok_or(Error::NotSupported)?;

        if clr_rva == 0 || clr_size == 0 {
            return Err(Error::NotSupported);
        }

        let clr_offset = file.rva_to_offset(clr_rva)?;
        let clr_end = clr_offset
            .checked_add(clr_size)
            .ok_or(out_of_bounds_error!())?;

        if clr_size > data.len() || clr_offset > data.len() || clr_end > data.len() {
            return Err(out_of_bounds_error!());
        }

        let cor20_header = Cor20Header::read(&data[clr_offset..clr_end])?;
        debug!(
            "PE header: CLR {}.{}, metadata at RVA 0x{:X}",
            cor20_header.major_runtime_version,
            cor20_header.minor_runtime_version,
            cor20_header.meta_data_rva
        );

        let metadata_offset = file.rva_to_offset(cor20_header.meta_data_rva as usize)?;
        let metadata_end = metadata_offset
            .checked_add(cor20_header.meta_data_size as usize)
            .ok_or(out_of_bounds_error!())?;

        if metadata_end > data.len() {
            return Err(out_of_bounds_error!());
        }

        let metadata_slice = &data[metadata_offset..metadata_end];
        let metadata_root = Root::read(metadata_slice)?;
        let stream_names: Vec<&str> = metadata_root
            .stream_headers
            .iter()
            .map(|s| s.name.as_str())
            .collect();
        debug!("Metadata streams: {}", stream_names.join(", "));

        let diagnostics = Arc::new(Diagnostics::new());

        let mut metadata_tables = None;
        let mut strings_heap = None;
        let mut userstrings_heap = None;
        let mut guid_heap = None;
        let mut blob_heap = None;

        // Track which streams we've seen to detect duplicates
        let mut seen_tables = false;
        let mut seen_strings = false;
        let mut seen_userstrings = false;
        let mut seen_guid = false;
        let mut seen_blob = false;

        for stream in &metadata_root.stream_headers {
            let stream_offset = stream.offset as usize;
            let stream_size = stream.size as usize;
            let stream_end = stream_offset
                .checked_add(stream_size)
                .ok_or(out_of_bounds_error!())?;

            if stream_end > metadata_slice.len() {
                return Err(out_of_bounds_error!());
            }

            let stream_data = &metadata_slice[stream_offset..stream_end];

            match stream.name.as_str() {
                "#~" | "#-" => {
                    if seen_tables {
                        warn!(
                            "Duplicate metadata stream '{}' detected, using first occurrence",
                            stream.name
                        );
                        diagnostics.warning(
                            DiagnosticCategory::Heap,
                            format!(
                                "Duplicate metadata tables stream '{}' found, using first occurrence",
                                stream.name
                            ),
                        );
                    } else {
                        metadata_tables = Some(TablesHeader::from(stream_data)?);
                        seen_tables = true;
                    }
                }
                "#Strings" => {
                    if seen_strings {
                        warn!(
                            "Duplicate metadata stream '#Strings' detected, using first occurrence"
                        );
                        diagnostics.warning(
                            DiagnosticCategory::Heap,
                            "Duplicate #Strings heap found, using first occurrence",
                        );
                    } else {
                        strings_heap = Some(Strings::from(stream_data)?);
                        seen_strings = true;
                    }
                }
                "#US" => {
                    if seen_userstrings {
                        warn!("Duplicate metadata stream '#US' detected, using first occurrence");
                        diagnostics.warning(
                            DiagnosticCategory::Heap,
                            "Duplicate #US heap found, using first occurrence",
                        );
                    } else {
                        userstrings_heap = Some(UserStrings::from(stream_data)?);
                        seen_userstrings = true;
                    }
                }
                "#GUID" => {
                    if seen_guid {
                        warn!("Duplicate metadata stream '#GUID' detected, using first occurrence");
                        diagnostics.warning(
                            DiagnosticCategory::Heap,
                            "Duplicate #GUID heap found, using first occurrence",
                        );
                    } else {
                        guid_heap = Some(Guid::from(stream_data)?);
                        seen_guid = true;
                    }
                }
                "#Blob" => {
                    if seen_blob {
                        warn!("Duplicate metadata stream '#Blob' detected, using first occurrence");
                        diagnostics.warning(
                            DiagnosticCategory::Heap,
                            "Duplicate #Blob heap found, using first occurrence",
                        );
                    } else {
                        blob_heap = Some(Blob::from(stream_data)?);
                        seen_blob = true;
                    }
                }
                _ => {
                    // Unknown stream - this could be a custom heap or obfuscation
                    diagnostics.info(
                        DiagnosticCategory::Heap,
                        format!("Unknown metadata stream '{}' encountered", stream.name),
                    );
                }
            }
        }

        Ok(CilAssemblyViewData {
            file,
            data,
            cor20header: cor20_header,
            metadata_root,
            metadata_tables,
            strings: strings_heap,
            userstrings: userstrings_heap,
            guids: guid_heap,
            blobs: blob_heap,
            diagnostics,
        })
    }
}

#[self_referencing]
/// A read-only view of a .NET assembly optimized for editing operations.
///
/// `CilAssemblyView` provides raw access to .NET assembly metadata structures
/// while maintaining a 1:1 mapping with the underlying file format. This design
/// preserves the original file layout and structure to enable future editing
/// and modification capabilities.
///
/// # Key Differences from CilObject
///
/// - **Raw Access**: Direct access to metadata tables without semantic resolution
/// - **Structure Preservation**: Maintains original file layout and offsets
/// - **Editing Foundation**: Designed as the base for modification operations
/// - **Minimal Processing**: No cross-reference resolution or object construction
/// - **No Validation**: Pure parsing without format validation or compliance checks
///
/// # Architecture
///
/// The view uses a self-referencing pattern to maintain efficient access to
/// file data while ensuring memory safety. The structure provides:
/// - Direct access to all metadata streams (#~, #Strings, #US, #GUID, #Blob)
/// - Raw metadata table data without semantic interpretation
/// - Original stream headers and layout information
/// - File-level operations for RVA resolution and section access
///
/// # Thread Safety
///
/// `CilAssemblyView` is designed for concurrent read access and implements
/// `Send` and `Sync` for safe use across threads. All operations are read-only
/// and do not modify the underlying file data.
pub struct CilAssemblyView {
    /// Holds the input data, either as memory buffer or memory-mapped file
    file: Arc<File>,

    #[borrows(file)]
    #[not_covariant]
    /// Holds direct references to metadata structures in the file
    data: CilAssemblyViewData<'this>,
}

impl CilAssemblyView {
    /// Creates a new `CilAssemblyView` by loading a .NET assembly from a path.
    ///
    /// This method loads the assembly and parses essential metadata structures
    /// while preserving their raw format. The file is memory-mapped for
    /// efficient access to large assemblies.
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the .NET assembly file (.dll, .exe, or .netmodule).
    ///   Accepts `&Path`, `&str`, `String`, or `PathBuf`.
    ///
    /// # Returns
    ///
    /// Returns a `CilAssemblyView` providing raw access to assembly metadata
    /// or an error if the file cannot be loaded or essential structures are missing.
    ///
    /// # Errors
    ///
    /// Returns [`crate::Error::Io`] if the file cannot be read.
    /// Returns [`crate::Error::NotSupported`] if the file is not a .NET assembly.
    /// Returns [`crate::Error::OutOfBounds`] if the file data is corrupted.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::CilAssemblyView;
    /// use std::path::Path;
    ///
    /// // With Path
    /// let view = CilAssemblyView::from_path(Path::new("assembly.dll"))?;
    ///
    /// // With string slice
    /// let view = CilAssemblyView::from_path("assembly.dll")?;
    ///
    /// // Access raw metadata
    /// let root = view.metadata_root();
    /// println!("Metadata root loaded");
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
        Self::from_path_with_validation(path, ValidationConfig::production())
    }

    /// Creates a new `CilAssemblyView` by loading a .NET assembly from a path with custom validation configuration.
    ///
    /// This method allows you to control which validation checks are performed during loading.
    /// Raw validation (stage 1) is performed if enabled in the configuration.
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the .NET assembly file (.dll, .exe, or .netmodule).
    ///   Accepts `&Path`, `&str`, `String`, or `PathBuf`.
    /// * `validation_config` - Configuration specifying which validation checks to perform
    ///
    /// # Returns
    ///
    /// Returns a `CilAssemblyView` providing raw access to assembly metadata
    /// or an error if the file cannot be loaded, essential structures are missing,
    /// or validation checks fail.
    ///
    /// # Errors
    ///
    /// Returns [`crate::Error::Io`] if the file cannot be read.
    /// Returns [`crate::Error::NotSupported`] if the file is not a .NET assembly.
    /// Returns [`crate::Error::OutOfBounds`] if the file data is corrupted.
    /// Returns validation errors if validation checks fail.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::{CilAssemblyView, ValidationConfig};
    /// use std::path::Path;
    ///
    /// // Load with minimal validation for maximum performance
    /// let view = CilAssemblyView::from_path_with_validation(
    ///     "assembly.dll",
    ///     ValidationConfig::minimal()
    /// )?;
    ///
    /// // Load with comprehensive validation for maximum safety
    /// let view = CilAssemblyView::from_path_with_validation(
    ///     Path::new("assembly.dll"),
    ///     ValidationConfig::comprehensive()
    /// )?;
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    pub fn from_path_with_validation(
        path: impl AsRef<Path>,
        validation_config: ValidationConfig,
    ) -> Result<Self> {
        let input = Arc::new(File::from_path(path)?);
        Self::load_with_validation(input, validation_config)
    }

    /// Creates a new `CilAssemblyView` by parsing a .NET assembly from a memory buffer.
    ///
    /// This method is useful for analyzing assemblies that are already loaded
    /// in memory or obtained from external sources. The data is managed
    /// internally to ensure proper lifetime handling.
    ///
    /// # Arguments
    ///
    /// * `data` - Raw bytes of the .NET assembly in PE format
    ///
    /// # Errors
    ///
    /// Returns [`crate::Error::NotSupported`] if the data is not a .NET assembly.
    /// Returns [`crate::Error::OutOfBounds`] if the data is corrupted or truncated.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::CilAssemblyView;
    ///
    /// let file_data = std::fs::read("assembly.dll")?;
    /// let view = CilAssemblyView::from_mem(file_data)?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn from_mem(data: Vec<u8>) -> Result<Self> {
        Self::from_mem_with_validation(data, ValidationConfig::production())
    }

    /// Creates a new `CilAssemblyView` by parsing a .NET assembly from a memory buffer with custom validation configuration.
    ///
    /// This method allows you to control which validation checks are performed during loading.
    /// Raw validation (stage 1) is performed if enabled in the configuration.
    ///
    /// # Arguments
    ///
    /// * `data` - Raw bytes of the .NET assembly in PE format
    /// * `validation_config` - Configuration specifying which validation checks to perform
    ///
    /// # Returns
    ///
    /// Returns a `CilAssemblyView` providing raw access to assembly metadata
    /// or an error if the data cannot be parsed or validation checks fail.
    ///
    /// # Errors
    ///
    /// Returns [`crate::Error::NotSupported`] if the data is not a .NET assembly.
    /// Returns [`crate::Error::OutOfBounds`] if the data is corrupted or truncated.
    /// Returns validation errors if validation checks fail.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::{CilAssemblyView, ValidationConfig};
    ///
    /// let file_data = std::fs::read("assembly.dll")?;
    ///
    /// // Load with production validation settings
    /// let view = CilAssemblyView::from_mem_with_validation(
    ///     file_data,
    ///     ValidationConfig::production()
    /// )?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn from_mem_with_validation(
        data: Vec<u8>,
        validation_config: ValidationConfig,
    ) -> Result<Self> {
        let input = Arc::new(File::from_mem(data)?);
        Self::load_with_validation(input, validation_config)
    }

    /// Creates a CilAssemblyView from a dotscope::file::File.
    ///
    /// This allows you to inspect the PE file before deciding to parse the .NET metadata,
    /// or to reuse an existing File instance.
    ///
    /// # Arguments
    /// * `file` - A File instance containing a .NET assembly
    ///
    /// # Errors
    /// Returns an error if:
    /// - The File doesn't contain a CLR runtime header
    /// - The .NET metadata is corrupted or invalid
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::{File, CilAssemblyView};
    /// use std::path::Path;
    ///
    /// let file = File::from_path(Path::new("assembly.dll"))?;
    /// if file.is_clr() {
    ///     let view = CilAssemblyView::from_dotscope_file(file)?;
    ///     println!("Loaded .NET assembly view");
    /// }
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    pub fn from_dotscope_file(file: File) -> Result<Self> {
        Self::from_dotscope_file_with_validation(file, ValidationConfig::production())
    }

    /// Creates a CilAssemblyView from a dotscope::file::File with validation.
    ///
    /// # Arguments
    /// * `file` - A File instance containing a .NET assembly
    /// * `validation_config` - Validation configuration
    ///
    /// # Errors
    /// Returns an error if:
    /// - The File doesn't contain a CLR runtime header
    /// - The .NET metadata is corrupted or invalid
    /// - Validation checks fail
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::{File, CilAssemblyView, ValidationConfig};
    /// use std::path::Path;
    ///
    /// let file = File::from_path(Path::new("assembly.dll"))?;
    /// let view = CilAssemblyView::from_dotscope_file_with_validation(
    ///     file,
    ///     ValidationConfig::production()
    /// )?;
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    pub fn from_dotscope_file_with_validation(
        file: File,
        validation_config: ValidationConfig,
    ) -> Result<Self> {
        let file_arc = Arc::new(file);
        Self::load_with_validation(file_arc, validation_config)
    }

    /// Creates a CilAssemblyView from an opened std::fs::File.
    ///
    /// The file is memory-mapped for efficient access.
    ///
    /// # Arguments
    /// * `file` - An opened file handle to a .NET assembly
    ///
    /// # Errors
    /// Returns an error if the file is not a valid .NET assembly.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::CilAssemblyView;
    /// use std::fs::File;
    ///
    /// let std_file = File::open("assembly.dll")?;
    /// let view = CilAssemblyView::from_std_file(std_file)?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn from_std_file(file: std::fs::File) -> Result<Self> {
        Self::from_std_file_with_validation(file, ValidationConfig::production())
    }

    /// Creates a CilAssemblyView from an opened std::fs::File with validation.
    ///
    /// The file is memory-mapped for efficient access.
    ///
    /// # Arguments
    /// * `file` - An opened file handle to a .NET assembly
    /// * `validation_config` - Configuration specifying which validation checks to perform
    ///
    /// # Errors
    /// Returns an error if the file is not a valid .NET assembly or if validation checks fail.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::{CilAssemblyView, ValidationConfig};
    /// use std::fs::File;
    ///
    /// let std_file = File::open("assembly.dll")?;
    /// let view = CilAssemblyView::from_std_file_with_validation(
    ///     std_file,
    ///     ValidationConfig::production()
    /// )?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn from_std_file_with_validation(
        file: std::fs::File,
        validation_config: ValidationConfig,
    ) -> Result<Self> {
        let pe_file = File::from_std_file(file)?;
        Self::from_dotscope_file_with_validation(pe_file, validation_config)
    }

    /// Creates a CilAssemblyView from any reader.
    ///
    /// # Arguments
    /// * `reader` - Any type implementing Read
    ///
    /// # Errors
    /// Returns an error if the data is not a valid .NET assembly.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::CilAssemblyView;
    /// use std::io::Cursor;
    ///
    /// let data = vec![/* PE bytes */];
    /// let cursor = Cursor::new(data);
    /// let view = CilAssemblyView::from_reader(cursor)?;
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    pub fn from_reader<R: std::io::Read>(reader: R) -> Result<Self> {
        Self::from_reader_with_validation(reader, ValidationConfig::production())
    }

    /// Creates a CilAssemblyView from any reader with validation.
    ///
    /// # Arguments
    /// * `reader` - Any type implementing Read
    /// * `validation_config` - Configuration specifying which validation checks to perform
    ///
    /// # Errors
    /// Returns an error if the data is not a valid .NET assembly or if validation checks fail.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::{CilAssemblyView, ValidationConfig};
    /// use std::io::Cursor;
    ///
    /// let data = vec![/* PE bytes */];
    /// let cursor = Cursor::new(data);
    /// let view = CilAssemblyView::from_reader_with_validation(
    ///     cursor,
    ///     ValidationConfig::production()
    /// )?;
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    pub fn from_reader_with_validation<R: std::io::Read>(
        reader: R,
        validation_config: ValidationConfig,
    ) -> Result<Self> {
        let pe_file = File::from_reader(reader)?;
        Self::from_dotscope_file_with_validation(pe_file, validation_config)
    }

    /// Internal method for loading a CilAssemblyView from a File structure with validation.
    ///
    /// This method serves as the common implementation for validation-enabled loading operations.
    /// It first loads the assembly normally, then performs raw validation (stage 1) if enabled
    /// in the configuration.
    ///
    /// # Arguments
    ///
    /// * `file` - Arc-wrapped File containing the PE assembly data
    /// * `validation_config` - Configuration specifying which validation checks to perform
    ///
    /// # Returns
    ///
    /// Returns a fully constructed `CilAssemblyView` with parsed metadata structures
    /// or an error if parsing or validation fails.
    fn load_with_validation(file: Arc<File>, validation_config: ValidationConfig) -> Result<Self> {
        let view = CilAssemblyView::try_new(file, |file| {
            CilAssemblyViewData::from_dotscope_file(file.clone(), file.data())
        })?;

        if validation_config.should_validate_raw() {
            view.validate(validation_config)?;
        }

        Ok(view)
    }

    /// Returns the COR20 header containing .NET-specific PE information.
    ///
    /// The COR20 header provides essential information about the .NET assembly
    /// including metadata location, entry point, and runtime flags.
    ///
    /// # Returns
    ///
    /// Reference to the [`Cor20Header`] structure.
    #[must_use]
    pub fn cor20header(&self) -> &Cor20Header {
        self.with_data(|data| &data.cor20header)
    }

    /// Returns the metadata root header containing stream directory information.
    ///
    /// The metadata root is the entry point to .NET metadata, containing
    /// version information and the directory of all metadata streams.
    ///
    /// # Returns
    ///
    /// Reference to the [`Root`] structure.
    #[must_use]
    pub fn metadata_root(&self) -> &Root {
        self.with_data(|data| &data.metadata_root)
    }

    /// Returns raw access to the metadata tables from the #~ or #- stream.
    ///
    /// Provides direct access to the metadata tables structure without
    /// semantic interpretation or cross-reference resolution.
    ///
    /// # Returns
    ///
    /// - `Some(&TablesHeader)` if metadata tables are present
    /// - `None` if no tables stream exists
    #[must_use]
    pub fn tables(&self) -> Option<&TablesHeader<'_>> {
        self.with_data(|data| data.metadata_tables.as_ref())
    }

    /// Returns direct access to the strings heap from the #Strings stream.
    ///
    /// # Returns
    ///
    /// - `Some(&Strings)` if the strings heap is present
    /// - `None` if no #Strings stream exists
    #[must_use]
    pub fn strings(&self) -> Option<&Strings<'_>> {
        self.with_data(|data| data.strings.as_ref())
    }

    /// Returns direct access to the user strings heap from the #US stream.
    ///
    /// # Returns
    ///
    /// - `Some(&UserStrings)` if the user strings heap is present
    /// - `None` if no #US stream exists
    #[must_use]
    pub fn userstrings(&self) -> Option<&UserStrings<'_>> {
        self.with_data(|data| data.userstrings.as_ref())
    }

    /// Returns direct access to the GUID heap from the #GUID stream.
    ///
    /// # Returns
    ///
    /// - `Some(&Guid)` if the GUID heap is present
    /// - `None` if no #GUID stream exists
    #[must_use]
    pub fn guids(&self) -> Option<&Guid<'_>> {
        self.with_data(|data| data.guids.as_ref())
    }

    /// Returns direct access to the blob heap from the #Blob stream.
    ///
    /// # Returns
    ///
    /// - `Some(&Blob)` if the blob heap is present
    /// - `None` if no #Blob stream exists
    #[must_use]
    pub fn blobs(&self) -> Option<&Blob<'_>> {
        self.with_data(|data| data.blobs.as_ref())
    }

    /// Returns all stream headers from the metadata root.
    ///
    /// Stream headers contain location and size information for all
    /// metadata streams in the assembly.
    ///
    /// # Returns
    ///
    /// Reference to the vector of [`StreamHeader`] structures.
    #[must_use]
    pub fn streams(&self) -> &[StreamHeader] {
        self.with_data(|data| &data.metadata_root.stream_headers)
    }

    /// Returns the underlying file representation of this assembly.
    ///
    /// Provides access to PE file operations, RVA resolution, and
    /// low-level file structure access.
    ///
    /// # Returns
    ///
    /// Reference to the `Arc<File>` containing the PE file representation.
    #[must_use]
    pub fn file(&self) -> &Arc<File> {
        self.borrow_file()
    }

    /// Returns the raw file data as a byte slice.
    ///
    /// # Returns
    ///
    /// Reference to the complete file data.
    #[must_use]
    pub fn data(&self) -> &[u8] {
        self.with_data(|data| data.data)
    }

    /// Returns the diagnostics collected during loading.
    ///
    /// Diagnostics include warnings about structural issues like duplicate heap
    /// streams that were encountered during loading. These don't prevent loading
    /// but may indicate obfuscation or malformed metadata.
    ///
    /// # Returns
    ///
    /// Reference to the shared [`crate::metadata::diagnostics::Diagnostics`] container.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::CilAssemblyView;
    /// use std::path::Path;
    ///
    /// let view = CilAssemblyView::from_path(Path::new("assembly.dll"))?;
    ///
    /// if view.diagnostics().has_any() {
    ///     println!("Loading issues found:");
    ///     for diag in view.diagnostics().iter() {
    ///         println!("  {}", diag);
    ///     }
    /// }
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    #[must_use]
    pub fn diagnostics(&self) -> &Arc<Diagnostics> {
        self.with_data(|data| &data.diagnostics)
    }

    /// Converts this read-only view into a mutable assembly.
    ///
    /// This method consumes the `CilAssemblyView` and creates a `CilAssembly`
    /// that can be modified. The original data remains unchanged until
    /// modifications are made.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::CilAssemblyView;
    /// use std::path::Path;
    ///
    /// let view = CilAssemblyView::from_path(Path::new("assembly.dll"))?;
    /// let mut assembly = view.to_owned();
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    #[must_use]
    pub fn to_owned(self) -> CilAssembly {
        CilAssembly::new(self)
    }

    /// Performs raw validation (stage 1) on the loaded assembly view.
    ///
    /// This method validates the raw assembly data using the unified validation engine
    /// without any modifications (changes = None). It performs basic structural
    /// validation and integrity checks on the raw metadata.
    ///
    /// # Arguments
    ///
    /// * `config` - Validation configuration specifying which validations to perform
    ///
    /// # Returns
    ///
    /// Returns `Ok(())` if validation passes, or an error describing validation failures.
    ///
    /// # Errors
    ///
    /// Returns validation errors if any validation checks fail, including schema violations,
    /// RID consistency issues, or referential integrity problems.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::{CilAssemblyView, ValidationConfig};
    /// use std::path::Path;
    ///
    /// let view = CilAssemblyView::from_path(Path::new("assembly.dll"))?;
    /// view.validate(ValidationConfig::production())?;
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    pub fn validate(&self, config: ValidationConfig) -> Result<()> {
        if config == ValidationConfig::disabled() {
            return Ok(());
        }

        let engine = ValidationEngine::new(self, config)?;
        let result = engine.execute_stage1_validation(self, None)?;
        result.into_result()
    }

    /// Extract assembly dependencies from the AssemblyRef metadata table.
    ///
    /// This method reads the AssemblyRef table (0x23) from the metadata to extract
    /// all referenced assemblies as `AssemblyIdentity` instances. These represent
    /// the external assemblies that this assembly depends on.
    ///
    /// # Returns
    ///
    /// A vector of `AssemblyIdentity` for each referenced assembly. Returns an empty
    /// vector if the metadata tables are unavailable or the AssemblyRef table is empty.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::CilAssemblyView;
    /// use std::path::Path;
    ///
    /// let view = CilAssemblyView::from_path(Path::new("assembly.dll"))?;
    /// let deps = view.dependencies();
    ///
    /// for dep in deps {
    ///     println!("Depends on: {} v{}", dep.name, dep.version);
    /// }
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    #[must_use]
    pub fn dependencies(&self) -> Vec<AssemblyIdentity> {
        let (Some(tables), Some(strings), Some(blobs)) =
            (self.tables(), self.strings(), self.blobs())
        else {
            return Vec::new();
        };

        let Some(assembly_ref_table) = tables.table::<AssemblyRefRaw>() else {
            return Vec::new();
        };

        assembly_ref_table
            .into_iter()
            .filter_map(|row| row.to_owned(strings, blobs).ok())
            .map(|assembly_ref| AssemblyIdentity::from_assembly_ref(&assembly_ref))
            .collect()
    }

    /// Extract assembly identity from the Assembly metadata table.
    ///
    /// This method reads the Assembly table (0x20) from the metadata to construct
    /// the complete AssemblyIdentity including name, version, culture, and strong name.
    /// This is the proper way to get assembly identity information rather than relying
    /// on file names or heuristics.
    ///
    /// # Returns
    ///
    /// * `Ok(Some(AssemblyIdentity))` - Complete assembly identity extracted from metadata
    /// * `Ok(None)` - Assembly table is missing or empty (this is a netmodule)
    /// * `Err(_)` - If metadata is malformed or cannot be parsed
    ///
    /// # Netmodules
    ///
    /// .NET netmodules (.netmodule files) are modules without their own assembly manifest.
    /// They contain types and code but are designed to be linked into a multi-file assembly.
    /// For these modules, this method returns `Ok(None)` instead of an error.
    ///
    /// # Errors
    ///
    /// Returns an error if the metadata tables or strings heap are missing, or if the assembly
    /// has a public key reference but the blob heap is unavailable.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::CilAssemblyView;
    /// use std::path::Path;
    ///
    /// let view = CilAssemblyView::from_path(Path::new("assembly.dll"))?;
    /// if let Some(identity) = view.identity()? {
    ///     println!("Assembly: {} v{}", identity.name, identity.version.major);
    /// } else {
    ///     println!("This is a netmodule without assembly identity");
    /// }
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    pub fn identity(&self) -> Result<Option<AssemblyIdentity>> {
        let (Some(tables), Some(strings)) = (self.tables(), self.strings()) else {
            return Err(malformed_error!(
                "Metadata tables or strings heap are missing"
            ));
        };

        let Some(assembly_table) = tables.table::<AssemblyRaw>() else {
            // No Assembly table - this is a netmodule
            return Ok(None);
        };

        let Some(assembly_row) = assembly_table.iter().next() else {
            // Empty Assembly table - also a netmodule
            return Ok(None);
        };

        // For identity extraction, we can work without the blob heap if there's no public key
        let blobs = self.blobs();

        // Get assembly name and version directly from raw row (doesn't need blob heap)
        let name = strings.get(assembly_row.name as usize)?.to_string();

        let culture = if assembly_row.culture == 0 {
            None
        } else {
            Some(strings.get(assembly_row.culture as usize)?.to_string())
        };

        // Public key requires blob heap - only fail if we need it but don't have it
        let strong_name = if assembly_row.public_key == 0 {
            None
        } else if let Some(blobs) = blobs {
            Some(Identity::PubKey(
                blobs.get(assembly_row.public_key as usize)?.to_vec(),
            ))
        } else {
            // Has public key index but no blob heap - this is malformed
            return Err(malformed_error!(
                "Assembly has public key reference but no blob heap"
            ));
        };

        let processor_architecture = tables
            .table::<AssemblyProcessorRaw>()
            .and_then(|proc_table| proc_table.iter().next())
            .and_then(|proc| ProcessorArchitecture::try_from(proc.processor).ok());

        #[allow(clippy::cast_possible_truncation)]
        Ok(Some(AssemblyIdentity {
            name,
            version: AssemblyVersion {
                major: assembly_row.major_version as u16,
                minor: assembly_row.minor_version as u16,
                build: assembly_row.build_number as u16,
                revision: assembly_row.revision_number as u16,
            },
            culture,
            strong_name,
            processor_architecture,
        }))
    }

    /// Get the module name from the Module metadata table.
    ///
    /// The Module table always contains exactly one row with the module name.
    /// This is useful for netmodules which don't have an Assembly table.
    ///
    /// # Returns
    ///
    /// * `Some(String)` - The module name (e.g., "MyModule.dll" or "Helper.netmodule")
    /// * `None` - If the Module table is missing or name cannot be resolved
    pub fn module_name(&self) -> Option<String> {
        let tables = self.tables()?;
        let strings = self.strings()?;

        let module_table = tables.table::<ModuleRaw>()?;
        let module_row = module_table.iter().next()?;

        strings.get(module_row.name as usize).ok().map(String::from)
    }

    /// Check if this is a netmodule (module without assembly manifest).
    ///
    /// A netmodule is a .NET module that lacks its own assembly identity.
    /// It contains types and code but is designed to be linked into a
    /// multi-file assembly. Netmodules have a Module table but no Assembly table.
    ///
    /// # Returns
    ///
    /// * `true` - This is a netmodule (no Assembly table)
    /// * `false` - This is a full assembly with its own identity
    #[must_use]
    pub fn is_netmodule(&self) -> bool {
        let Some(tables) = self.tables() else {
            return false;
        };

        // A netmodule has a Module table but no Assembly table
        let has_module = tables.table::<ModuleRaw>().is_some();
        let has_assembly = tables
            .table::<AssemblyRaw>()
            .is_some_and(|t| t.iter().next().is_some());

        has_module && !has_assembly
    }

    /// Get assembly identity, or create a fallback identity from module name for netmodules.
    ///
    /// This method is useful for cases where an identity is required even for netmodules,
    /// such as type registry initialization. For netmodules, a synthetic identity is
    /// created with version 0.0.0.0 and the module name (without extension).
    ///
    /// # Returns
    ///
    /// * `Ok(AssemblyIdentity)` - Either from Assembly table or synthesized from module name
    /// * `Err(_)` - If neither Assembly nor Module table can provide identity
    ///
    /// # Errors
    ///
    /// Returns an error if neither the Assembly nor Module table are available for identity
    /// extraction, or if the underlying `identity()` call fails.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use dotscope::CilAssemblyView;
    /// use std::path::Path;
    ///
    /// let view = CilAssemblyView::from_path(Path::new("helper.netmodule"))?;
    /// let identity = view.identity_or_fallback()?;
    /// // Returns identity with name "helper" and version 0.0.0.0
    /// # Ok::<(), dotscope::Error>(())
    /// ```
    pub fn identity_or_fallback(&self) -> Result<AssemblyIdentity> {
        // Try to get real assembly identity first
        if let Some(identity) = self.identity()? {
            return Ok(identity);
        }

        // Fallback to module name for netmodules
        let module_name = self.module_name().ok_or_else(|| {
            malformed_error!("Neither Assembly nor Module table available for identity")
        })?;

        // Strip extension from module name (e.g., "helper.netmodule" -> "helper")
        let name = module_name
            .rsplit_once('.')
            .map(|(base, _)| base.to_string())
            .unwrap_or(module_name);

        Ok(AssemblyIdentity {
            name,
            version: AssemblyVersion {
                major: 0,
                minor: 0,
                build: 0,
                revision: 0,
            },
            culture: None,
            strong_name: None,
            processor_architecture: None,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test::factories::metadata::cilassemblyview::verify_assembly_view_complete;
    use std::{fs, path::PathBuf};

    #[test]
    fn from_file() {
        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
        let view = CilAssemblyView::from_path(&path).unwrap();

        verify_assembly_view_complete(&view);
    }

    #[test]
    fn from_buffer() {
        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
        let data = fs::read(path).unwrap();
        let view = CilAssemblyView::from_mem(data.clone()).unwrap();

        assert_eq!(view.data(), data.as_slice());
        verify_assembly_view_complete(&view);
    }

    #[test]
    fn test_error_handling() {
        // Test with non-existent file
        let result = CilAssemblyView::from_path(Path::new("non_existent_file.dll"));
        assert!(result.is_err());

        // Test with invalid data
        let invalid_data = vec![0u8; 100];
        let result = CilAssemblyView::from_mem(invalid_data);
        assert!(result.is_err());

        // Test with empty data
        let empty_data = Vec::new();
        let result = CilAssemblyView::from_mem(empty_data);
        assert!(result.is_err());
    }
}