1#![allow(dead_code)]
19
20use super::memory_view::MemoryView;
21use super::policy::{InferenceDecision, InferencePolicy};
22use super::scoring::{
23 candidate_table, content_analysis, lifetime_analysis, pointer_heuristic, size_heuristic,
24 stack_trace_analysis, vec_string_layout, Score,
25};
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31pub enum TypeKind {
32 Vec,
34 String,
36 CString,
38 Pointer,
40 FatPtr,
42 Buffer,
44 CStruct,
46 Unknown,
48}
49
50impl std::fmt::Display for TypeKind {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 match self {
53 TypeKind::Vec => write!(f, "Vec<_>"),
54 TypeKind::String => write!(f, "String"),
55 TypeKind::CString => write!(f, "CString"),
56 TypeKind::Pointer => write!(f, "*mut c_void"),
57 TypeKind::FatPtr => write!(f, "&[T]"),
58 TypeKind::Buffer => write!(f, "[u8]"),
59 TypeKind::CStruct => write!(f, "CStruct"),
60 TypeKind::Unknown => write!(f, "unknown"),
61 }
62 }
63}
64
65#[derive(Clone, Copy, Debug)]
72pub struct TypeGuess {
73 pub kind: TypeKind,
75 pub confidence: u16,
77 pub method: InferenceMethod,
79}
80
81#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
83pub enum InferenceMethod {
84 #[default]
85 Unknown,
86 SizeHeuristic,
87 LayoutDetection,
88 Utf8Validation,
89 ContentAnalysis,
90 EntropyAnalysis,
91 StackTraceAnalysis,
92 LifetimeAnalysis,
93 Combined,
94}
95
96impl TypeGuess {
97 pub fn new(kind: TypeKind, confidence: u16) -> Self {
99 Self {
100 kind,
101 confidence,
102 method: InferenceMethod::Combined,
103 }
104 }
105
106 pub fn with_method(kind: TypeKind, confidence: u16, method: InferenceMethod) -> Self {
108 Self {
109 kind,
110 confidence,
111 method,
112 }
113 }
114
115 pub fn unknown() -> Self {
117 Self {
118 kind: TypeKind::Unknown,
119 confidence: 0,
120 method: InferenceMethod::Unknown,
121 }
122 }
123
124 pub fn display_with_confidence(&self) -> String {
129 if self.confidence == 0 {
130 return "-".to_string();
131 }
132 format!("{} (score {})", self.kind, self.confidence)
133 }
134}
135
136pub struct UnsafeInferenceEngine;
150
151impl UnsafeInferenceEngine {
152 pub fn infer_single(view: &MemoryView, size: usize) -> TypeGuess {
154 infer(view, size, None, None, None)
155 }
156
157 pub fn infer_from_bytes(data: &[u8], size: usize) -> TypeGuess {
159 let view = MemoryView::new(data);
160 infer(&view, size, None, None, None)
161 }
162
163 pub fn infer_with_context(
165 data: &[u8],
166 size: usize,
167 stack_trace: Option<&[String]>,
168 alloc_time: Option<u64>,
169 dealloc_time: Option<u64>,
170 ) -> TypeGuess {
171 let view = MemoryView::new(data);
172 infer(&view, size, stack_trace, alloc_time, dealloc_time)
173 }
174
175 pub fn decide_from_bytes(data: &[u8], size: usize) -> InferenceDecision {
181 let view = MemoryView::new(data);
182 infer_decision(&view, size, None, None, None, &InferencePolicy::PRELIMINARY)
183 }
184
185 pub fn decide_with_policy(
187 data: &[u8],
188 size: usize,
189 policy: &InferencePolicy,
190 ) -> InferenceDecision {
191 let view = MemoryView::new(data);
192 infer_decision(&view, size, None, None, None, policy)
193 }
194
195 pub fn run(records: &mut [InferenceRecord]) {
197 for record in records.iter_mut() {
198 if let Some(ref memory) = record.memory {
199 let view = MemoryView::new(memory);
200 record.inferred = Some(infer(
201 &view,
202 record.size,
203 record.stack_trace.as_deref(),
204 record.alloc_time,
205 record.dealloc_time,
206 ));
207 }
208 }
209 }
210}
211
212pub struct InferenceRecord {
214 pub ptr: usize,
216 pub size: usize,
218 pub memory: Option<Vec<u8>>,
220 pub stack_trace: Option<Vec<String>>,
222 pub alloc_time: Option<u64>,
224 pub dealloc_time: Option<u64>,
226 pub inferred: Option<TypeGuess>,
228}
229
230impl InferenceRecord {
231 pub fn new(ptr: usize, size: usize, memory: Option<Vec<u8>>) -> Self {
233 Self {
234 ptr,
235 size,
236 memory,
237 stack_trace: None,
238 alloc_time: None,
239 dealloc_time: None,
240 inferred: None,
241 }
242 }
243
244 pub fn with_context(
246 ptr: usize,
247 size: usize,
248 memory: Option<Vec<u8>>,
249 stack_trace: Option<Vec<String>>,
250 alloc_time: Option<u64>,
251 dealloc_time: Option<u64>,
252 ) -> Self {
253 Self {
254 ptr,
255 size,
256 memory,
257 stack_trace,
258 alloc_time,
259 dealloc_time,
260 inferred: None,
261 }
262 }
263}
264
265fn infer(
267 view: &MemoryView,
268 size: usize,
269 stack_trace: Option<&[String]>,
270 alloc_time: Option<u64>,
271 dealloc_time: Option<u64>,
272) -> TypeGuess {
273 decision_to_guess(&infer_decision(
274 view,
275 size,
276 stack_trace,
277 alloc_time,
278 dealloc_time,
279 &InferencePolicy::PRELIMINARY,
280 ))
281}
282
283fn infer_decision(
285 view: &MemoryView,
286 size: usize,
287 stack_trace: Option<&[String]>,
288 alloc_time: Option<u64>,
289 dealloc_time: Option<u64>,
290 policy: &InferencePolicy,
291) -> InferenceDecision {
292 let mut score = Score::default();
293
294 size_heuristic(size, &mut score);
296
297 let header_only =
299 vec_string_layout(view, &mut score) && view.len() == 3 * std::mem::size_of::<usize>();
300
301 if !header_only {
305 content_analysis(view, &mut score);
306 }
307
308 pointer_heuristic(view, &mut score);
310
311 stack_trace_analysis(stack_trace, &mut score);
313
314 lifetime_analysis(alloc_time, dealloc_time, &mut score);
316
317 finalize(&score, policy)
318}
319
320fn method_for(kind: TypeKind) -> InferenceMethod {
323 match kind {
324 TypeKind::Vec => InferenceMethod::LayoutDetection,
325 TypeKind::String => InferenceMethod::Utf8Validation,
326 TypeKind::CString => InferenceMethod::ContentAnalysis,
327 TypeKind::Pointer | TypeKind::FatPtr => InferenceMethod::SizeHeuristic,
328 TypeKind::Buffer => InferenceMethod::EntropyAnalysis,
329 TypeKind::CStruct => InferenceMethod::Combined,
330 TypeKind::Unknown => InferenceMethod::Unknown,
331 }
332}
333
334fn decision_to_guess(decision: &InferenceDecision) -> TypeGuess {
336 match decision.predicted {
337 Some(kind) => TypeGuess::with_method(kind, decision.raw_score, method_for(kind)),
338 None => TypeGuess::unknown(),
339 }
340}
341
342fn finalize(score: &Score, policy: &InferencePolicy) -> InferenceDecision {
347 let mut table = candidate_table(score);
348 table.sort_by_key(|candidate| std::cmp::Reverse(candidate.1));
349
350 let (best_kind, best_score, _) = table[0];
351 let (runner_kind, runner_score, _) = table[1];
352
353 if best_score < policy.min_score {
354 return InferenceDecision {
355 predicted: None,
356 best_candidate: best_kind,
357 raw_score: best_score,
358 runner_up_score: runner_score,
359 abstain_reason: Some(format!(
360 "best candidate {best_kind} scored {best_score}, below min_score {}",
361 policy.min_score
362 )),
363 };
364 }
365
366 if best_score.saturating_sub(runner_score) < policy.min_margin {
367 return InferenceDecision {
368 predicted: None,
369 best_candidate: best_kind,
370 raw_score: best_score,
371 runner_up_score: runner_score,
372 abstain_reason: Some(format!(
373 "ambiguous: {best_kind} scored {best_score} vs {runner_kind} {runner_score}, \
374 margin below min_margin {}",
375 policy.min_margin
376 )),
377 };
378 }
379
380 InferenceDecision {
381 predicted: Some(best_kind),
382 best_candidate: best_kind,
383 raw_score: best_score,
384 runner_up_score: runner_score,
385 abstain_reason: None,
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392
393 fn create_vec_memory(ptr: usize, len: usize, cap: usize) -> Vec<u8> {
394 let mut data = vec![0u8; 24];
395 data[..8].copy_from_slice(&ptr.to_le_bytes());
396 data[8..16].copy_from_slice(&len.to_le_bytes());
397 data[16..24].copy_from_slice(&cap.to_le_bytes());
398 data
399 }
400
401 fn create_string_memory(ptr: usize, len: usize, cap: usize) -> Vec<u8> {
402 create_vec_memory(ptr, len, cap)
403 }
404
405 fn create_cstring_memory(content: &[u8]) -> Vec<u8> {
406 let mut data = content.to_vec();
407 data.push(0);
408 data
409 }
410
411 #[test]
412 #[cfg(target_os = "macos")]
413 fn test_infer_vec_with_large_capacity() {
414 let memory = create_vec_memory(0x10000, 10, 100);
415 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 24);
416
417 assert_eq!(guess.kind, TypeKind::Vec);
418 assert!(guess.confidence >= 60);
419 }
420
421 #[test]
422 #[cfg(target_os = "macos")]
423 fn test_infer_vec_with_power_of_two_capacity() {
424 let memory = create_vec_memory(0x10000, 50, 64);
425 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 24);
426
427 assert!(guess.kind == TypeKind::Vec || guess.kind == TypeKind::String);
429 }
430
431 #[test]
432 #[cfg(target_os = "macos")]
433 fn test_infer_string_with_small_spare() {
434 let memory = create_string_memory(0x10000, 10, 12);
435 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 24);
436
437 assert!(guess.kind == TypeKind::String || guess.kind == TypeKind::Vec);
439 }
440
441 #[test]
442 fn test_infer_cstring_printable() {
443 let memory = create_cstring_memory(b"hello world");
444 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 12);
445
446 assert_eq!(guess.kind, TypeKind::CString);
447 assert!(guess.confidence >= 70);
448 }
449
450 #[test]
451 fn test_infer_cstring_mixed_content() {
452 let memory = create_cstring_memory(&[0x30, 0x31, 0x80, 0x90, 0x20]);
453 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 6);
454
455 assert!(guess.confidence < 70);
457 }
458
459 #[test]
460 fn test_infer_pointer_size_8() {
461 let memory = [0u8; 8];
462 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 8);
463
464 assert_eq!(guess.kind, TypeKind::Pointer);
465 assert!(guess.confidence >= 30);
466 }
467
468 #[test]
469 #[cfg(target_os = "macos")]
470 fn test_infer_fat_ptr_size_16() {
471 let mut memory = [0u8; 16];
474 let ptr: usize = 0x10000;
475 let len: usize = 100;
476 memory[0..8].copy_from_slice(&ptr.to_le_bytes());
477 memory[8..16].copy_from_slice(&len.to_le_bytes());
478
479 let decision = UnsafeInferenceEngine::decide_from_bytes(&memory, 16);
483 assert!(
484 !decision.is_accepted(),
485 "a FatPtr/buffer tie must abstain, got {:?}",
486 decision.predicted
487 );
488 assert!(
489 decision.abstain_reason.is_some(),
490 "abstaining must always carry a reason"
491 );
492 }
493
494 #[test]
495 #[cfg(target_os = "macos")]
496 fn test_infer_cstruct_multiple_pointers() {
497 let mut memory = vec![1u8; 40];
498 let ptr1: usize = 0x10000;
499 let ptr2: usize = 0x20000;
500 memory[0..8].copy_from_slice(&ptr1.to_le_bytes());
501 memory[24..32].copy_from_slice(&ptr2.to_le_bytes());
502
503 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 40);
504
505 assert_eq!(guess.kind, TypeKind::CStruct);
506 assert!(guess.confidence >= 30);
507 }
508
509 #[test]
510 fn test_power_of_two_signal() {
511 let memory = vec![0u8; 64];
513 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 64);
514
515 assert!(guess.confidence > 0 || guess.kind == TypeKind::Unknown);
517 }
518
519 #[test]
520 fn test_zero_filled_buffer() {
521 let memory = [0u8; 64];
522 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 64);
523
524 assert!(
526 guess.kind == TypeKind::Buffer
527 || guess.kind == TypeKind::CStruct
528 || guess.kind == TypeKind::Unknown
529 );
530 }
531
532 #[test]
533 fn test_type_guess_display() {
534 let guess = TypeGuess::new(TypeKind::Vec, 85);
535 assert_eq!(
536 guess.display_with_confidence(),
537 "Vec<_> (score 85)",
538 "the heuristic score must not be rendered as a percentage"
539 );
540
541 let unknown = TypeGuess::unknown();
542 assert_eq!(unknown.display_with_confidence(), "-");
543 }
544
545 #[test]
546 fn test_run_on_records() {
547 let vec_memory = create_vec_memory(0x10000, 10, 100);
548 let mut records = vec![
549 InferenceRecord::new(0x1000, 24, Some(vec_memory)),
550 InferenceRecord::new(0x2000, 8, None),
551 ];
552
553 UnsafeInferenceEngine::run(&mut records);
554
555 assert!(records[0].inferred.is_some());
556 assert!(records[1].inferred.is_none());
557 }
558
559 #[test]
560 fn test_multiple_nulls_not_cstring() {
561 let memory = [
563 b'h', b'e', b'l', b'l', b'o', 0, b'w', b'o', b'r', b'l', b'd', 0,
564 ];
565 let single_nul =
566 UnsafeInferenceEngine::decide_from_bytes(&create_cstring_memory(b"hello world"), 12);
567 let multiple_nuls = UnsafeInferenceEngine::decide_from_bytes(&memory, 12);
568
569 assert!(
572 multiple_nuls.raw_score < single_nul.raw_score,
573 "multiple NULs must lower the CString evidence ({} vs {})",
574 multiple_nuls.raw_score,
575 single_nul.raw_score
576 );
577 }
578
579 #[test]
580 fn test_utf8_validation_printable_string() {
581 let memory = b"Hello, World! This is a test string.".to_vec();
583 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, memory.len());
584
585 assert_eq!(guess.kind, TypeKind::String);
587 assert!(guess.confidence >= 90);
588 assert_eq!(guess.method, InferenceMethod::Utf8Validation);
589 }
590
591 #[test]
592 fn test_utf8_validation_non_printable() {
593 let memory = vec![0xC2, 0x80, 0xC2, 0x81, 0xC2, 0x82]; let decision = UnsafeInferenceEngine::decide_from_bytes(&memory, memory.len());
596
597 assert!(
600 !decision.is_accepted(),
601 "an undecidable block must not be reported as a concrete type, got {:?}",
602 decision.predicted
603 );
604 assert!(
605 decision.abstain_reason.is_some(),
606 "the abstain reason must be reported"
607 );
608 }
609
610 #[test]
614 fn test_utf8_validation_cjk_string() {
615 let memory = "内存分析工具:数据准确性验证".as_bytes().to_vec();
616 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, memory.len());
617
618 assert_eq!(
619 guess.kind,
620 TypeKind::String,
621 "CJK text is text: an ASCII-only printable ratio must not turn it into Vec"
622 );
623 }
624
625 #[test]
626 fn test_utf8_validation_invalid() {
627 let memory = vec![0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA];
629 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, memory.len());
630
631 assert_ne!(guess.kind, TypeKind::String);
633 }
634
635 #[test]
636 fn test_large_data_buffer_boost() {
637 let memory = vec![0u8; 5000];
639 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 5000);
640
641 assert!(guess.kind == TypeKind::Buffer || guess.kind == TypeKind::CStruct);
643 }
644
645 #[test]
646 fn test_inference_method_tracking() {
647 let string_memory = b"test string content".to_vec();
649 let guess = UnsafeInferenceEngine::infer_from_bytes(&string_memory, string_memory.len());
650
651 assert_eq!(guess.kind, TypeKind::String);
653 assert_eq!(guess.method, InferenceMethod::Utf8Validation);
654 }
655
656 #[test]
657 fn test_stack_trace_vec_detection() {
658 let memory = vec![0u8; 24];
659 let stack = vec![
660 "alloc::vec::Vec::push".to_string(),
661 "my_app::process".to_string(),
662 ];
663 let guess =
664 UnsafeInferenceEngine::infer_with_context(&memory, 24, Some(&stack), None, None);
665
666 assert!(guess.kind == TypeKind::Vec || guess.kind == TypeKind::Unknown);
668 }
669
670 #[test]
671 fn test_stack_trace_string_detection() {
672 let memory = b"test".to_vec();
673 let stack = vec!["alloc::string::String::push".to_string()];
674 let guess = UnsafeInferenceEngine::infer_with_context(&memory, 4, Some(&stack), None, None);
675
676 assert!(guess.kind == TypeKind::String || guess.kind == TypeKind::Unknown);
678 }
679
680 #[test]
681 fn test_stack_trace_cstring_detection() {
682 let memory = b"hello\0".to_vec();
683 let stack = vec!["std::ffi::c_str::CString::new".to_string()];
684 let guess = UnsafeInferenceEngine::infer_with_context(&memory, 6, Some(&stack), None, None);
685
686 assert!(guess.kind == TypeKind::CString || guess.kind == TypeKind::String);
688 }
689
690 #[test]
691 fn test_stack_trace_ffi_detection() {
692 let memory = vec![0u8; 64];
693 let stack = vec!["libc::malloc".to_string()];
694 let guess =
695 UnsafeInferenceEngine::infer_with_context(&memory, 64, Some(&stack), None, None);
696
697 assert!(
699 guess.kind == TypeKind::CStruct
700 || guess.kind == TypeKind::Buffer
701 || guess.kind == TypeKind::Unknown
702 );
703 }
704
705 #[test]
706 fn test_lifetime_transient_allocation() {
707 let memory = b"test".to_vec();
708 let alloc_time = Some(1000);
709 let dealloc_time = Some(1_000_500); let guess =
711 UnsafeInferenceEngine::infer_with_context(&memory, 4, None, alloc_time, dealloc_time);
712
713 assert!(guess.confidence > 0 || guess.kind == TypeKind::Unknown);
715 }
716
717 #[test]
718 fn test_lifetime_long_lived() {
719 let memory = vec![0u8; 64];
720 let alloc_time = Some(1000);
721 let dealloc_time = Some(15_000_000_000); let guess =
723 UnsafeInferenceEngine::infer_with_context(&memory, 64, None, alloc_time, dealloc_time);
724
725 assert!(guess.confidence > 0 || guess.kind == TypeKind::Unknown);
727 }
728
729 #[test]
730 fn test_combined_stack_and_lifetime() {
731 let memory = vec![0u8; 24];
732 let stack = vec!["alloc::vec::Vec::new".to_string()];
733 let alloc_time = Some(1000);
734 let dealloc_time = Some(1_000_500); let guess = UnsafeInferenceEngine::infer_with_context(
736 &memory,
737 24,
738 Some(&stack),
739 alloc_time,
740 dealloc_time,
741 );
742
743 assert!(guess.confidence > 0 || guess.kind == TypeKind::Unknown);
745 }
746
747 #[test]
748 fn test_inference_record_with_context() {
749 let memory = Some(vec![0u8; 24]);
750 let stack = Some(vec!["alloc::vec::Vec::new".to_string()]);
751 let record =
752 InferenceRecord::with_context(0x1000, 24, memory, stack, Some(1000), Some(2000));
753
754 assert_eq!(record.ptr, 0x1000);
755 assert_eq!(record.size, 24);
756 assert!(record.stack_trace.is_some());
757 assert!(record.alloc_time.is_some());
758 assert!(record.dealloc_time.is_some());
759 }
760}
761
762#[cfg(test)]
764mod real_data_tests {
765 use super::*;
766
767 fn vec_to_memory<T>(v: &Vec<T>) -> Vec<u8> {
769 let ptr = v.as_ptr() as usize;
770 let len = v.len();
771 let cap = v.capacity();
772 let mut memory = vec![0u8; 24];
773 memory[..8].copy_from_slice(&ptr.to_le_bytes());
774 memory[8..16].copy_from_slice(&len.to_le_bytes());
775 memory[16..24].copy_from_slice(&cap.to_le_bytes());
776 memory
777 }
778
779 fn string_to_memory(s: &String) -> Vec<u8> {
781 let ptr = s.as_ptr() as usize;
782 let len = s.len();
783 let cap = s.capacity();
784 let mut memory = vec![0u8; 24];
785 memory[..8].copy_from_slice(&ptr.to_le_bytes());
786 memory[8..16].copy_from_slice(&len.to_le_bytes());
787 memory[16..24].copy_from_slice(&cap.to_le_bytes());
788 memory
789 }
790
791 fn assert_kind_or_abstain(
799 decision: &crate::analysis::unsafe_inference::InferenceDecision,
800 allowed: &[TypeKind],
801 ) {
802 match decision.predicted {
803 Some(kind) => assert!(
804 allowed.contains(&kind),
805 "unexpected confident classification {kind:?}, allowed {allowed:?}"
806 ),
807 None => assert!(
808 decision.abstain_reason.is_some(),
809 "abstaining must always carry a reason"
810 ),
811 }
812 }
813
814 fn box_to_memory<T>(b: &T) -> Vec<u8> {
815 let ptr = b as *const T as usize;
816 let mut memory = vec![0u8; 8];
817 memory[..8].copy_from_slice(&ptr.to_le_bytes());
818 memory
819 }
820
821 #[test]
822 fn test_real_vec_i32() {
823 let v = vec![1i32, 2, 3, 4, 5];
824 let memory = vec_to_memory(&v);
825 let decision = UnsafeInferenceEngine::decide_from_bytes(&memory, 24);
826 assert_kind_or_abstain(&decision, &[TypeKind::Vec, TypeKind::String]);
827 }
828
829 #[test]
830 fn test_real_vec_u8() {
831 let v = vec![1u8, 2, 3, 4, 5, 6, 7, 8];
832 let memory = vec_to_memory(&v);
833 let guess = UnsafeInferenceEngine::infer_from_bytes(&memory, 24);
834
835 let decision = UnsafeInferenceEngine::decide_from_bytes(&memory, 24);
841 assert_kind_or_abstain(&decision, &[TypeKind::Vec, TypeKind::String]);
842 let _ = guess;
843 }
844
845 #[test]
846 fn test_real_string() {
847 let s = String::from("Hello, World!");
848 let memory = string_to_memory(&s);
849 let decision = UnsafeInferenceEngine::decide_from_bytes(&memory, 24);
852 assert_kind_or_abstain(&decision, &[TypeKind::String, TypeKind::Vec]);
853 #[cfg(target_os = "macos")]
856 {
857 let mut memory = memory;
858 memory[..8].copy_from_slice(&0x6000_4342_4140u64.to_le_bytes());
859 let decision = UnsafeInferenceEngine::decide_from_bytes(&memory, 24);
860 assert_kind_or_abstain(&decision, &[TypeKind::String, TypeKind::Vec]);
861 }
862 }
863
864 #[test]
865 fn test_real_string_with_capacity() {
866 let mut s = String::with_capacity(100);
867 s.push_str("Hello");
868 let memory = string_to_memory(&s);
869 let decision = UnsafeInferenceEngine::decide_from_bytes(&memory, 24);
870 assert_kind_or_abstain(&decision, &[TypeKind::Vec, TypeKind::String]);
871 }
872
873 #[test]
874 fn test_real_box_i32() {
875 let b = Box::new(42i32);
876 let memory = box_to_memory(&*b);
877 let decision = UnsafeInferenceEngine::decide_from_bytes(&memory, 8);
882 assert_kind_or_abstain(
883 &decision,
884 &[
885 TypeKind::Pointer,
886 TypeKind::Vec,
887 TypeKind::String,
888 TypeKind::CString,
889 TypeKind::Buffer,
890 ],
891 );
892 }
893
894 #[test]
895 fn test_real_string_content() {
896 let s = "Hello, World! This is a test string for type inference.";
897 let guess = UnsafeInferenceEngine::infer_from_bytes(s.as_bytes(), s.len());
898
899 assert_eq!(guess.kind, TypeKind::String);
900 assert!(guess.confidence >= 90);
901 }
902
903 #[test]
904 fn test_real_cstring_content() {
905 let cstr = std::ffi::CString::new("Hello, C World!").unwrap();
906 let bytes = cstr.as_bytes_with_nul();
907 let guess = UnsafeInferenceEngine::infer_from_bytes(bytes, bytes.len());
908
909 assert_eq!(guess.kind, TypeKind::CString);
910 assert!(guess.confidence >= 70);
911 }
912
913 #[test]
914 fn test_real_binary_data() {
915 let binary: Vec<u8> = (0..=255).collect();
916 let guess = UnsafeInferenceEngine::infer_from_bytes(&binary, binary.len());
917
918 let decision = UnsafeInferenceEngine::decide_from_bytes(&binary, binary.len());
921 assert_kind_or_abstain(&decision, &[TypeKind::Buffer, TypeKind::Vec]);
922 let _ = guess;
923 }
924
925 #[test]
926 fn test_real_zero_filled() {
927 let zeros = vec![0u8; 1024];
928 let guess = UnsafeInferenceEngine::infer_from_bytes(&zeros, 1024);
929
930 assert!(guess.kind == TypeKind::Buffer || guess.kind == TypeKind::CStruct);
932 }
933
934 #[test]
935 fn test_real_vec_with_stack_trace() {
936 let v = vec![1i32, 2, 3, 4, 5];
937 let memory = vec_to_memory(&v);
938 let stack = vec!["alloc::vec::Vec::push".to_string()];
939 let guess =
940 UnsafeInferenceEngine::infer_with_context(&memory, 24, Some(&stack), None, None);
941
942 assert_eq!(guess.kind, TypeKind::Vec);
943 assert!(guess.confidence >= 80);
945 }
946
947 #[test]
948 fn test_real_string_with_stack_trace() {
949 let s = String::from("Hello");
950 let memory = string_to_memory(&s);
951 let stack = vec!["alloc::string::String::push_str".to_string()];
952 let guess =
953 UnsafeInferenceEngine::infer_with_context(&memory, 24, Some(&stack), None, None);
954
955 assert!(guess.kind == TypeKind::String || guess.kind == TypeKind::Vec);
956 }
957
958 #[test]
959 #[cfg(target_os = "macos")]
960 fn test_real_struct_with_pointers() {
961 struct TestStruct {
962 _ptr1: *const u8,
963 _ptr2: *const u8,
964 _value: u64,
965 }
966
967 let s = TestStruct {
968 _ptr1: &0u8,
969 _ptr2: &1u8,
970 _value: 42,
971 };
972
973 let memory = unsafe {
974 std::slice::from_raw_parts(
975 &s as *const TestStruct as *const u8,
976 std::mem::size_of::<TestStruct>(),
977 )
978 };
979
980 let guess = UnsafeInferenceEngine::infer_from_bytes(memory, memory.len());
981
982 assert!(
985 guess.kind == TypeKind::CStruct
986 || guess.kind == TypeKind::Buffer
987 || guess.kind == TypeKind::Pointer
988 || guess.kind == TypeKind::Vec
989 || guess.kind == TypeKind::String
990 || guess.kind == TypeKind::Unknown,
991 "Got {:?}",
992 guess.kind
993 );
994 }
995}