shuriken_bindings/
lib.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4#![allow(improper_ctypes)]
5
6#![allow(dead_code)]
7#![allow(unused_variables)]
8
9pub mod parser;
10pub mod disassembler;
11pub mod analysis;
12pub mod dvm_access_flags;
13
14use std::path::Path;
15use std::ffi::{ CStr, CString };
16
17use crate::parser::{
18    DvmHeader,
19    DvmMethod,
20    DvmClass
21};
22use crate::disassembler::DvmDisassembledMethod;
23use crate::analysis::{
24    DvmStringAnalysis,
25    DvmMethodAnalysis,
26    DvmClassAnalysis
27};
28
29mod shuriken {
30    #[cfg(not(docsrs))]
31    include!(concat!(env!("OUT_DIR"), "/shuriken_core.rs"));
32
33    // Include pre-generated bindings if building on docs.rs
34    #[cfg(docsrs)]
35    include!(".docs.rs/shuriken_core.rs");
36}
37
38/// Type alias for Shuriken's `hDexContext`
39///
40/// This struct also contains caches of raw pointers and of the
41/// different analysis classes.
42#[derive(Debug)]
43pub struct DexContext {
44    ptr: shuriken::hDexContext
45}
46
47// --------------------------- Parser API ---------------------------
48
49impl Drop for DexContext {
50    fn drop(&mut self) {
51        unsafe {
52            shuriken::destroy_dex(self.ptr);
53        }
54    }
55}
56
57impl DexContext {
58    /// Main method from the DEX core API
59    ///
60    /// Parse a DEX file and return a DEX context.
61    /// TODO: make sure we correctly handle non-ascii paths
62    pub fn parse_dex(filepath: &Path) -> Self {
63        let c_str = CString::new(filepath.to_path_buf().into_os_string().into_string().unwrap()).unwrap();
64        let c_world = c_str.as_ptr();
65
66        let ptr = unsafe { shuriken::parse_dex(c_world) };
67
68        Self { ptr }
69    }
70
71    /// Get the number of strings in the DEX file
72    pub fn get_number_of_strings(&self) -> usize {
73        unsafe {
74            shuriken::get_number_of_strings(self.ptr)
75        }
76    }
77
78    /// Get the DEX header
79    pub fn get_header(&self) -> Option<DvmHeader> {
80        let header_ptr = unsafe {
81            shuriken::get_header(self.ptr)
82        };
83
84        match header_ptr.is_null() {
85            true => None,
86            false => unsafe {
87                Some(DvmHeader::from_ptr(*header_ptr))
88            }
89        }
90    }
91
92    /// Get a string given its ID
93    pub fn get_string_by_id(&self, string_id: usize) -> Option<String> {
94        unsafe {
95            let c_string = shuriken::get_string_by_id(self.ptr, string_id);
96            if let Ok(string) = CStr::from_ptr(c_string).to_str() {
97                Some(string.to_owned())
98            } else {
99                None
100            }
101        }
102    }
103
104    /// Get the number of classes in the DEX file
105    pub fn get_number_of_classes(&self) -> usize {
106        unsafe {
107            shuriken::get_number_of_classes(self.ptr).into()
108        }
109    }
110
111    /// Get a class structure given an ID
112    pub fn get_class_by_id(&self, id: u16) -> Option<DvmClass> {
113        let dvm_class_ptr = unsafe { shuriken::get_class_by_id(self.ptr, id) };
114
115        if ! dvm_class_ptr.is_null() {
116            unsafe {
117                Some(DvmClass::from_ptr(*dvm_class_ptr))
118            }
119        } else {
120            None
121        }
122    }
123
124    /// Get a class structure given a class name
125    pub fn get_class_by_name(&self, class_name: &str) -> Option<DvmClass> {
126        let c_str = CString::new(class_name)
127            .expect("CString::new failed");
128
129        let class_ptr = unsafe { shuriken::get_class_by_name(self.ptr, c_str.as_ptr()) };
130        if ! class_ptr.is_null() {
131            unsafe {
132                Some(DvmClass::from_ptr(*class_ptr))
133            }
134        } else {
135            None
136        }
137    }
138
139    /// Get a method structure given a full dalvik name.
140    pub fn get_method_by_name(&self, method_name: &str) -> Option<DvmMethod> {
141        let c_str = CString::new(method_name)
142            .expect("CString::new failed");
143
144        let method_ptr = unsafe { shuriken::get_method_by_name(self.ptr, c_str.as_ptr()) };
145        if ! method_ptr.is_null() {
146            unsafe {
147                Some(DvmMethod::from_ptr(*method_ptr))
148            }
149        } else {
150            None
151        }
152    }
153
154    // --------------------------- Disassembler API ---------------------------
155
156    /// Disassemble a DEX file and generate an internal DexDisassembler
157    pub fn disassemble_dex(&self) {
158        unsafe {
159            shuriken::disassemble_dex(self.ptr)
160        }
161    }
162
163    /// Get a method structure given a full dalvik name.
164    pub fn get_disassembled_method(&self, method_name: &str) -> Option<DvmDisassembledMethod> {
165        let c_str = CString::new(method_name)
166            .expect("CString::new failed");
167
168        let dvm_method = self.get_method_by_name(method_name)
169                             .expect("Cannot find function");
170        let dvm_disas = unsafe { shuriken::get_disassembled_method(self.ptr, c_str.as_ptr()) };
171
172        if ! dvm_disas.is_null() {
173            Some(unsafe { DvmDisassembledMethod::from_dvmdisassembled_method_t(*dvm_disas, dvm_method) })
174        } else {
175            eprintln!("No disassembled method. Did you run `DexContext::disassemble_dex()`?");
176            None
177        }
178    }
179
180    // --------------------------- Analysis API ---------------------------
181
182    /// Create a DEX analysis object inside of &self
183    ///
184    /// Optionally this function can create the cross-refs. In that case the analysis will take longer.
185    /// To obtain the analysis, you must also call [`analyze_classes`](fn.analyze_classes.html)
186    pub fn create_dex_analysis(&self, create_xrefs: bool) {
187        let xrefs = if create_xrefs {
188            1
189        } else {
190            0
191        };
192
193        unsafe {
194            shuriken::create_dex_analysis(self.ptr, xrefs)
195        }
196    }
197
198    /// Analyze the classes, add fields and methods into the classes, optionally create the xrefs
199    pub fn analyze_classes(&self) {
200        unsafe {
201            shuriken::analyze_classes(self.ptr)
202        }
203    }
204
205    /// Obtain a `DvmClassAnalysis` given a `DvmClass`
206    pub fn get_analyzed_class_by_hdvmclass(&self, class: &DvmClass) -> Option<DvmClassAnalysis> {
207        self.get_analyzed_class(class.class_name())
208    }
209
210    /// Obtain a `DvmClassAnalysis` given a class name
211    pub fn get_analyzed_class(&self, class_name: &str) -> Option<DvmClassAnalysis> {
212        let c_str = CString::new(class_name)
213            .expect("CString::new failed");
214
215        let class_analysis_ptr = unsafe {
216            shuriken::get_analyzed_class(self.ptr, c_str.as_ptr())
217        };
218
219        match class_analysis_ptr.is_null() {
220            true => None,
221            false => {
222                let dvm_class_analysis = unsafe { DvmClassAnalysis::from_ptr(*class_analysis_ptr) };
223                Some(dvm_class_analysis)
224            }
225        }
226    }
227
228    /// Obtain one DvmMethodAnalysis given its DvmMethod
229    pub fn get_analyzed_method_by_hdvmmethod(&self, method: &DvmMethod ) -> Option<DvmMethodAnalysis> {
230        self.get_analyzed_method(method.dalvik_name())
231    }
232
233    /// Obtain one DvmMethodAnalysis given its full, demangled name
234    pub fn get_analyzed_method(&self, method_full_name: &str) -> Option<DvmMethodAnalysis> {
235        let c_str = CString::new(method_full_name)
236            .expect("CString::new failed");
237
238        let method_analysis_ptr = unsafe {
239            shuriken::get_analyzed_method(self.ptr, c_str.as_ptr())
240        };
241
242        match method_analysis_ptr.is_null() {
243            true => None,
244            false => {
245                let dvm_method_analysis = unsafe { DvmMethodAnalysis::from_ptr(*method_analysis_ptr) };
246                Some(dvm_method_analysis)
247            }
248        }
249    }
250}
251
252// C - APK part of the CORE API from ShurikenLib
253// --------------------------- Parser API ---------------------------
254
255/// Type alias for Shuriken's `hApkContext`
256#[derive(Debug)]
257pub struct ApkContext {
258    ptr: shuriken::hApkContext
259}
260
261impl Drop for ApkContext {
262    /// Since the context object use dynamic memory this method will properly destroy the object
263    fn drop(&mut self) {
264        unsafe {
265            shuriken::destroy_apk(self.ptr);
266        }
267    }
268}
269
270impl ApkContext {
271    /// main method from the APK Core API it parses the APK file and it retrieves a context object
272    pub fn parse_apk(filepath: &Path, create_xrefs: bool) -> Self {
273        let xrefs = if create_xrefs {
274            1
275        } else {
276            0
277        };
278
279        let c_str = CString::new(filepath.to_path_buf().into_os_string().into_string().unwrap()).unwrap();
280
281        let ptr = unsafe {
282            shuriken::parse_apk(c_str.as_ptr(), xrefs)
283        };
284
285        Self { ptr }
286    }
287
288    /// Get the number of DEX files in an APK
289    ///
290    /// APKs may contain multiple DEX files. This function retrieve the number of DEX files in an APK.
291    pub fn get_number_of_dex_files(&self) -> usize {
292        unsafe { shuriken::get_number_of_dex_files(self.ptr) as usize }
293    }
294
295    /// Given an index, retrieve the name of one of the DEX file
296    pub fn get_dex_file_by_index(&self, idx: usize) -> Option<String> {
297        let str_ptr = unsafe { shuriken::get_dex_file_by_index(self.ptr, idx as u32) };
298
299        match str_ptr.is_null() {
300            true => None,
301            false => if let Ok(string) = unsafe { CStr::from_ptr(str_ptr).to_str() } {
302                Some(string.to_owned())
303            } else {
304                None
305            }
306        }
307    }
308
309    /// Get the number of classes in a DEX file
310    ///
311    /// Every DEX file contains a number of classes. This function retrieves the total number of
312    /// classes in a given DEX file
313    pub fn get_number_of_classes_from_dex(&self, dex_file: &str) -> Option<usize> {
314        let dex_name = CString::new(dex_file)
315            .expect("CString::new() failed");
316
317        match unsafe { shuriken::get_number_of_classes_for_dex_file(self.ptr, dex_name.as_ptr()) } {
318            -1 => None,
319            nb => Some(nb as usize)
320        }
321    }
322
323    /// Retrieve one of the `DvmClass` from a DEX file
324    pub fn get_hdvmclass_from_dex_by_index(&self, dex_file: &str, idx: usize) -> Option<DvmClass> {
325        let dex_name = CString::new(dex_file)
326            .expect("CString::new() failed");
327
328        let ptr = unsafe {
329            shuriken::get_hdvmclass_from_dex_by_index(self.ptr, dex_name.as_ptr(), idx as u32)
330        };
331
332        match ptr.is_null() {
333            true => None,
334            false => unsafe {
335                Some(DvmClass::from_ptr(*ptr))
336            }
337        }
338    }
339
340    /// Get the header of a given DEX file
341    pub fn get_header_from_dex(&self, dex_file: &str) -> Option<DvmHeader> {
342        let dex_name = CString::new(dex_file)
343            .expect("CString::new() failed");
344
345        let header_ptr = unsafe {
346            shuriken::get_header_for_dex_file(self.ptr, dex_name.as_ptr())
347        };
348
349        match header_ptr.is_null() {
350            true => None,
351            false => unsafe {
352                Some(DvmHeader::from_ptr(*header_ptr))
353            }
354        }
355    }
356
357    /// Retrieve the number of strings from a given DEX
358    pub fn get_number_of_strings_from_dex(&self, dex_file: &str) -> Option<usize> {
359        let dex_name = CString::new(dex_file)
360            .expect("CString::new() failed");
361
362        match unsafe { shuriken::get_number_of_strings_from_dex(self.ptr, dex_name.as_ptr()) } {
363            -1 => None,
364            nb => Some(nb as usize)
365        }
366    }
367
368    /// Get a string from a DEX by its index
369    pub fn get_string_by_id_from_dex(&self, dex_file: &str, idx: usize) -> Option<String> {
370        let dex_name = CString::new(dex_file)
371            .expect("CString::new() failed");
372
373        let str_ptr = unsafe {
374            shuriken::get_string_by_id_from_dex(self.ptr, dex_name.as_ptr(), idx as u32)
375        };
376
377        match str_ptr.is_null() {
378            true => None,
379            false => if let Ok(string) = unsafe { CStr::from_ptr(str_ptr).to_str() } {
380                Some(string.to_owned())
381            } else {
382                None
383            }
384        }
385    }
386
387    // --------------------------- Disassembly API ---------------------------
388
389    /// Get a method structure given a full dalvik name.
390    pub fn get_disassembled_method_from_apk(&self, method_name: &str) -> Option<DvmDisassembledMethod> {
391        let method_name = CString::new(method_name)
392            .expect("CString::new() failed");
393
394        let method_ptr = unsafe {
395            shuriken::get_disassembled_method_from_apk(self.ptr, method_name.as_ptr())
396        };
397
398        match method_ptr.is_null() {
399            true => None,
400            false => unsafe {
401                Some(DvmDisassembledMethod::from_ptr(*method_ptr))
402            }
403        }
404    }
405
406    // --------------------------- Analysis API ---------------------------
407
408    /// Obtain one `DvmClassAnalysis` given its `DvmClass`
409    pub fn get_analyzed_class_by_hdvmclass_from_apk(&self, class: &DvmClass) -> Option<DvmClassAnalysis> {
410        self.get_analyzed_class_from_apk(class.class_name())
411    }
412
413    /// Obtain one `DvmClassAnalysis` given its name
414    pub fn get_analyzed_class_from_apk(&self, class_name: &str) -> Option<DvmClassAnalysis> {
415        let class_name = CString::new(class_name)
416            .expect("CString::new() failed");
417
418        let class_ptr = unsafe {
419            shuriken::get_analyzed_class_from_apk(self.ptr, class_name.as_ptr())
420        };
421
422        match class_ptr.is_null() {
423            true => None,
424            false => unsafe {
425                Some(DvmClassAnalysis::from_ptr(*class_ptr))
426            }
427        }
428    }
429
430    /// Obtain one `DvmMethodAnalysis` given its `DvmMethodAnalysis`
431    pub fn get_analyzed_method_by_hdvmmethod_from_apk(&self, method: &DvmMethod) -> Option<DvmMethodAnalysis> {
432        self.get_analyzed_method_from_apk(method.dalvik_name())
433    }
434
435    /// Obtain one `DvmMethodAnalysis` given its name
436    pub fn get_analyzed_method_from_apk(&self, method_full_name: &str) -> Option<DvmMethodAnalysis> {
437        let method_name = CString::new(method_full_name)
438            .expect("CString::new() failed");
439
440        let method_ptr = unsafe {
441            shuriken::get_analyzed_method_from_apk(self.ptr, method_name.as_ptr())
442        };
443
444        match method_ptr.is_null() {
445            true => None,
446            false => unsafe {
447                Some(DvmMethodAnalysis::from_ptr(*method_ptr))
448            }
449        }
450    }
451
452    /// Obtain the number of `DvmMethodAnalysis` objects in the APK
453    pub fn get_number_of_method_analysis_objects(&self) -> usize {
454        unsafe {
455            shuriken::get_number_of_methodanalysis_objects(self.ptr)
456        }
457    }
458
459    /// Obtain a `DvmMethodAnalysis` object from the APK by idx
460    pub fn get_analyzed_method_by_idx(&self, idx: usize) -> Option<DvmMethodAnalysis> {
461        let method_ptr = unsafe {
462            shuriken::get_analyzed_method_by_idx(self.ptr, idx)
463        };
464
465        match method_ptr.is_null() {
466            true => None,
467            false => unsafe {
468                Some(DvmMethodAnalysis::from_ptr(*method_ptr))
469            }
470        }
471    }
472
473    /// Obtain a `DvmStringAnalysis` given a string
474    pub fn get_analyzed_string_from_apk(&self, string: &str) -> Option<DvmStringAnalysis> {
475        let string = CString::new(string)
476            .expect("CString::new() failed");
477
478        let analysis_ptr = unsafe {
479            shuriken::get_analyzed_string_from_apk(self.ptr, string.as_ptr())
480        };
481
482        match analysis_ptr.is_null() {
483            true => None,
484            false => unsafe {
485                Some(DvmStringAnalysis::from_ptr(*analysis_ptr))
486            }
487        }
488    }
489}
490
491#[cfg(test)]
492mod tests {
493    mod dex {
494        use super::super::*;
495
496        use std::fs;
497        use std::path::PathBuf;
498
499        use parser::*;
500        use dvm_access_flags::{ DvmAccessFlag, DvmAccessFlagType };
501
502        const TEST_FILES_PATH: &str = "test_files/";
503
504        #[test]
505        fn test_parse_dex() {
506            let paths = fs::read_dir(TEST_FILES_PATH).unwrap();
507
508            for path in paths {
509                let path = path.unwrap().path();
510
511                // Only testing DEX files
512                if path.extension().unwrap() == "apk" {
513                    continue;
514                }
515
516                let context = DexContext::parse_dex(&path);
517            }
518        }
519
520        #[test]
521        fn test_dex_header() {
522            let path = PathBuf::from("test_files/DexParserTest.dex");
523            let context = DexContext::parse_dex(&path);
524
525            let header = context.get_header();
526            assert!(header.is_some());
527            let header = header.unwrap();
528
529            assert_eq!(header.magic(), &[0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00]);
530            assert_eq!(header.checksum(), 0xe4eefae3);
531            assert_eq!(header.file_size(), 1624);
532            assert_eq!(header.header_size(), 112);
533
534            assert_eq!(header.link_size(), 0);
535            assert_eq!(header.link_off(), 0);
536            assert_eq!(header.string_ids_size(), 33);
537            assert_eq!(header.string_ids_off(), 112);
538            assert_eq!(header.type_ids_size(), 9);
539            assert_eq!(header.type_ids_off(), 244);
540            assert_eq!(header.proto_ids_size(), 7);
541            assert_eq!(header.proto_ids_off(), 280);
542            assert_eq!(header.field_ids_size(), 3);
543            assert_eq!(header.field_ids_off(), 364);
544            assert_eq!(header.method_ids_size(), 10);
545            assert_eq!(header.method_ids_off(), 388);
546            assert_eq!(header.class_defs_size(), 1);
547            assert_eq!(header.class_defs_off(), 468);
548        }
549
550        #[test]
551        fn test_nb_strings() {
552            use std::collections::HashMap;
553
554            let counts = HashMap::from([
555                ("test_files/_pi.dex", 32usize),
556                ("test_files/_null.dex", 23),
557                ("test_files/_float.dex", 71),
558                ("test_files/_test_lifter.dex", 16),
559                ("test_files/_long.dex", 28),
560                ("test_files/_double.dex", 71),
561                ("test_files/DexParserTest.dex", 33),
562                ("test_files/_exception.dex", 31),
563                ("test_files/_cast.dex", 29),
564                ("test_files/test_zip.apk", 0),
565                ("test_files/_loop.dex", 23),
566                ("test_files/TestFieldsLifter.dex", 44),
567                ("test_files/_instance.dex", 28),
568                ("test_files/_switch.dex", 33),
569                ("test_files/_int.dex", 27)
570            ]);
571
572            let paths = fs::read_dir(TEST_FILES_PATH).unwrap();
573
574            for path in paths {
575                let path = path.unwrap().path();
576
577                // Only testing DEX files
578                if path.extension().unwrap() == "apk" {
579                    continue;
580                }
581
582                let context = DexContext::parse_dex(&path);
583                let count = context.get_number_of_strings();
584
585                assert_eq!(count, *counts.get(&path.to_str().unwrap()).unwrap());
586            }
587        }
588
589        #[test]
590        fn test_get_string() {
591            let strings = vec![
592                " and ",
593                " is: ",
594                "<init>",
595                "DexParserTest.java",
596                "Field 1: ",
597                "Field 2: ",
598                "Hello, Dex Parser!",
599                "I",
600                "III",
601                "L",
602                "LDexParserTest;",
603                "LI",
604                "LL",
605                "Ljava/io/PrintStream;",
606                "Ljava/lang/Object;",
607                "Ljava/lang/String;",
608                "Ljava/lang/StringBuilder;",
609                "Ljava/lang/System;",
610                "Sum of ",
611                "This is a test message printed from DexParserTest class.",
612                "V",
613                "VL",
614                "[Ljava/lang/String;",
615                "append",
616                "calculateSum",
617                "field1",
618                "field2",
619                "main",
620                "out",
621                "printMessage",
622                "println",
623                "toString",
624                "~~D8{\"backend\":\"dex\",\"compilation-mode\":\"debug\",\"has-checksums\":false,\"min-api\":1,\"version\":\"3.3.20-dev+aosp5\"}"
625            ];
626
627            let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
628
629            assert_eq!(context.get_number_of_strings(), 33);
630
631            for idx in 0..context.get_number_of_strings() {
632                let string = context.get_string_by_id(idx);
633                assert!(string.is_some());
634                assert_eq!(string.unwrap(), strings[idx]);
635            }
636        }
637
638        #[test]
639        fn test_fields() {
640            use std::collections::HashMap;
641
642            let fields = [HashMap::from([
643                    ("name", "field1"),
644                    ("flags", "2"),
645                    ("type", "I")
646                ]),
647                HashMap::from([
648                    ("name", "field2"),
649                    ("flags", "2"),
650                    ("type", "Ljava/lang/String;"),
651                ])];
652
653            let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
654            let class = context.get_class_by_id(0);
655
656            assert!(class.is_some());
657            let class = class.unwrap();
658
659            assert_eq!(class.class_name(), "DexParserTest");
660            assert_eq!(class.super_class(), "java.lang.Object");
661            assert_eq!(class.source_file(), "DexParserTest.java");
662
663            assert_eq!(class.access_flags(), vec![DvmAccessFlag::ACC_PUBLIC]);
664            assert_eq!(class.instance_fields_size(), 2);
665            assert_eq!(class.static_fields_size(), 0);
666
667            let class_descriptor = String::from("LDexParserTest;");
668            let access_flags = [DvmAccessFlag::ACC_PUBLIC];
669
670            for (idx, field) in class.instance_fields().iter().enumerate() {
671                let access_flags = DvmAccessFlag::parse(
672                    fields[idx]["flags"].parse::<u32>().unwrap(),
673                    DvmAccessFlagType::Field
674                );
675
676                assert_eq!(field.class_name(), class_descriptor);
677                assert_eq!(field.name(), fields[idx]["name"]);
678                assert_eq!(field.access_flags(), access_flags);
679
680                if fields[idx]["type"].starts_with("L") {
681                    assert_eq!(field.field_type(), DexTypes::Class);
682                    assert_eq!(field.fundamental_value(), DexBasicTypes::FundamentalNone);
683                    assert_eq!(field.type_value(), "Ljava/lang/String;");
684                } else {
685                    assert_eq!(field.field_type(), DexTypes::Fundamental);
686                    assert_eq!(field.fundamental_value(), DexBasicTypes::Int);
687                    assert_eq!(field.type_value(), "I");
688                }
689            }
690        }
691
692        #[test]
693        fn test_methods() {
694            use std::collections::HashMap;
695
696            let methods = [HashMap::from([
697                    ("dalvik_name", "LDexParserTest;-><init>()V"),
698                    ("flags", "1"),
699                ]),
700                HashMap::from([
701                    ("dalvik_name", "LDexParserTest;->calculateSum(II)I"),
702                    ("flags", "2"),
703                ]),
704                HashMap::from([
705                    ("dalvik_name", "LDexParserTest;->main([Ljava/lang/String;)V"),
706                    ("flags", "9"),
707                ]),
708                HashMap::from([
709                    ("dalvik_name", "LDexParserTest;->printMessage()V"),
710                    ("flags", "2"),
711                ])];
712
713            let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
714            let class = context.get_class_by_id(0);
715
716            assert!(class.is_some());
717            let class = class.unwrap();
718
719            assert_eq!(class.class_name(), "DexParserTest");
720            assert_eq!(class.super_class(), "java.lang.Object");
721            assert_eq!(class.source_file(), "DexParserTest.java");
722
723            assert_eq!(class.access_flags(), vec![DvmAccessFlag::ACC_PUBLIC]);
724            assert_eq!(class.direct_methods_size(), 4);
725            assert_eq!(class.virtual_methods_size(), 0);
726
727            let class_descriptor = String::from("LDexParserTest;");
728            let access_flags = [DvmAccessFlag::ACC_PUBLIC];
729
730            for (idx, method) in class.direct_methods().iter().enumerate() {
731                let access_flags = DvmAccessFlag::parse(
732                    methods[idx]["flags"].parse::<u32>().unwrap(),
733                    DvmAccessFlagType::Method
734                );
735
736                assert_eq!(method.class_name(), class_descriptor);
737                assert_eq!(method.dalvik_name(), methods[idx]["dalvik_name"]);
738                assert_eq!(method.access_flags(), access_flags);
739            }
740        }
741
742        #[test]
743        fn test_get_class_by_name() {
744            let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
745            let class = context.get_class_by_name("DexParserTest");
746
747            assert!(class.is_some());
748            assert_eq!(class.as_ref().unwrap().class_name(), "DexParserTest");
749            assert_eq!(class.as_ref().unwrap().super_class(), "java.lang.Object");
750            assert_eq!(class.as_ref().unwrap().source_file(), "DexParserTest.java");
751            assert_eq!(class.as_ref().unwrap().access_flags(), vec![DvmAccessFlag::ACC_PUBLIC]);
752        }
753
754        #[test]
755        fn test_get_method_by_name() {
756            let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
757            let method = context.get_method_by_name("LDexParserTest;->printMessage()V");
758
759            assert!(method.is_some());
760            assert_eq!(method.as_ref().unwrap().method_name(), "printMessage");
761            assert_eq!(method.as_ref().unwrap().class_name(), "LDexParserTest;");
762            assert_eq!(method.as_ref().unwrap().prototype(), "()V");
763            assert_eq!(method.as_ref().unwrap().access_flags(), vec![DvmAccessFlag::ACC_PRIVATE]);
764        }
765
766        #[test]
767        fn test_disassemble_dex() {
768            let paths = fs::read_dir(TEST_FILES_PATH).unwrap();
769
770            for path in paths {
771                let path = path.unwrap().path();
772
773                // Only testing DEX files
774                if path.extension().unwrap() == "apk" {
775                    continue;
776                }
777
778                let context = DexContext::parse_dex(&path);
779                context.disassemble_dex();
780            }
781        }
782
783        #[test]
784        fn test_get_disassembled_method() {
785            use std::collections::HashMap;
786
787            let methods = HashMap::from([
788                (
789                    String::from("LDexParserTest;-><init>()V"),
790                    vec![
791                        ".method constructor public LDexParserTest;-><init>()V",
792                        ".registers 2",
793                        "00000000 invoke-direct {v1}, Ljava/lang/Object;-><init>()V // method@5",
794                        "00000006 const/16 v0, 42",
795                        "0000000a iput v0, v1, DexParserTest->field1 int // field@0",
796                        "0000000e const-string v0, \"Hello, Dex Parser!\" // string@6",
797                        "00000012 iput-object v0, v1, DexParserTest->field2 java.lang.String // field@1",
798                        "00000016 return-void",
799                        ".end method"
800                    ]
801                ),
802                (
803                    String::from("LDexParserTest;->calculateSum(II)I"),
804                    vec![
805                        ".method private LDexParserTest;->calculateSum(II)I",
806                        ".registers 7",
807                        "00000000 add-int v0, v5, v6",
808                        "00000004 sget-object v1, java.lang.System->out java.io.PrintStream // field@2",
809                        "00000008 new-instance v2, Ljava/lang/StringBuilder; // type@5",
810                        "0000000c invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V // method@6",
811                        "00000012 const-string v3, \"Sum of \" // string@18",
812                        "00000016 invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
813                        "0000001c move-result-object v2",
814                        "0000001e invoke-virtual {v2, v5}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
815                        "00000024 move-result-object v5",
816                        "00000026 const-string v2, \" and \" // string@0",
817                        "0000002a invoke-virtual {v5, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
818                        "00000030 move-result-object v5",
819                        "00000032 invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
820                        "00000038 move-result-object v5",
821                        "0000003a const-string v6, \" is: \" // string@1",
822                        "0000003e invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
823                        "00000044 move-result-object v5",
824                        "00000046 invoke-virtual {v5, v0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
825                        "0000004c move-result-object v5",
826                        "0000004e invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
827                        "00000054 move-result-object v5",
828                        "00000056 invoke-virtual {v1, v5}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
829                        "0000005c return v0",
830                        ".end method"
831                    ]
832                ),
833                (
834                    String::from("LDexParserTest;->main([Ljava/lang/String;)V"),
835                    vec![
836                        ".method public static LDexParserTest;->main([Ljava/lang/String;)V",
837                        ".registers 3",
838                        "00000000 new-instance v2, LDexParserTest; // type@1",
839                        "00000004 invoke-direct {v2}, LDexParserTest;-><init>()V // method@0",
840                        "0000000a invoke-direct {v2}, LDexParserTest;->printMessage()V // method@3",
841                        "00000010 const/16 v0, 10",
842                        "00000014 const/16 v1, 20",
843                        "00000018 invoke-direct {v2, v0, v1}, LDexParserTest;->calculateSum(II)I // method@1",
844                        "0000001e return-void",
845                        ".end method"
846                    ]
847                ),
848                (
849                    String::from("LDexParserTest;->printMessage()V"),
850                    vec![
851                        ".method private LDexParserTest;->printMessage()V",
852                        ".registers 4",
853                        "00000000 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
854                        "00000004 new-instance v1, Ljava/lang/StringBuilder; // type@5",
855                        "00000008 invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V // method@6",
856                        "0000000e const-string v2, \"Field 1: \" // string@4",
857                        "00000012 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
858                        "00000018 move-result-object v1",
859                        "0000001a iget v2, v3, DexParserTest->field1 int // field@0",
860                        "0000001e invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
861                        "00000024 move-result-object v1",
862                        "00000026 invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
863                        "0000002c move-result-object v1",
864                        "0000002e invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
865                        "00000034 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
866                        "00000038 new-instance v1, Ljava/lang/StringBuilder; // type@5",
867                        "0000003c invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V // method@6",
868                        "00000042 const-string v2, \"Field 2: \" // string@5",
869                        "00000046 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
870                        "0000004c move-result-object v1",
871                        "0000004e iget-object v2, v3, DexParserTest->field2 java.lang.String // field@1",
872                        "00000052 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
873                        "00000058 move-result-object v1",
874                        "0000005a invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
875                        "00000060 move-result-object v1",
876                        "00000062 invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
877                        "00000068 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
878                        "0000006c const-string v1, \"This is a test message printed from DexParserTest class.\" // string@19",
879                        "00000070 invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
880                        "00000076 return-void",
881                        ".end method"
882                    ]
883                )
884            ]);
885
886            let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
887
888            // Check that we get nothing if we have not run `DexContext::disassemble_dex()`
889            let dvm_method = context.get_disassembled_method(
890                "LDexParserTest;->printMessage()V"
891            );
892            assert!(dvm_method.is_none());
893
894            context.disassemble_dex();
895
896            for (method, code) in methods.iter() {
897                let dvm_method = context.get_disassembled_method(method);
898                assert!(dvm_method.is_some());
899
900                let dvm_method = dvm_method.unwrap();
901                assert_eq!(dvm_method.method_string()
902                                     .split("\n")
903                                     .zip(code)
904                                     .filter(|&(a, b)| a != *b)
905                                     .map(|x| println!("{x:?}"))
906                                     .count(),
907                    0
908                );
909            }
910        }
911
912        #[test]
913        fn test_dvm_basic_block() {
914            use std::collections::HashMap;
915
916            let methods = HashMap::from([
917                (
918                    String::from("LDexParserTest;->printMessage()V"),
919                    vec![
920                        "BB.0-120",
921                        "00000000 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
922                        "00000004 new-instance v1, Ljava/lang/StringBuilder; // type@5",
923                        "00000008 invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V // method@6",
924                        "0000000e const-string v2, \"Field 1: \" // string@4",
925                        "00000012 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
926                        "00000018 move-result-object v1",
927                        "0000001a iget v2, v3, DexParserTest->field1 int // field@0",
928                        "0000001e invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
929                        "00000024 move-result-object v1",
930                        "00000026 invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
931                        "0000002c move-result-object v1",
932                        "0000002e invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
933                        "00000034 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
934                        "00000038 new-instance v1, Ljava/lang/StringBuilder; // type@5",
935                        "0000003c invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V // method@6",
936                        "00000042 const-string v2, \"Field 2: \" // string@5",
937                        "00000046 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
938                        "0000004c move-result-object v1",
939                        "0000004e iget-object v2, v3, DexParserTest->field2 java.lang.String // field@1",
940                        "00000052 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
941                        "00000058 move-result-object v1",
942                        "0000005a invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
943                        "00000060 move-result-object v1",
944                        "00000062 invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
945                        "00000068 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
946                        "0000006c const-string v1, \"This is a test message printed from DexParserTest class.\" // string@19",
947                        "00000070 invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
948                        "00000076 return-void",
949                    ]
950                ),
951
952                (
953                    String::from("LDexParserTest;->main([Ljava/lang/String;)V"),
954                    vec![
955                        "BB.0-32",
956                        "00000000 new-instance v2, LDexParserTest; // type@1",
957                        "00000004 invoke-direct {v2}, LDexParserTest;-><init>()V // method@0",
958                        "0000000a invoke-direct {v2}, LDexParserTest;->printMessage()V // method@3",
959                        "00000010 const/16 v0, 10",
960                        "00000014 const/16 v1, 20",
961                        "00000018 invoke-direct {v2, v0, v1}, LDexParserTest;->calculateSum(II)I // method@1",
962                        "0000001e return-void",
963                    ]
964                ),
965
966                (
967                    String::from("LDexParserTest;->calculateSum(II)I"),
968                    vec![
969                        "BB.0-94",
970                        "00000000 add-int v0, v5, v6",
971                        "00000004 sget-object v1, java.lang.System->out java.io.PrintStream // field@2",
972                        "00000008 new-instance v2, Ljava/lang/StringBuilder; // type@5",
973                        "0000000c invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V // method@6",
974                        "00000012 const-string v3, \"Sum of \" // string@18",
975                        "00000016 invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
976                        "0000001c move-result-object v2",
977                        "0000001e invoke-virtual {v2, v5}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
978                        "00000024 move-result-object v5",
979                        "00000026 const-string v2, \" and \" // string@0",
980                        "0000002a invoke-virtual {v5, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
981                        "00000030 move-result-object v5",
982                        "00000032 invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
983                        "00000038 move-result-object v5",
984                        "0000003a const-string v6, \" is: \" // string@1",
985                        "0000003e invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
986                        "00000044 move-result-object v5",
987                        "00000046 invoke-virtual {v5, v0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
988                        "0000004c move-result-object v5",
989                        "0000004e invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
990                        "00000054 move-result-object v5",
991                        "00000056 invoke-virtual {v1, v5}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
992                        "0000005c return v0",
993                    ]
994                ),
995
996                (
997                    String::from("LDexParserTest;-><init>()V"),
998                    vec![
999                        "BB.0-24",
1000                        "00000000 invoke-direct {v1}, Ljava/lang/Object;-><init>()V // method@5",
1001                        "00000006 const/16 v0, 42",
1002                        "0000000a iput v0, v1, DexParserTest->field1 int // field@0",
1003                        "0000000e const-string v0, \"Hello, Dex Parser!\" // string@6",
1004                        "00000012 iput-object v0, v1, DexParserTest->field2 java.lang.String // field@1",
1005                        "00000016 return-void",
1006                    ]
1007                ),
1008            ]);
1009
1010
1011            let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
1012            context.disassemble_dex();
1013            context.create_dex_analysis(true);
1014            context.analyze_classes();
1015
1016            for idx in 0..context.get_number_of_classes() {
1017                let class = context.get_class_by_id(idx as u16);
1018                assert!(class.is_some());
1019                let class = class.unwrap();
1020
1021                let class_name = class.class_name();
1022                assert_eq!(class_name, "DexParserTest");
1023
1024                let class_analysis = context.get_analyzed_class(class_name);
1025                assert!(class_analysis.is_some());
1026                let class_analysis = class_analysis.unwrap();
1027
1028                for method_analysis in class_analysis.methods() {
1029                    let basic_blocks = method_analysis.basic_blocks();
1030
1031                    for (block_idx, block) in basic_blocks.blocks().iter().enumerate() {
1032                        let data = methods.get(method_analysis.full_name());
1033                        assert!(data.is_some());
1034
1035                        let data = data.unwrap();
1036                        assert_eq!(block.block_string()
1037                                        .split("\n")
1038                                        .zip(data)
1039                                        .filter(|&(a, b)| a != *b)
1040                                        .map(|x| println!("{x:?}"))
1041                                        .count(),
1042                            0
1043                        );
1044                    }
1045                }
1046            }
1047        }
1048
1049        #[test]
1050        fn test_get_analyzed_class() {
1051            let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
1052            context.disassemble_dex();
1053            context.create_dex_analysis(true);
1054            context.analyze_classes();
1055
1056            assert_eq!(context.get_number_of_classes(), 1);
1057
1058            let class_analysis = context.get_analyzed_class("DexParserTest");
1059            assert!(class_analysis.is_some());
1060            let class_analysis = class_analysis.unwrap();
1061
1062            let dvm_class = context.get_class_by_name("DexParserTest");
1063            assert!(dvm_class.is_some());
1064            let dvm_class = dvm_class.unwrap();
1065
1066            let class_analysis_by_hdvmclass = context.get_analyzed_class_by_hdvmclass(&dvm_class);
1067            assert!(class_analysis_by_hdvmclass.is_some());
1068            let class_analysis_by_hdvmclass = class_analysis_by_hdvmclass.unwrap();
1069
1070            assert_eq!(class_analysis.is_external(), class_analysis_by_hdvmclass.is_external());
1071            assert_eq!(class_analysis.extends(), class_analysis_by_hdvmclass.extends());
1072            assert_eq!(class_analysis.name(), class_analysis_by_hdvmclass.name());
1073            assert_eq!(class_analysis.n_of_methods(), class_analysis_by_hdvmclass.n_of_methods());
1074            assert_eq!(class_analysis.methods(), class_analysis_by_hdvmclass.methods());
1075            assert_eq!(class_analysis.n_of_fields(), class_analysis_by_hdvmclass.n_of_fields());
1076            assert_eq!(class_analysis.fields(), class_analysis_by_hdvmclass.fields());
1077            assert_eq!(class_analysis.n_of_xrefnewinstance(), class_analysis_by_hdvmclass.n_of_xrefnewinstance());
1078            assert_eq!(class_analysis.xrefnewinstance(), class_analysis_by_hdvmclass.xrefnewinstance());
1079            assert_eq!(class_analysis.n_of_xrefconstclass(), class_analysis_by_hdvmclass.n_of_xrefconstclass());
1080            assert_eq!(class_analysis.xrefconstclass(), class_analysis_by_hdvmclass.xrefconstclass());
1081            assert_eq!(class_analysis.n_of_xrefto(), class_analysis_by_hdvmclass.n_of_xrefto());
1082            assert_eq!(class_analysis.xrefto(), class_analysis_by_hdvmclass.xrefto());
1083            assert_eq!(class_analysis.n_of_xreffrom(), class_analysis_by_hdvmclass.n_of_xreffrom());
1084            assert_eq!(class_analysis.xreffrom(), class_analysis_by_hdvmclass.xreffrom());
1085        }
1086
1087        #[test]
1088        fn test_get_analyzed_method() {
1089            let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
1090            context.disassemble_dex();
1091            context.create_dex_analysis(true);
1092            context.analyze_classes();
1093
1094            let dvm_method = context.get_method_by_name("LDexParserTest;->printMessage()V");
1095            assert!(dvm_method.is_some());
1096            let dvm_method = dvm_method.unwrap();
1097
1098            let method_analysis = context.get_analyzed_method(dvm_method.dalvik_name());
1099            assert!(method_analysis.is_some());
1100            let method_analysis = method_analysis.unwrap();
1101
1102            let method_analysis_by_hdvmmethod = context.get_analyzed_method_by_hdvmmethod(&dvm_method);
1103            assert!(method_analysis_by_hdvmmethod.is_some());
1104            let method_analysis_by_hdvmmethod = method_analysis_by_hdvmmethod.unwrap();
1105
1106            assert_eq!(method_analysis.name(), method_analysis_by_hdvmmethod.name());
1107            assert_eq!(method_analysis.descriptor(), method_analysis_by_hdvmmethod.descriptor());
1108            assert_eq!(method_analysis.full_name(), method_analysis_by_hdvmmethod.full_name());
1109            assert_eq!(method_analysis.external(), method_analysis_by_hdvmmethod.external());
1110            assert_eq!(method_analysis.is_android_api(), method_analysis_by_hdvmmethod.is_android_api());
1111            assert_eq!(method_analysis.access_flags(), method_analysis_by_hdvmmethod.access_flags());
1112            assert_eq!(method_analysis.class_name(), method_analysis_by_hdvmmethod.class_name());
1113            assert_eq!(method_analysis.basic_blocks(), method_analysis_by_hdvmmethod.basic_blocks());
1114            assert_eq!(method_analysis.n_of_xrefread(), method_analysis_by_hdvmmethod.n_of_xrefread());
1115            assert_eq!(method_analysis.xrefread(), method_analysis_by_hdvmmethod.xrefread());
1116            assert_eq!(method_analysis.n_of_xrefwrite(), method_analysis_by_hdvmmethod.n_of_xrefwrite());
1117            assert_eq!(method_analysis.xrefwrite(), method_analysis_by_hdvmmethod.xrefwrite());
1118            assert_eq!(method_analysis.n_of_xrefto(), method_analysis_by_hdvmmethod.n_of_xrefto());
1119            assert_eq!(method_analysis.xrefto(), method_analysis_by_hdvmmethod.xrefto());
1120            assert_eq!(method_analysis.n_of_xreffrom(), method_analysis_by_hdvmmethod.n_of_xreffrom());
1121            assert_eq!(method_analysis.xreffrom(), method_analysis_by_hdvmmethod.xreffrom());
1122            assert_eq!(method_analysis.n_of_xrefnewinstance(), method_analysis_by_hdvmmethod.n_of_xrefnewinstance());
1123            assert_eq!(method_analysis.xrefnewinstance(), method_analysis_by_hdvmmethod.xrefnewinstance());
1124            assert_eq!(method_analysis.n_of_xrefconstclass(), method_analysis_by_hdvmmethod.n_of_xrefconstclass());
1125            assert_eq!(method_analysis.xrefconstclass(), method_analysis_by_hdvmmethod.xrefconstclass());
1126            assert_eq!(method_analysis.method_string(), method_analysis_by_hdvmmethod.method_string());
1127        }
1128    }
1129}