1use std::{
38 collections::{HashMap, HashSet},
39 fmt,
40 time::Duration,
41};
42
43use crate::target::Target;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum EventKind {
49 StringDecrypted,
51 ConstantDecrypted,
53 ConstantFolded,
55 BranchSimplified,
57 InstructionRemoved,
59 BlockRemoved,
61 MethodInlined,
63 PhiSimplified,
65 ValueResolved,
67 MethodMarkedDead,
69 ControlFlowRestructured,
71 OpaquePredicateRemoved,
73 CopyPropagated,
75 ArrayDecrypted,
77 StrengthReduced,
79 VariablesCompacted,
81 MethodBodyDecrypted,
83 ResourceDecrypted,
86 AntiTamperRemoved,
88 ArtifactRemoved,
90 CodeRegenerated,
92 LoadForwarded,
95 DeadStoreRemoved,
98}
99
100impl EventKind {
101 #[must_use]
103 pub fn description(&self) -> &'static str {
104 match self {
105 Self::LoadForwarded => "load forwarded",
106 Self::DeadStoreRemoved => "dead store removed",
107 Self::StringDecrypted => "string decrypted",
108 Self::ConstantDecrypted => "constant decrypted",
109 Self::ConstantFolded => "constant folded",
110 Self::BranchSimplified => "branch simplified",
111 Self::InstructionRemoved => "instruction removed",
112 Self::BlockRemoved => "block removed",
113 Self::MethodInlined => "method inlined",
114 Self::PhiSimplified => "phi simplified",
115 Self::ValueResolved => "value resolved",
116 Self::MethodMarkedDead => "method marked dead",
117 Self::ControlFlowRestructured => "control flow restructured",
118 Self::OpaquePredicateRemoved => "opaque predicate removed",
119 Self::CopyPropagated => "copy propagated",
120 Self::ArrayDecrypted => "array decrypted",
121 Self::StrengthReduced => "strength reduced",
122 Self::VariablesCompacted => "variables compacted",
123 Self::MethodBodyDecrypted => "method body decrypted",
124 Self::ResourceDecrypted => "resource decrypted",
125 Self::AntiTamperRemoved => "anti-tamper removed",
126 Self::ArtifactRemoved => "artifact removed",
127 Self::CodeRegenerated => "code regenerated",
128 }
129 }
130
131 #[must_use]
133 pub fn is_transformation(&self) -> bool {
134 !matches!(self, Self::CodeRegenerated)
135 }
136}
137
138impl fmt::Display for EventKind {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 f.write_str(self.description())
141 }
142}
143
144#[derive(Debug, Clone)]
146pub struct Event<T: Target> {
147 pub kind: EventKind,
149 pub method: Option<T::MethodRef>,
151 pub location: Option<usize>,
153 pub message: String,
155 pub pass: Option<String>,
157}
158
159impl<T: Target> fmt::Display for Event<T> {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 write!(f, "[{}] {}", self.kind, self.message)
162 }
163}
164
165pub trait EventListener<T: Target> {
173 fn push(&self, event: Event<T>);
175
176 fn is_enabled(&self) -> bool {
183 true
184 }
185
186 fn recorded_count(&self) -> usize {
192 0
193 }
194
195 fn count_by_kind_since(&self, offset: usize) -> HashMap<EventKind, usize> {
200 let _ = offset;
201 HashMap::new()
202 }
203
204 fn record(&self, kind: EventKind) -> EventBuilder<'_, T, Self>
208 where
209 Self: Sized,
210 {
211 EventBuilder::new(self, kind)
212 }
213}
214
215#[derive(Debug, Default, Clone, Copy)]
221pub struct NullListener;
222
223impl<T: Target> EventListener<T> for NullListener {
224 fn push(&self, _event: Event<T>) {}
225
226 fn is_enabled(&self) -> bool {
227 false
228 }
229}
230
231pub struct EventBuilder<'a, T: Target, L: EventListener<T> + ?Sized> {
235 listener: &'a L,
236 kind: EventKind,
237 method: Option<T::MethodRef>,
238 location: Option<usize>,
239 message: Option<String>,
240 pass: Option<String>,
241}
242
243impl<'a, T: Target, L: EventListener<T> + ?Sized> EventBuilder<'a, T, L> {
244 fn new(listener: &'a L, kind: EventKind) -> Self {
245 Self {
246 listener,
247 kind,
248 method: None,
249 location: None,
250 message: None,
251 pass: None,
252 }
253 }
254
255 pub fn at(mut self, method: impl Into<T::MethodRef>, location: usize) -> Self {
259 self.method = Some(method.into());
260 self.location = Some(location);
261 self
262 }
263
264 pub fn method(mut self, method: impl Into<T::MethodRef>) -> Self {
266 self.method = Some(method.into());
267 self
268 }
269
270 pub fn location(mut self, location: usize) -> Self {
272 self.location = Some(location);
273 self
274 }
275
276 pub fn message(mut self, msg: impl Into<String>) -> Self {
278 self.message = Some(msg.into());
279 self
280 }
281
282 pub fn pass(mut self, pass_name: impl Into<String>) -> Self {
284 self.pass = Some(pass_name.into());
285 self
286 }
287}
288
289impl<T: Target, L: EventListener<T> + ?Sized> Drop for EventBuilder<'_, T, L> {
290 fn drop(&mut self) {
291 if !self.listener.is_enabled() {
294 return;
295 }
296
297 let message = self
298 .message
299 .take()
300 .unwrap_or_else(|| self.kind.description().to_string());
301
302 let event = Event {
303 kind: self.kind,
304 method: self.method.take(),
305 location: self.location.take(),
306 message,
307 pass: self.pass.take(),
308 };
309
310 self.listener.push(event);
311 }
312}
313
314pub struct EventLog<T: Target> {
319 events: boxcar::Vec<Event<T>>,
320}
321
322impl<T: Target> Default for EventLog<T> {
323 fn default() -> Self {
324 Self {
325 events: boxcar::Vec::new(),
326 }
327 }
328}
329
330impl<T: Target> fmt::Debug for EventLog<T> {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 f.debug_struct("EventLog")
333 .field("len", &self.len())
334 .finish()
335 }
336}
337
338impl<T: Target> Clone for EventLog<T> {
339 fn clone(&self) -> Self {
340 let new_log = Self::new();
341 for (_, event) in &self.events {
342 new_log.events.push(event.clone());
343 }
344 new_log
345 }
346}
347
348impl<T: Target> EventListener<T> for EventLog<T> {
349 fn push(&self, event: Event<T>) {
350 self.events.push(event);
351 }
352
353 fn recorded_count(&self) -> usize {
354 self.len()
355 }
356
357 fn count_by_kind_since(&self, offset: usize) -> HashMap<EventKind, usize> {
358 Self::count_by_kind_since(self, offset)
359 }
360}
361
362impl<T: Target> EventLog<T> {
363 #[must_use]
365 pub fn new() -> Self {
366 Self::default()
367 }
368
369 pub fn record(&self, kind: EventKind) -> EventBuilder<'_, T, Self> {
374 EventBuilder::new(self, kind)
375 }
376
377 #[must_use]
379 pub fn is_empty(&self) -> bool {
380 self.events.count() == 0
381 }
382
383 #[must_use]
385 pub fn len(&self) -> usize {
386 self.events.count()
387 }
388
389 pub fn merge(&self, other: &EventLog<T>) {
391 for (_, event) in &other.events {
392 self.events.push(event.clone());
393 }
394 }
395
396 #[must_use]
398 pub fn has(&self, kind: EventKind) -> bool {
399 self.events.iter().any(|(_, e)| e.kind == kind)
400 }
401
402 #[must_use]
404 pub fn has_any(&self, kinds: &[EventKind]) -> bool {
405 self.events.iter().any(|(_, e)| kinds.contains(&e.kind))
406 }
407
408 #[must_use]
410 pub fn count_kind(&self, kind: EventKind) -> usize {
411 self.events.iter().filter(|(_, e)| e.kind == kind).count()
412 }
413
414 pub fn iter(&self) -> impl Iterator<Item = &Event<T>> {
416 self.events.iter().map(|(_, e)| e)
417 }
418
419 pub fn filter_kind(&self, kind: EventKind) -> impl Iterator<Item = &Event<T>> + '_ {
421 self.events
422 .iter()
423 .filter_map(move |(_, e)| if e.kind == kind { Some(e) } else { None })
424 }
425
426 #[must_use]
432 pub fn take(&self) -> EventLog<T> {
433 self.clone()
434 }
435
436 #[must_use]
444 pub fn into_events(self) -> Vec<Event<T>> {
445 self.events.into_iter().collect()
446 }
447
448 pub fn filter_method<'a>(
450 &'a self,
451 method: &'a T::MethodRef,
452 ) -> impl Iterator<Item = &'a Event<T>> + 'a {
453 self.events.iter().filter_map(move |(_, e)| {
454 if e.method.as_ref() == Some(method) {
455 Some(e)
456 } else {
457 None
458 }
459 })
460 }
461
462 pub fn transformations(&self) -> impl Iterator<Item = &Event<T>> + '_ {
464 self.events.iter().filter_map(|(_, e)| {
465 if e.kind.is_transformation() {
466 Some(e)
467 } else {
468 None
469 }
470 })
471 }
472
473 #[must_use]
475 pub fn count_by_kind(&self) -> HashMap<EventKind, usize> {
476 let mut counts: HashMap<EventKind, usize> = HashMap::new();
477 for (_, event) in &self.events {
478 let entry = counts.entry(event.kind).or_insert(0);
479 *entry = entry.saturating_add(1);
480 }
481 counts
482 }
483
484 #[must_use]
489 pub fn count_by_kind_since(&self, offset: usize) -> HashMap<EventKind, usize> {
490 let mut counts: HashMap<EventKind, usize> = HashMap::new();
491 for (idx, event) in &self.events {
492 if idx >= offset {
493 let entry = counts.entry(event.kind).or_insert(0);
494 *entry = entry.saturating_add(1);
495 }
496 }
497 counts
498 }
499
500 #[must_use]
502 pub fn transformation_count(&self) -> usize {
503 self.events
504 .iter()
505 .filter(|(_, e)| e.kind.is_transformation())
506 .count()
507 }
508
509 #[must_use]
511 pub fn methods_affected(&self) -> usize {
512 self.events
513 .iter()
514 .filter_map(|(_, e)| e.method.as_ref())
515 .collect::<HashSet<_>>()
516 .len()
517 }
518
519 #[must_use]
521 pub fn summary(&self) -> String {
522 if self.is_empty() {
523 return "no events".to_string();
524 }
525
526 let counts = self.count_by_kind();
527
528 let mut parts: Vec<String> = counts
529 .iter()
530 .filter(|(k, _)| k.is_transformation())
531 .map(|(kind, count)| format!("{} {}", count, kind.description()))
532 .collect();
533
534 if parts.is_empty() {
535 return format!("{} events", self.len());
536 }
537
538 parts.sort();
539 parts.join(", ")
540 }
541}
542
543pub struct EventLogIter<'a, T: Target> {
545 inner: boxcar::Iter<'a, Event<T>>,
546}
547
548impl<'a, T: Target> Iterator for EventLogIter<'a, T> {
549 type Item = &'a Event<T>;
550
551 fn next(&mut self) -> Option<Self::Item> {
552 self.inner.next().map(|(_, e)| e)
553 }
554}
555
556impl<'a, T: Target> IntoIterator for &'a EventLog<T> {
557 type Item = &'a Event<T>;
558 type IntoIter = EventLogIter<'a, T>;
559
560 fn into_iter(self) -> Self::IntoIter {
561 EventLogIter {
562 inner: self.events.iter(),
563 }
564 }
565}
566
567impl<T: Target> Extend<Event<T>> for EventLog<T> {
568 fn extend<I: IntoIterator<Item = Event<T>>>(&mut self, iter: I) {
569 for event in iter {
570 self.events.push(event);
571 }
572 }
573}
574
575impl<T: Target> FromIterator<Event<T>> for EventLog<T> {
576 fn from_iter<I: IntoIterator<Item = Event<T>>>(iter: I) -> Self {
577 let log = Self::new();
578 for event in iter {
579 log.events.push(event);
580 }
581 log
582 }
583}
584
585#[derive(Debug, Clone, Default)]
588pub struct DerivedStats {
589 pub methods_transformed: usize,
591 pub strings_decrypted: usize,
593 pub arrays_decrypted: usize,
595 pub constants_folded: usize,
597 pub constants_decrypted: usize,
599 pub instructions_removed: usize,
601 pub blocks_removed: usize,
603 pub branches_simplified: usize,
605 pub opaque_predicates_removed: usize,
607 pub methods_inlined: usize,
609 pub methods_marked_dead: usize,
611 pub methods_regenerated: usize,
613 pub artifacts_removed: usize,
615 pub loads_forwarded: usize,
617 pub dead_stores_removed: usize,
619 pub iterations: usize,
621 pub total_time: Duration,
623}
624
625impl DerivedStats {
626 #[must_use]
628 pub fn from_log<T: Target>(log: &EventLog<T>) -> Self {
629 let counts = log.count_by_kind();
630 let get = |kind: EventKind| counts.get(&kind).copied().unwrap_or(0);
631
632 Self {
633 methods_transformed: log.methods_affected(),
634 strings_decrypted: get(EventKind::StringDecrypted),
635 arrays_decrypted: get(EventKind::ArrayDecrypted),
636 constants_folded: get(EventKind::ConstantFolded),
637 constants_decrypted: get(EventKind::ConstantDecrypted),
638 instructions_removed: get(EventKind::InstructionRemoved),
639 blocks_removed: get(EventKind::BlockRemoved),
640 branches_simplified: get(EventKind::BranchSimplified),
641 opaque_predicates_removed: get(EventKind::OpaquePredicateRemoved),
642 methods_inlined: get(EventKind::MethodInlined),
643 methods_marked_dead: get(EventKind::MethodMarkedDead),
644 methods_regenerated: get(EventKind::CodeRegenerated),
645 artifacts_removed: get(EventKind::ArtifactRemoved),
646 loads_forwarded: get(EventKind::LoadForwarded),
647 dead_stores_removed: get(EventKind::DeadStoreRemoved),
648 iterations: 0,
649 total_time: Duration::ZERO,
650 }
651 }
652
653 #[must_use]
655 pub fn with_time(mut self, time: Duration) -> Self {
656 self.total_time = time;
657 self
658 }
659
660 #[must_use]
662 pub fn with_iterations(mut self, iterations: usize) -> Self {
663 self.iterations = iterations;
664 self
665 }
666
667 #[must_use]
669 pub fn summary(&self) -> String {
670 let mut parts = Vec::new();
671
672 if self.methods_transformed > 0 {
673 parts.push(format!("{} methods", self.methods_transformed));
674 }
675
676 if self.strings_decrypted > 0 {
677 parts.push(format!("{} strings decrypted", self.strings_decrypted));
678 }
679 if self.arrays_decrypted > 0 {
680 parts.push(format!("{} arrays decrypted", self.arrays_decrypted));
681 }
682 if self.constants_decrypted > 0 {
683 parts.push(format!("{} constants decrypted", self.constants_decrypted));
684 }
685
686 if self.constants_folded > 0 {
687 parts.push(format!("{} constants folded", self.constants_folded));
688 }
689 if self.instructions_removed > 0 {
690 parts.push(format!(
691 "{} instructions removed",
692 self.instructions_removed
693 ));
694 }
695 if self.blocks_removed > 0 {
696 parts.push(format!("{} blocks removed", self.blocks_removed));
697 }
698 if self.branches_simplified > 0 {
699 parts.push(format!("{} branches simplified", self.branches_simplified));
700 }
701 if self.methods_inlined > 0 {
702 parts.push(format!("{} inlined", self.methods_inlined));
703 }
704 if self.opaque_predicates_removed > 0 {
705 parts.push(format!(
706 "{} opaque predicates",
707 self.opaque_predicates_removed
708 ));
709 }
710
711 if self.methods_marked_dead > 0 {
712 parts.push(format!("{} dead methods", self.methods_marked_dead));
713 }
714 if self.methods_regenerated > 0 {
715 parts.push(format!("{} regenerated", self.methods_regenerated));
716 }
717 if self.artifacts_removed > 0 {
718 parts.push(format!("{} artifacts removed", self.artifacts_removed));
719 }
720
721 let stats = if parts.is_empty() {
722 "no transformations".to_string()
723 } else {
724 parts.join(", ")
725 };
726
727 if self.total_time.as_millis() > 0 {
728 format!(
729 "{} in {:?} ({} iterations)",
730 stats, self.total_time, self.iterations
731 )
732 } else {
733 stats
734 }
735 }
736}
737
738impl fmt::Display for DerivedStats {
739 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
740 f.write_str(&self.summary())
741 }
742}
743
744#[must_use]
746pub fn truncate_string(s: &str, max_len: usize) -> String {
747 if s.len() <= max_len {
748 s.to_string()
749 } else {
750 let end = max_len.saturating_sub(3);
751 let split = s
752 .char_indices()
753 .map(|(i, _)| i)
754 .take_while(|&i| i <= end)
755 .last()
756 .unwrap_or(0);
757 format!("{}...", &s[..split])
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use std::{sync::Arc, thread};
764
765 use super::*;
766 use crate::testing::MockTarget;
767
768 type Method = <MockTarget as Target>::MethodRef;
769
770 fn method(id: u32) -> Method {
771 id
772 }
773
774 #[test]
775 fn empty_log() {
776 let log: EventLog<MockTarget> = EventLog::new();
777 assert!(log.is_empty());
778 assert_eq!(log.len(), 0);
779 assert!(!log.has(EventKind::StringDecrypted));
780 }
781
782 #[test]
783 fn record_event() {
784 let log: EventLog<MockTarget> = EventLog::new();
785 let m = method(0x06000001);
786
787 log.record(EventKind::StringDecrypted)
788 .at(m, 0x10)
789 .message("decrypted: \"hello\"");
790
791 assert!(!log.is_empty());
792 assert_eq!(log.len(), 1);
793 assert!(log.has(EventKind::StringDecrypted));
794
795 let event = log.iter().next().unwrap();
796 assert_eq!(event.method, Some(m));
797 assert_eq!(event.location, Some(0x10));
798 assert_eq!(event.message, "decrypted: \"hello\"");
799 }
800
801 #[test]
802 fn null_listener_discards() {
803 let listener = NullListener;
804 let m = method(0x06000001);
805
806 EventListener::<MockTarget>::record(&listener, EventKind::StringDecrypted)
807 .at(m, 0x10)
808 .message("dropped on the floor");
809
810 }
813
814 #[test]
815 fn multiple_events() {
816 let log: EventLog<MockTarget> = EventLog::new();
817 let m = method(0x06000001);
818
819 log.record(EventKind::StringDecrypted)
820 .at(m, 0x10)
821 .message("first");
822 log.record(EventKind::ConstantFolded)
823 .at(m, 0x20)
824 .message("second");
825
826 assert_eq!(log.len(), 2);
827 assert!(log.has(EventKind::StringDecrypted));
828 assert!(log.has(EventKind::ConstantFolded));
829 assert!(!log.has(EventKind::BlockRemoved));
830 }
831
832 #[test]
833 fn has_any() {
834 let log: EventLog<MockTarget> = EventLog::new();
835 log.record(EventKind::StringDecrypted)
836 .at(method(0x06000001), 0x10);
837
838 assert!(log.has_any(&[EventKind::StringDecrypted, EventKind::ArrayDecrypted]));
839 assert!(!log.has_any(&[EventKind::BlockRemoved, EventKind::MethodInlined]));
840 }
841
842 #[test]
843 fn merge() {
844 let log1: EventLog<MockTarget> = EventLog::new();
845 let log2: EventLog<MockTarget> = EventLog::new();
846 let m = method(0x06000001);
847
848 log1.record(EventKind::StringDecrypted).at(m, 0x10);
849 log2.record(EventKind::ConstantFolded).at(m, 0x20);
850
851 log1.merge(&log2);
852
853 assert_eq!(log1.len(), 2);
854 assert!(log1.has(EventKind::StringDecrypted));
855 assert!(log1.has(EventKind::ConstantFolded));
856 }
857
858 #[test]
859 fn summary() {
860 let log: EventLog<MockTarget> = EventLog::new();
861 let m = method(0x06000001);
862
863 log.record(EventKind::StringDecrypted).at(m, 0x10);
864 log.record(EventKind::StringDecrypted).at(m, 0x20);
865 log.record(EventKind::ConstantFolded).at(m, 0x30);
866
867 let summary = log.summary();
868 assert!(summary.contains("2 string decrypted"));
869 assert!(summary.contains("1 constant folded"));
870 }
871
872 #[test]
873 fn count_by_kind() {
874 let log: EventLog<MockTarget> = EventLog::new();
875 let m = method(0x06000001);
876
877 log.record(EventKind::StringDecrypted).at(m, 0x10);
878 log.record(EventKind::StringDecrypted).at(m, 0x20);
879 log.record(EventKind::ConstantFolded).at(m, 0x30);
880
881 let counts = log.count_by_kind();
882 assert_eq!(counts.get(&EventKind::StringDecrypted), Some(&2));
883 assert_eq!(counts.get(&EventKind::ConstantFolded), Some(&1));
884 assert_eq!(counts.get(&EventKind::BlockRemoved), None);
885 }
886
887 #[test]
888 fn count_by_kind_since() {
889 let log: EventLog<MockTarget> = EventLog::new();
890 let m = method(0x06000001);
891
892 log.record(EventKind::StringDecrypted).at(m, 0x10);
893 log.record(EventKind::StringDecrypted).at(m, 0x20);
894
895 let offset = log.len();
896
897 log.record(EventKind::ConstantFolded).at(m, 0x30);
898 log.record(EventKind::ConstantFolded).at(m, 0x40);
899 log.record(EventKind::StringDecrypted).at(m, 0x50);
900
901 let counts = log.count_by_kind_since(offset);
902 assert_eq!(counts.get(&EventKind::ConstantFolded), Some(&2));
903 assert_eq!(counts.get(&EventKind::StringDecrypted), Some(&1));
904 assert_eq!(counts.get(&EventKind::BlockRemoved), None);
905
906 let all = log.count_by_kind_since(0);
907 assert_eq!(all.get(&EventKind::StringDecrypted), Some(&3));
908 assert_eq!(all.get(&EventKind::ConstantFolded), Some(&2));
909 }
910
911 #[test]
912 fn derived_stats() {
913 let log: EventLog<MockTarget> = EventLog::new();
914 let m1 = method(0x06000001);
915 let m2 = method(0x06000002);
916
917 log.record(EventKind::StringDecrypted).at(m1, 0x10);
918 log.record(EventKind::StringDecrypted).at(m2, 0x20);
919 log.record(EventKind::ConstantFolded).at(m1, 0x30);
920
921 let stats = DerivedStats::from_log(&log);
922 assert_eq!(stats.methods_transformed, 2);
923 assert_eq!(stats.strings_decrypted, 2);
924 assert_eq!(stats.constants_folded, 1);
925 }
926
927 #[test]
928 fn filter_methods() {
929 let log: EventLog<MockTarget> = EventLog::new();
930 let m1 = method(0x06000001);
931 let m2 = method(0x06000002);
932
933 log.record(EventKind::StringDecrypted).at(m1, 0x10);
934 log.record(EventKind::ConstantFolded).at(m2, 0x20);
935 log.record(EventKind::BlockRemoved).at(m1, 0x30);
936
937 let m1_events: Vec<_> = log.filter_method(&m1).collect();
938 assert_eq!(m1_events.len(), 2);
939 }
940
941 #[test]
942 fn transformations_filter() {
943 let log: EventLog<MockTarget> = EventLog::new();
944 let m = method(0x06000001);
945
946 log.record(EventKind::StringDecrypted).at(m, 0x10);
947 log.record(EventKind::BlockRemoved).at(m, 0x20);
948
949 let transformations: Vec<_> = log.transformations().collect();
950 assert_eq!(transformations.len(), 2);
951 }
952
953 #[test]
954 fn event_with_pass() {
955 let log: EventLog<MockTarget> = EventLog::new();
956 let m = method(0x06000001);
957
958 log.record(EventKind::ConstantFolded)
959 .at(m, 0x10)
960 .pass("ConstantFolding")
961 .message("42 + 0 → 42");
962
963 let event = log.iter().next().unwrap();
964 assert_eq!(event.pass.as_deref(), Some("ConstantFolding"));
965 }
966
967 #[test]
968 fn default_message() {
969 let log: EventLog<MockTarget> = EventLog::new();
970 let m = method(0x06000001);
971
972 log.record(EventKind::StringDecrypted).at(m, 0x10);
973
974 let event = log.iter().next().unwrap();
975 assert_eq!(event.message, "string decrypted");
976 }
977
978 #[test]
979 fn thread_safe_append() {
980 let log: Arc<EventLog<MockTarget>> = Arc::new(EventLog::new());
981 let mut handles = vec![];
982
983 for i in 0..4u32 {
984 let log_clone = Arc::clone(&log);
985 handles.push(thread::spawn(move || {
986 for j in 0..100u32 {
987 let m = method(
988 0x06000000u32
989 .saturating_add(i.saturating_mul(100))
990 .saturating_add(j),
991 );
992 log_clone
993 .record(EventKind::StringDecrypted)
994 .at(m, j as usize)
995 .message(format!("thread {i} event {j}"));
996 }
997 }));
998 }
999
1000 for handle in handles {
1001 handle.join().unwrap();
1002 }
1003
1004 assert_eq!(log.len(), 400);
1005 }
1006
1007 #[test]
1008 fn into_events_moves_without_cloning() {
1009 let log: EventLog<MockTarget> = EventLog::new();
1010 log.record(EventKind::StringDecrypted)
1011 .at(method(0x0600_0001), 0)
1012 .message("a");
1013 log.record(EventKind::ConstantFolded)
1014 .at(method(0x0600_0002), 1)
1015 .message("b");
1016 let events = log.into_events();
1017 assert_eq!(events.len(), 2);
1018 assert_eq!(events[0].kind, EventKind::StringDecrypted);
1019 assert_eq!(events[1].kind, EventKind::ConstantFolded);
1020 }
1021
1022 #[test]
1023 fn null_listener_is_disabled() {
1024 let null = NullListener;
1025 assert!(!EventListener::<MockTarget>::is_enabled(&null));
1026 EventListener::<MockTarget>::record(&null, EventKind::StringDecrypted)
1028 .at(method(0x0600_0003), 0)
1029 .message("ignored");
1030 }
1031
1032 #[test]
1033 fn truncate_string_short() {
1034 assert_eq!(truncate_string("hi", 10), "hi");
1035 }
1036
1037 #[test]
1038 fn truncate_string_long() {
1039 let result = truncate_string("hello world", 8);
1040 assert!(result.ends_with("..."));
1041 assert!(result.len() <= 8);
1042 }
1043}