├── .gitignore ├── resources └── classes.dex ├── examples ├── classes.rs └── simple.rs ├── Cargo.toml ├── src ├── cache.rs ├── source.rs ├── error.rs ├── lib.rs ├── search.rs ├── utils.rs ├── encoded_item.rs ├── field.rs ├── jtype.rs ├── string.rs ├── method.rs ├── code.rs ├── annotation.rs ├── class.rs ├── encoded_value.rs └── dex.rs ├── .travis.yml ├── README.md └── tests └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.swp 5 | *.swo 6 | /.idea 7 | -------------------------------------------------------------------------------- /resources/classes.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letmutx/dex-parser/HEAD/resources/classes.dex -------------------------------------------------------------------------------- /examples/classes.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use dex::DexReader; 4 | 5 | fn main() -> io::Result<()> { 6 | env_logger::init(); 7 | let dex = DexReader::from_file("resources/classes.dex").unwrap(); 8 | for class in dex.classes() { 9 | let class = class.expect("Class failed"); 10 | println!("class name: {:?}", class.jtype()); 11 | } 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use dex::DexReader; 2 | 3 | use std::io; 4 | 5 | fn main() -> io::Result<()> { 6 | env_logger::init(); 7 | let dex = DexReader::from_file("resources/classes.dex").unwrap(); 8 | let class = dex 9 | .find_class_by_name("Lorg/adw/launcher/Launcher;") 10 | .expect("Failed to load class") 11 | .expect("class not found"); 12 | println!("class type: {}", class.jtype()); 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dex" 3 | version = "0.5.0" 4 | authors = ["Rohit Kumar "] 5 | edition = "2018" 6 | description = "Rust library for parsing dex files" 7 | repository = "https://github.com/letmutx/dex-parser.git" 8 | license = "MIT" 9 | readme = "README.md" 10 | keywords = ["dex", "android", "parser", "dexlib"] 11 | 12 | [dependencies] 13 | cesu8 = "1.1.0" 14 | scroll = "0.9.0" 15 | scroll_derive = "0.9.2" 16 | lru = "0.12.3" 17 | memmap2 = "0.9.4" 18 | num-traits = "0.2" 19 | num-derive = "0.2" 20 | bitflags = "1.2.1" 21 | log = "0.4.8" 22 | getset = "0.0.9" 23 | adler32 = "1.0.4" 24 | 25 | [dev-dependencies] 26 | tempfile = "3.0.8" 27 | env_logger = "0.11.3" 28 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, cmp::Eq, hash::Hash, num::NonZeroUsize, rc::Rc}; 2 | 3 | use lru::LruCache; 4 | 5 | /// LRU cache that provides interior mutability 6 | #[derive(Debug)] 7 | pub(crate) struct Cache { 8 | inner: Rc>>, 9 | } 10 | 11 | impl Cache { 12 | /// Get a new instance of cache with the given capacity 13 | pub(crate) fn new(cap: NonZeroUsize) -> Self { 14 | Self { 15 | inner: Rc::new(RefCell::new(LruCache::new(cap))), 16 | } 17 | } 18 | 19 | /// Get a reference to the value at key from the cache, if found 20 | pub(crate) fn get(&self, key: &K) -> Option { 21 | self.inner 22 | .borrow_mut() 23 | .get(key) 24 | .map(std::clone::Clone::clone) 25 | } 26 | 27 | /// Insert a new key value pair into the cache 28 | pub(crate) fn put(&self, key: K, value: V) { 29 | self.inner.borrow_mut().put(key, value); 30 | } 31 | } 32 | 33 | impl Clone for Cache { 34 | fn clone(&self) -> Self { 35 | Self { 36 | inner: self.inner.clone(), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/source.rs: -------------------------------------------------------------------------------- 1 | use std::{clone::Clone, convert::AsRef, ops::Index, rc::Rc}; 2 | 3 | use crate::ubyte; 4 | 5 | /// Represents the source `Dex` file. This is a 6 | /// wrapper type that allows for shallow copies 7 | /// of the dex file's source. 8 | #[derive(Debug)] 9 | pub(crate) struct Source { 10 | inner: Rc, 11 | } 12 | 13 | impl Source 14 | where 15 | T: AsRef<[u8]>, 16 | { 17 | pub(crate) fn new(inner: T) -> Self { 18 | Self { 19 | inner: Rc::new(inner), 20 | } 21 | } 22 | } 23 | 24 | impl Index for Source 25 | where 26 | T: AsRef<[u8]>, 27 | { 28 | type Output = ubyte; 29 | 30 | fn index(&self, index: usize) -> &Self::Output { 31 | &self.as_ref()[index] 32 | } 33 | } 34 | 35 | impl Index> for Source 36 | where 37 | T: AsRef<[u8]>, 38 | { 39 | type Output = [ubyte]; 40 | 41 | fn index(&self, index: std::ops::Range) -> &Self::Output { 42 | &self.as_ref()[index] 43 | } 44 | } 45 | 46 | impl Index> for Source 47 | where 48 | T: AsRef<[u8]>, 49 | { 50 | type Output = [ubyte]; 51 | 52 | fn index(&self, index: std::ops::RangeFrom) -> &Self::Output { 53 | &self.as_ref()[index] 54 | } 55 | } 56 | 57 | impl Clone for Source { 58 | fn clone(&self) -> Self { 59 | Self { 60 | inner: self.inner.clone(), 61 | } 62 | } 63 | } 64 | 65 | impl> AsRef<[u8]> for Source { 66 | fn as_ref(&self) -> &[ubyte] { 67 | self.inner.as_ref().as_ref() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: oraclejdk8 3 | dist: trusty 4 | 5 | before_cache: 6 | # Do not cache a few Gradle files/directories (see https://docs.travis-ci.com/user/languages/java/#Caching) 7 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 8 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 9 | - cargo sweep --file 10 | - rm -rf "$TRAVIS_HOME/.cargo/registry/src" 11 | 12 | before_script: 13 | - cargo install cargo-sweep --force 14 | - cargo sweep --stamp 15 | 16 | cache: 17 | directories: 18 | # Android SDK 19 | - $HOME/android-sdk-dl 20 | - $HOME/android-sdk 21 | 22 | # cargo 23 | - $HOME/.cargo 24 | - $TRAVIS_BUILD_DIR/target 25 | 26 | 27 | install: 28 | # Download and unzip the Android SDK tools (if not already there thanks to the cache mechanism) 29 | # Latest version available here: https://developer.android.com/studio/#command-tools 30 | - curl -sSf https://build.travis-ci.org/files/rustup-init.sh | sh -s -- --default-toolchain=stable --profile=minimal -y 31 | - rustup update 32 | - if test ! -e $HOME/android-sdk-dl/sdk-tools.zip ; then curl https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip > $HOME/android-sdk-dl/sdk-tools.zip ; fi 33 | - unzip -qq -n $HOME/android-sdk-dl/sdk-tools.zip -d $HOME/android-sdk 34 | 35 | # Install or update Android SDK components (will not do anything if already up to date thanks to the cache mechanism) 36 | - echo y | $HOME/android-sdk/tools/bin/sdkmanager 'build-tools;29.0.2' > /dev/null 37 | - echo y | $HOME/android-sdk/tools/bin/sdkmanager 'platforms;android-29' > /dev/null 38 | 39 | env: 40 | - ANDROID_HOME="$HOME/android-sdk" ANDROID_LIB_PATH="$HOME/android-sdk/platforms/android-29/android.jar" PATH="$TRAVIS_HOME/.cargo/bin:$HOME/android-sdk/build-tools/29.0.2/:$PATH" 41 | 42 | script: cargo test --verbose --all 43 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error, 3 | fmt::{self, Display}, 4 | io, 5 | }; 6 | 7 | use scroll; 8 | 9 | #[derive(Debug)] 10 | pub enum Error { 11 | MalFormed(String), 12 | IO(io::Error), 13 | InvalidId(String), 14 | Scroll(scroll::Error), 15 | BadOffset(usize, String), 16 | } 17 | 18 | impl error::Error for Error { 19 | fn description(&self) -> &str { 20 | match *self { 21 | Error::IO(_) => "IO error", 22 | Error::MalFormed(_) => "Entity is malformed in some way", 23 | Error::Scroll(_) => "Scroll error", 24 | Error::InvalidId(_) => "Invalid index", 25 | Error::BadOffset(_, _) => "Invalid offset", 26 | } 27 | } 28 | 29 | fn cause(&self) -> Option<&error::Error> { 30 | match *self { 31 | Error::IO(ref io) => io.source(), 32 | Error::Scroll(ref err) => err.source(), 33 | Error::MalFormed(_) => None, 34 | Error::InvalidId(_) => None, 35 | Error::BadOffset(_, _) => None, 36 | } 37 | } 38 | } 39 | 40 | impl From for Error { 41 | fn from(err: io::Error) -> Error { 42 | Error::IO(err) 43 | } 44 | } 45 | 46 | impl From for Error { 47 | fn from(err: scroll::Error) -> Error { 48 | Error::Scroll(err) 49 | } 50 | } 51 | 52 | impl Display for Error { 53 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 54 | match *self { 55 | Error::IO(ref err) => write!(fmt, "{}", err), 56 | Error::Scroll(ref err) => write!(fmt, "{}", err), 57 | Error::MalFormed(ref msg) => write!(fmt, "Malformed entity: {}", msg), 58 | Error::InvalidId(ref msg) => write!(fmt, "{}", msg), 59 | Error::BadOffset(offset, ref msg) => write!(fmt, "{}: {}", msg, offset), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Dex is a library for reading Android's 2 | //! [dex](https://source.android.com/devices/tech/dalvik/dex-format) file format. 3 | // Silence warnings in error module for now 4 | #![allow(bare_trait_objects)] 5 | 6 | pub extern crate scroll; 7 | 8 | #[macro_use] 9 | extern crate scroll_derive; 10 | 11 | #[macro_use] 12 | extern crate bitflags; 13 | 14 | #[macro_use] 15 | extern crate log; 16 | 17 | extern crate getset; 18 | 19 | pub use error::Error; 20 | 21 | pub use crate::dex::{Dex, DexReader, Header}; 22 | 23 | #[macro_use] 24 | mod utils; 25 | pub mod annotation; 26 | mod cache; 27 | pub mod class; 28 | pub mod code; 29 | mod dex; 30 | mod encoded_item; 31 | pub mod encoded_value; 32 | mod error; 33 | pub mod field; 34 | pub mod jtype; 35 | pub mod method; 36 | mod search; 37 | mod source; 38 | pub mod string; 39 | 40 | /// The constant NO_INDEX is used to indicate that an index value is absent. 41 | pub const NO_INDEX: uint = 0xffff_ffff; 42 | const ENDIAN_CONSTANT: (ubyte, ubyte, ubyte, ubyte) = (0x12, 0x34, 0x56, 0x78); 43 | const REVERSE_ENDIAN_CONSTANT: (ubyte, ubyte, ubyte, ubyte) = (0x78, 0x56, 0x34, 0x12); 44 | 45 | /// 8-bit signed int 46 | #[allow(non_camel_case_types)] 47 | pub type byte = i8; 48 | /// 32-bit unsigned int 49 | #[allow(non_camel_case_types)] 50 | pub type uint = u32; 51 | /// 32-bit signed int 52 | #[allow(non_camel_case_types)] 53 | pub type int = i32; 54 | /// 16-bit unsigned int 55 | #[allow(non_camel_case_types)] 56 | pub type ushort = u16; 57 | /// 16-bit signed int 58 | #[allow(non_camel_case_types)] 59 | pub type short = i16; 60 | /// 8-bit unsigned int 61 | #[allow(non_camel_case_types)] 62 | pub type ubyte = u8; 63 | /// 64-bit unsigned int 64 | #[allow(non_camel_case_types)] 65 | pub type ulong = u64; 66 | /// 64-bit signed int 67 | #[allow(non_camel_case_types)] 68 | pub type long = i64; 69 | 70 | /// A `Result` of `T` or an error of `error::Error` 71 | pub type Result = std::result::Result; 72 | 73 | // ref. https://source.android.com/devices/tech/dalvik/dex-format 74 | 75 | /// The endianness of bytes. 76 | pub type Endian = scroll::Endian; 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dex 2 | 3 | [![Build Status](https://api.travis-ci.org/letmutx/dex-parser.svg?branch=master)](https://travis-ci.org/letmutx/dex-parser) 4 | 5 | Dex is a parser for Android's [Dex](https://source.android.com/devices/tech/dalvik/dex-format) format written completely in Rust. 6 | 7 | Most of the functionality to access the data structures in the file is implemented. Test coverage stands at 81% as of v0.3.0 8 | 9 | ## Usage 10 | Add to your `Cargo.toml`: 11 | ``` 12 | dex = "0.4.0" 13 | ``` 14 | 15 | ## Documentation 16 | The primary source of documentation for dex format is [Android website](https://source.android.com/devices/tech/dalvik/dex-format). Most of the public `struct`s, and `method`s in this crate have the same names. There are a few examples [here](https://github.com/letmutx/dex-parser/tree/master/examples/) to get you started. 17 | 18 | ## Development Notes 19 | * The library makes use of [`mmap`](https://en.wikipedia.org/wiki/Mmap) to access the file contents. 20 | * [scroll](https://crates.io/crates/scroll) is used to parse binary data. 21 | * The included `classes.dex` in the resources folder is from the open-source application [ADW launcher](https://f-droid.org/en/packages/org.adw.launcher/). You can find the source code [here](https://f-droid.org/repo/org.adw.launcher_34_src.tar.gz) 22 | 23 | ## Running test cases 24 | Some tests contains Java code and require `javac` and [d8](https://developer.android.com/studio/command-line/d8). The other option is to open a PR and test using the travis setup. 25 | 26 | * To get `d8`, you need to install Android SDK and add `Android/Sdk/build-tools//` directory to PATH variable. 27 | * For `javac`, you need to install Java. 28 | * Also, `ANDROID_LIB_PATH` variable needs to be set in the environment. It should point to the `android.jar` file in the SDK. (ex: `Android/Sdk/platforms/android-/android.jar`). This is needed to prevent warnings when running `d8`. 29 | * Use `cargo install cargo-tarpaulin` and run `cargo tarpaulin` to get test coverage. 30 | 31 | 32 | ## Contributing 33 | All contributions are welcome! Feel free to raise issues/PRs on Github if you find a bug, have a question or think something can be improved! Please add a test when you open an PR! 34 | -------------------------------------------------------------------------------- /src/search.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | use scroll::{ctx, Pread}; 3 | use std::{cmp::Ordering, fmt::Debug}; 4 | 5 | pub(crate) struct Section<'a> { 6 | inner: &'a [u8], 7 | } 8 | 9 | impl<'a> Section<'a> { 10 | pub(crate) fn new(inner: &'a [u8]) -> Self { 11 | Section { inner } 12 | } 13 | 14 | /// Binary search the contents of this section. 15 | /// * The items in the section should be of fixed size. 16 | /// * The items must be sorted in the order that predicate expects. 17 | pub(crate) fn binary_search<'b, F, T, S, C: Copy>( 18 | &self, 19 | element: &'b S, 20 | ctx: C, 21 | predicate: F, 22 | ) -> Result> 23 | where 24 | S: std::fmt::Debug, 25 | F: Fn(&T, &S) -> Result, 26 | T: ctx::TryFromCtx<'a, C, Size = usize, Error = scroll::Error> + Debug, 27 | { 28 | if self.inner.is_empty() { 29 | return Ok(None); 30 | } 31 | // Figure out the size of one item, all items must be of fixed size 32 | let mut size = 0; 33 | let _: T = self.inner.gread_with(&mut size, ctx)?; 34 | // Number of elements = Size of buffer / Item size 35 | let len = self.inner.len() / size; 36 | debug!(target: "binary-search", "binary-search: item size: {}, buffer length: {}, array length: {}, element: {:?}", 37 | size, self.inner.len(), len, *element); 38 | let (mut start, mut end) = (0, len - 1); 39 | while start < end { 40 | let mid = start + (end - start) / 2; 41 | let mid_offset = mid * size; 42 | let item = self.inner.pread_with(mid_offset, ctx)?; 43 | let result = predicate(&item, element)?; 44 | debug!(target: "binary-search", "binary-search: index: {}, item: {:?}, result: {:?}", mid, item, result); 45 | match result { 46 | Ordering::Equal => { 47 | debug!(target: "binary-search", "binary search: success! index: {}", mid); 48 | return Ok(Some(mid)); 49 | } 50 | Ordering::Less => end = mid - 1, 51 | Ordering::Greater => start = mid + 1, 52 | } 53 | } 54 | let start_offset = start * size; 55 | let item = self.inner.pread_with(start_offset, ctx)?; 56 | Ok(if predicate(&item, element)? == Ordering::Equal { 57 | debug!(target: "binary-search", "binary search: success! index: {}", start); 58 | Some(start) 59 | } else { 60 | None 61 | }) 62 | } 63 | } 64 | 65 | impl<'a> AsRef<[u8]> for Section<'a> { 66 | fn as_ref(&self) -> &[u8] { 67 | self.inner 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | annotation::AnnotationSetItem, 3 | encoded_item::{EncodedItem, EncodedItemArray}, 4 | encoded_value::EncodedValue, 5 | error::Error, 6 | jtype::{Type, TypeId}, 7 | ushort, 8 | }; 9 | 10 | macro_rules! try_gread_vec_with { 11 | ($source:ident,$offset:ident,$cap:expr,$ctx:expr) => {{ 12 | let cap = $cap as usize; 13 | let ctx = $ctx; 14 | let mut vec = Vec::with_capacity(cap); 15 | // NOTE: gread_inout_with doesn't work when de-serializing encoded array 16 | // so using an explicit loop here. 17 | for _ in 0..cap { 18 | vec.push($source.gread_with($offset, ctx)?); 19 | } 20 | vec 21 | }}; 22 | } 23 | 24 | macro_rules! encoded_array { 25 | ($source:ident,$dex:ident,$offset:ident,$size:expr) => { 26 | if $size > 0 { 27 | let encoded_array_ctx = EncodedItemArrayCtx::new($dex, $size as usize); 28 | Some($source.gread_with($offset, encoded_array_ctx)?) 29 | } else { 30 | None 31 | } 32 | }; 33 | } 34 | 35 | pub(crate) fn from_item( 36 | array: Option>, 37 | f: F, 38 | ) -> Option>> 39 | where 40 | F: FnMut(T) -> super::Result, 41 | T: EncodedItem, 42 | { 43 | array.map(|array| array.iter().map(f).collect()) 44 | } 45 | 46 | macro_rules! try_from_item { 47 | ($array:expr,$closure:expr) => {{ 48 | use crate::utils::from_item; 49 | match from_item($array, $closure) { 50 | Some(v) => v?, 51 | None => Default::default(), 52 | } 53 | }}; 54 | } 55 | 56 | pub(crate) fn get_types(dex: &super::Dex, type_ids: &[ushort]) -> super::Result> 57 | where 58 | S: AsRef<[u8]>, 59 | { 60 | type_ids 61 | .iter() 62 | .map(|type_id| dex.get_type(TypeId::from(*type_id))) 63 | .collect() 64 | } 65 | 66 | macro_rules! gen_is_flag_set { 67 | ($name: ident, $flag: ident) => { 68 | /// Returns `true` if the access flag is set 69 | pub fn $name(&self) -> bool { 70 | self.access_flags().contains(AccessFlags::$flag) 71 | } 72 | } 73 | } 74 | 75 | pub(crate) fn get_signature(annotations: &AnnotationSetItem) -> super::Result> { 76 | annotations 77 | .iter() 78 | .find(|item| item.jtype() == "Ldalvik/annotation/Signature;") 79 | .map(|item| { 80 | let element = item.annotation().find_element("value"); 81 | if element.is_none() { 82 | return Err(Error::MalFormed( 83 | "Expected element with name value, but not found".to_string(), 84 | )); 85 | } 86 | let element = element.unwrap(); 87 | match *element.value() { 88 | EncodedValue::Array(ref v) => { 89 | let signature: super::Result = v 90 | .iter() 91 | .map(|s| { 92 | if let EncodedValue::String(ref v) = s { 93 | Ok(v.to_string()) 94 | } else { 95 | Err(Error::MalFormed(format!( 96 | "Expected string element in signature, found: {:?}", 97 | s 98 | ))) 99 | } 100 | }) 101 | .collect(); 102 | Ok(Some(signature?)) 103 | } 104 | ref e => Err(Error::MalFormed(format!("Expected array, found: {:?}", e))), 105 | } 106 | }) 107 | .unwrap_or_else(|| Ok(None)) 108 | } 109 | -------------------------------------------------------------------------------- /src/encoded_item.rs: -------------------------------------------------------------------------------- 1 | use scroll::{ctx, Pread, Sleb128, Uleb128}; 2 | use std::ops::Deref; 3 | 4 | use getset::Getters; 5 | 6 | use crate::{ 7 | code::{CatchHandler, ExceptionType}, 8 | error::Error, 9 | jtype::TypeId, 10 | uint, ulong, ushort, 11 | }; 12 | 13 | pub trait EncodedItem { 14 | /// Returns the id of the encoded item. 15 | fn id(&self) -> ulong; 16 | } 17 | 18 | #[derive(Getters)] 19 | #[get = "pub"] 20 | pub struct EncodedItemArray { 21 | inner: Vec, 22 | } 23 | 24 | impl Deref for EncodedItemArray { 25 | type Target = Vec; 26 | 27 | fn deref(&self) -> &Self::Target { 28 | &self.inner 29 | } 30 | } 31 | 32 | impl EncodedItemArray { 33 | pub(crate) fn iter(self) -> impl Iterator { 34 | self.inner.into_iter() 35 | } 36 | } 37 | 38 | pub(crate) struct EncodedItemArrayCtx<'a, S: AsRef<[u8]>> { 39 | dex: &'a super::Dex, 40 | len: usize, 41 | } 42 | 43 | impl<'a, S: AsRef<[u8]>> EncodedItemArrayCtx<'a, S> { 44 | pub(crate) fn new(dex: &'a super::Dex, len: usize) -> Self { 45 | Self { dex, len } 46 | } 47 | } 48 | 49 | impl<'a, S: AsRef<[u8]>> Copy for EncodedItemArrayCtx<'a, S> {} 50 | 51 | impl<'a, S: AsRef<[u8]>> Clone for EncodedItemArrayCtx<'a, S> { 52 | fn clone(&self) -> Self { 53 | Self { 54 | dex: self.dex, 55 | len: self.len, 56 | } 57 | } 58 | } 59 | 60 | impl<'a, S, T: 'a> ctx::TryFromCtx<'a, EncodedItemArrayCtx<'a, S>> for EncodedItemArray 61 | where 62 | S: AsRef<[u8]>, 63 | T: EncodedItem + ctx::TryFromCtx<'a, ulong, Size = usize, Error = Error>, 64 | { 65 | type Error = Error; 66 | type Size = usize; 67 | 68 | fn try_from_ctx( 69 | source: &'a [u8], 70 | ctx: EncodedItemArrayCtx<'a, S>, 71 | ) -> super::Result<(Self, Self::Size)> { 72 | let len = ctx.len; 73 | let mut prev = 0; 74 | let offset = &mut 0; 75 | let mut inner = Vec::with_capacity(len); 76 | for _ in 0..len { 77 | let encoded_item: T = source.gread_with(offset, prev)?; 78 | prev = encoded_item.id(); 79 | inner.push(encoded_item); 80 | } 81 | Ok((EncodedItemArray { inner }, *offset)) 82 | } 83 | } 84 | 85 | #[derive(Debug)] 86 | pub(crate) struct EncodedCatchHandlers { 87 | inner: Vec<(usize, EncodedCatchHandler)>, 88 | } 89 | 90 | impl EncodedCatchHandlers { 91 | pub(crate) fn iter(&self) -> impl Iterator { 92 | self.inner.iter() 93 | } 94 | 95 | pub(crate) fn find(&self, handler_offset: ushort) -> Option<&EncodedCatchHandler> { 96 | self.iter() 97 | .find(|p| p.0 == handler_offset as usize) 98 | .map(|p| &p.1) 99 | } 100 | } 101 | 102 | #[derive(Debug)] 103 | pub(crate) struct EncodedCatchHandler { 104 | handlers: Vec, 105 | } 106 | 107 | impl EncodedCatchHandler { 108 | pub(crate) fn handlers(&self) -> Vec { 109 | self.handlers.to_vec() 110 | } 111 | } 112 | 113 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for EncodedCatchHandler 114 | where 115 | S: AsRef<[u8]>, 116 | { 117 | type Error = crate::error::Error; 118 | type Size = usize; 119 | 120 | fn try_from_ctx(source: &'a [u8], dex: &super::Dex) -> super::Result<(Self, Self::Size)> { 121 | let offset = &mut 0; 122 | let size = Sleb128::read(source, offset)?; 123 | let type_addr_pairs: Vec = 124 | try_gread_vec_with!(source, offset, size.abs(), ()); 125 | let mut handlers: Vec = type_addr_pairs 126 | .into_iter() 127 | .map(|type_addr_pair| { 128 | Ok(CatchHandler { 129 | exception: ExceptionType::Ty(dex.get_type(type_addr_pair.type_id)?), 130 | addr: type_addr_pair.addr, 131 | }) 132 | }) 133 | .collect::>()?; 134 | if size <= 0 { 135 | let all_handler_addr = Uleb128::read(source, offset)?; 136 | handlers.push(CatchHandler { 137 | exception: ExceptionType::BaseException, 138 | addr: all_handler_addr as ulong, 139 | }); 140 | } 141 | Ok((Self { handlers }, *offset)) 142 | } 143 | } 144 | 145 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for EncodedCatchHandlers 146 | where 147 | S: AsRef<[u8]>, 148 | { 149 | type Error = crate::error::Error; 150 | type Size = usize; 151 | 152 | fn try_from_ctx(source: &'a [u8], dex: &super::Dex) -> super::Result<(Self, Self::Size)> { 153 | let offset = &mut 0; 154 | let encoded_handler_size = Uleb128::read(source, offset)?; 155 | let mut encoded_catch_handlers = Vec::with_capacity(encoded_handler_size as usize); 156 | for _ in 0..encoded_handler_size { 157 | let off = *offset; 158 | let encoded_catch_handler = source.gread_with(offset, dex)?; 159 | encoded_catch_handlers.push((off, encoded_catch_handler)); 160 | } 161 | Ok(( 162 | Self { 163 | inner: encoded_catch_handlers, 164 | }, 165 | *offset, 166 | )) 167 | } 168 | } 169 | 170 | #[derive(Copy, Clone, Debug)] 171 | pub(crate) struct EncodedTypeAddrPair { 172 | pub(crate) type_id: TypeId, 173 | pub(crate) addr: ulong, 174 | } 175 | 176 | impl<'a> ctx::TryFromCtx<'a, ()> for EncodedTypeAddrPair { 177 | type Error = crate::error::Error; 178 | type Size = usize; 179 | 180 | fn try_from_ctx(source: &'a [u8], _: ()) -> super::Result<(Self, Self::Size)> { 181 | let offset = &mut 0; 182 | let type_id = Uleb128::read(source, offset)?; 183 | let addr = Uleb128::read(source, offset)?; 184 | Ok(( 185 | Self { 186 | type_id: type_id as uint, 187 | addr, 188 | }, 189 | *offset, 190 | )) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/field.rs: -------------------------------------------------------------------------------- 1 | //! Dex `Field` and supporting structures 2 | use scroll::{ctx, Pread, Uleb128}; 3 | 4 | use crate::{ 5 | annotation::AnnotationSetItem, 6 | class::ClassId, 7 | encoded_item::{EncodedItem, EncodedItemArray}, 8 | encoded_value::EncodedValue, 9 | error::Error, 10 | jtype::{Type, TypeId}, 11 | string::{DexString, StringId}, 12 | ulong, ushort, utils, 13 | }; 14 | use getset::{CopyGetters, Getters}; 15 | 16 | bitflags! { 17 | /// Access flags of a Field. 18 | pub struct AccessFlags: ulong { 19 | const PUBLIC = 0x1; 20 | const PRIVATE = 0x2; 21 | const PROTECTED = 0x4; 22 | const STATIC = 0x8; 23 | const FINAL = 0x10; 24 | const VOLATILE = 0x40; 25 | const TRANSIENT = 0x80; 26 | const SYNTHETIC = 0x1000; 27 | const ENUM = 0x4000; 28 | } 29 | } 30 | 31 | /// Represents the field of a class 32 | #[derive(Debug, Getters, CopyGetters)] 33 | pub struct Field { 34 | /// Name of the field. 35 | #[get = "pub"] 36 | name: DexString, 37 | /// Type of the field. 38 | #[get = "pub"] 39 | jtype: Type, 40 | /// Class which this field belongs to. 41 | #[get_copy = "pub"] 42 | class: ClassId, 43 | /// Access flags for the field. 44 | #[get_copy = "pub"] 45 | access_flags: AccessFlags, 46 | /// Initial value of the field. Always `None` for non-static fields. 47 | /// If the value is `None`, it is not guaranteed that initial_value is `null` 48 | /// at runtime. The field might be initialized in `` method. 49 | initial_value: Option, 50 | /// Annotations of the field. 51 | #[get = "pub"] 52 | annotations: AnnotationSetItem, 53 | /// `FieldId` of the field. 54 | #[get_copy = "pub"] 55 | id: FieldId, 56 | } 57 | 58 | impl Field { 59 | /// Initial value of the field. Always `None` for non-static fields. 60 | /// If the value is `None`, it is not guaranteed that initial_value is `null` 61 | /// at runtime. The field might be initialized in `` method. 62 | pub fn initial_value(&self) -> Option<&EncodedValue> { 63 | self.initial_value.as_ref() 64 | } 65 | 66 | gen_is_flag_set!(is_public, PUBLIC); 67 | gen_is_flag_set!(is_private, PRIVATE); 68 | gen_is_flag_set!(is_protected, PROTECTED); 69 | gen_is_flag_set!(is_static, STATIC); 70 | gen_is_flag_set!(is_final, FINAL); 71 | gen_is_flag_set!(is_volatile, VOLATILE); 72 | gen_is_flag_set!(is_transient, TRANSIENT); 73 | gen_is_flag_set!(is_synthetic, SYNTHETIC); 74 | gen_is_flag_set!(is_enum, ENUM); 75 | 76 | /// Returns the value of `dalvik.annotation.Signature`. 77 | pub fn signature(&self) -> super::Result> { 78 | utils::get_signature(self.annotations()) 79 | } 80 | 81 | pub(crate) fn try_from_dex>( 82 | dex: &super::Dex, 83 | encoded_field: &EncodedField, 84 | initial_value: Option, 85 | annotations: AnnotationSetItem, 86 | ) -> super::Result { 87 | debug!(target: "field", "encoded field: {:?}", encoded_field); 88 | let field_item = dex.get_field_item(encoded_field.field_id)?; 89 | debug!(target: "field", "field id item: {:?}", field_item); 90 | Ok(Self { 91 | name: dex.get_string(field_item.name_idx)?, 92 | jtype: dex.get_type(TypeId::from(field_item.type_idx))?, 93 | class: ClassId::from(field_item.class_idx), 94 | access_flags: AccessFlags::from_bits(encoded_field.access_flags).ok_or_else(|| { 95 | Error::InvalidId(format!( 96 | "Invalid access flags when loading field {}", 97 | field_item.name_idx 98 | )) 99 | })?, 100 | initial_value, 101 | annotations, 102 | id: encoded_field.field_id, 103 | }) 104 | } 105 | } 106 | 107 | /// List of `EncodedField`s 108 | pub type EncodedFieldArray = EncodedItemArray; 109 | 110 | #[derive(Pread)] 111 | struct FieldIdData { 112 | class_idx: ushort, 113 | type_idx: ushort, 114 | name_idx: StringId, 115 | } 116 | 117 | /// Defines a `Field` 118 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#field-id-item) 119 | #[derive(Debug, CopyGetters, PartialEq)] 120 | #[get_copy = "pub"] 121 | pub struct FieldIdItem { 122 | /// Index into `TypeId`s list which contains the defining class's `Type`. 123 | class_idx: ushort, 124 | /// Index into `TypeId`s list which contains the `Type` of the field. 125 | type_idx: ushort, 126 | /// Index into `StringId`s list which contains the name of the field. 127 | name_idx: StringId, 128 | /// `FieldId` of this field. 129 | id: FieldId, 130 | } 131 | 132 | impl FieldIdItem { 133 | pub(crate) fn try_from_dex>( 134 | dex: &super::Dex, 135 | offset: ulong, 136 | field_id: FieldId, 137 | ) -> super::Result { 138 | let source = &dex.source; 139 | let field: FieldIdData = source.pread_with(offset as usize, dex.get_endian())?; 140 | Ok(FieldIdItem { 141 | class_idx: field.class_idx, 142 | type_idx: field.type_idx, 143 | name_idx: field.name_idx, 144 | id: field_id, 145 | }) 146 | } 147 | } 148 | 149 | /// Index into the `FieldId`s list. 150 | pub type FieldId = ulong; 151 | 152 | /// Contains a `FieldId` along with its access flags. 153 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#encoded-field-format) 154 | #[derive(Debug, CopyGetters)] 155 | #[get_copy = "pub"] 156 | pub struct EncodedField { 157 | /// Index into the `FieldId`s list for the identity of this field represented as 158 | /// a difference from the index of previous element in the list. 159 | pub(crate) field_id: FieldId, 160 | /// Access flags for the field. 161 | access_flags: ulong, 162 | } 163 | 164 | impl EncodedItem for EncodedField { 165 | fn id(&self) -> ulong { 166 | self.field_id 167 | } 168 | } 169 | 170 | impl<'a> ctx::TryFromCtx<'a, ulong> for EncodedField { 171 | type Error = Error; 172 | type Size = usize; 173 | 174 | fn try_from_ctx(source: &'a [u8], prev_id: ulong) -> super::Result<(Self, Self::Size)> { 175 | let offset = &mut 0; 176 | let id = Uleb128::read(source, offset)?; 177 | let access_flags = Uleb128::read(source, offset)?; 178 | Ok(( 179 | Self { 180 | field_id: prev_id + id, 181 | access_flags, 182 | }, 183 | *offset, 184 | )) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/jtype.rs: -------------------------------------------------------------------------------- 1 | //! Dex `Type` and utilities 2 | use std::{clone::Clone, fmt}; 3 | 4 | use getset::{CopyGetters, Getters}; 5 | 6 | use crate::{string::DexString, uint}; 7 | 8 | /// Dex representation of a boolean type 9 | pub const BOOLEAN: &'static str = "Z"; 10 | /// Dex representation of a byte type 11 | pub const BYTE: &'static str = "B"; 12 | /// Dex representation of a short type 13 | pub const SHORT: &'static str = "S"; 14 | /// Dex representation of a char type 15 | pub const CHAR: &'static str = "C"; 16 | /// Dex representation of an integer type 17 | pub const INT: &'static str = "I"; 18 | /// Dex representation of a long type 19 | pub const LONG: &'static str = "J"; 20 | /// Dex representation of a float type 21 | pub const FLOAT: &'static str = "F"; 22 | /// Dex representation of a double type 23 | pub const DOUBLE: &'static str = "D"; 24 | /// Dex representation of a void type 25 | pub const VOID: &'static str = "V"; 26 | 27 | /// Offset into the `TypeId`s section. 28 | pub type TypeId = uint; 29 | 30 | /// Represents a Java type. The type descriptor conforms to 31 | /// the syntax described [here](https://source.android.com/devices/tech/dalvik/dex-format#typedescriptor) 32 | #[derive(Debug, Getters, CopyGetters)] 33 | pub struct Type { 34 | #[get_copy = "pub"] 35 | pub(crate) id: TypeId, 36 | /// The type descriptor string for this string. 37 | #[get = "pub"] 38 | pub(crate) type_descriptor: DexString, 39 | } 40 | 41 | macro_rules! gen_is_type_method { 42 | ($func_name: ident, $descriptor: ident, $doc: literal) => { 43 | #[doc = $doc] 44 | pub fn $func_name(&self) -> bool { 45 | self.type_descriptor == $descriptor 46 | } 47 | } 48 | } 49 | 50 | impl Type { 51 | /// Returns `true` if the type is primitive 52 | pub fn is_primitive(&self) -> bool { 53 | self.is_bool() 54 | || self.is_byte() 55 | || self.is_short() 56 | || self.is_char() 57 | || self.is_int() 58 | || self.is_long() 59 | || self.is_float() 60 | || self.is_double() 61 | || self.is_void() 62 | } 63 | 64 | /// Returns `true` if the type is an array or a class 65 | pub fn is_reference(&self) -> bool { 66 | self.is_array() || self.is_class() 67 | } 68 | 69 | /// Returns `true` if the type is a class 70 | pub fn is_class(&self) -> bool { 71 | self.type_descriptor.starts_with("L") 72 | } 73 | 74 | /// Returns `true` if the type is an array 75 | pub fn is_array(&self) -> bool { 76 | self.type_descriptor.starts_with("[") 77 | } 78 | 79 | /// If the type represents an array, get it's dimensions, 80 | /// otherwise returns `None` 81 | pub fn array_dimensions(&self) -> Option { 82 | if self.is_array() { 83 | Some( 84 | self.type_descriptor 85 | .chars() 86 | .take_while(|c| *c == '[') 87 | .count(), 88 | ) 89 | } else { 90 | None 91 | } 92 | } 93 | 94 | /// Returns the Java representation of the `Type` 95 | pub fn to_java_type(&self) -> String { 96 | to_java_type(&*self.type_descriptor) 97 | } 98 | 99 | gen_is_type_method!(is_bool, BOOLEAN, "Returns `true` if the type is a boolean"); 100 | gen_is_type_method!(is_byte, BYTE, "Returns `true` if the type is a byte"); 101 | gen_is_type_method!(is_short, SHORT, "Returns `true` if the type is a short"); 102 | gen_is_type_method!(is_char, CHAR, "Returns `true` if the type is a char"); 103 | gen_is_type_method!(is_int, INT, "Returns `true` if the type is an integer"); 104 | gen_is_type_method!(is_long, LONG, "Returns `true` if the type is a long"); 105 | gen_is_type_method!(is_float, FLOAT, "Returns `true` if the type is a float"); 106 | gen_is_type_method!(is_double, DOUBLE, "Returns `true` if the type is a double"); 107 | gen_is_type_method!(is_void, VOID, "Returns `true` if the type is void"); 108 | } 109 | 110 | fn to_java_type(s: &str) -> String { 111 | match s { 112 | BOOLEAN => "boolean".to_string(), 113 | BYTE => "byte".to_string(), 114 | SHORT => "short".to_string(), 115 | CHAR => "char".to_string(), 116 | INT => "int".to_string(), 117 | LONG => "long".to_string(), 118 | FLOAT => "float".to_string(), 119 | DOUBLE => "double".to_string(), 120 | VOID => "void".to_string(), 121 | s if s.starts_with('L') => s[1..].replace('/', ".").replace(';', ""), 122 | s if s.starts_with('[') => { 123 | let d = s.chars().take_while(|c| *c == '[').count(); 124 | let mut base_type = to_java_type(&s[d..]); 125 | base_type.push_str(&"[]".repeat(d)); 126 | base_type 127 | } 128 | _ => unreachable!(), 129 | } 130 | } 131 | 132 | impl Clone for Type { 133 | fn clone(&self) -> Self { 134 | Type { 135 | id: self.id, 136 | type_descriptor: self.type_descriptor.clone(), 137 | } 138 | } 139 | } 140 | 141 | impl PartialEq for Type { 142 | fn eq(&self, other: &Type) -> bool { 143 | self.id == other.id 144 | } 145 | } 146 | 147 | impl PartialEq for Type { 148 | fn eq(&self, other: &DexString) -> bool { 149 | self.type_descriptor() == other 150 | } 151 | } 152 | 153 | impl PartialEq for Type { 154 | fn eq(&self, other: &str) -> bool { 155 | self.type_descriptor() == other 156 | } 157 | } 158 | 159 | impl<'a> PartialEq<&'a str> for Type { 160 | fn eq(&self, other: &&'a str) -> bool { 161 | self.type_descriptor() == *other 162 | } 163 | } 164 | 165 | impl fmt::Display for Type { 166 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 167 | write!(f, "{}", self.type_descriptor) 168 | } 169 | } 170 | 171 | #[cfg(test)] 172 | mod tests { 173 | #[test] 174 | fn test_to_java_type() { 175 | use super::to_java_type; 176 | assert_eq!(to_java_type(super::BOOLEAN), "boolean"); 177 | assert_eq!(to_java_type(super::BYTE), "byte"); 178 | assert_eq!(to_java_type(super::SHORT), "short"); 179 | assert_eq!(to_java_type(super::CHAR), "char"); 180 | assert_eq!(to_java_type(super::INT), "int"); 181 | assert_eq!(to_java_type(super::LONG), "long"); 182 | assert_eq!(to_java_type(super::FLOAT), "float"); 183 | assert_eq!(to_java_type(super::DOUBLE), "double"); 184 | assert_eq!(to_java_type(super::VOID), "void"); 185 | assert_eq!(to_java_type("Ljava/lang/String;"), "java.lang.String"); 186 | assert_eq!(to_java_type("[Ljava/lang/String;"), "java.lang.String[]"); 187 | assert_eq!(to_java_type("[[Ljava/lang/String;"), "java.lang.String[][]"); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | //! Dex String utilities 2 | use std::{ 3 | convert::AsRef, 4 | fmt, 5 | num::NonZeroUsize, 6 | ops::{Deref, Range}, 7 | }; 8 | 9 | use cesu8::{from_java_cesu8, to_java_cesu8}; 10 | use scroll::{self, ctx, Pread, Uleb128}; 11 | 12 | use crate::{cache::Cache, error, error::Error, source::Source, uint, Result}; 13 | use std::rc::Rc; 14 | 15 | /// Index into the `StringId`s section. 16 | pub type StringId = uint; 17 | 18 | /// Strings in `Dex` file are encoded as MUTF-8 code units. DexString is a 19 | /// wrapper type for converting Dex strings into Rust strings. 20 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#mutf-8) 21 | #[derive(Debug, Hash, Eq, PartialEq, Clone, PartialOrd, Ord)] 22 | pub struct DexString { 23 | string: Rc, 24 | } 25 | 26 | impl PartialEq for DexString { 27 | fn eq(&self, other: &str) -> bool { 28 | *self.string == other 29 | } 30 | } 31 | 32 | impl<'a> PartialEq<&'a str> for DexString { 33 | fn eq(&self, other: &&'a str) -> bool { 34 | *self.string == *other 35 | } 36 | } 37 | 38 | impl fmt::Display for DexString { 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 | write!(f, "{}", self.string) 41 | } 42 | } 43 | 44 | impl From for DexString { 45 | fn from(string: String) -> Self { 46 | DexString { 47 | string: Rc::new(string), 48 | } 49 | } 50 | } 51 | 52 | impl Deref for DexString { 53 | type Target = str; 54 | 55 | fn deref(&self) -> &Self::Target { 56 | &self.string 57 | } 58 | } 59 | 60 | impl<'a> ctx::TryFromCtx<'a, scroll::Endian> for DexString { 61 | type Error = error::Error; 62 | type Size = usize; 63 | 64 | // https://source.android.com/devices/tech/dalvik/dex-format#string-data-item 65 | fn try_from_ctx(source: &'a [u8], _: scroll::Endian) -> Result<(Self, Self::Size)> { 66 | let offset = &mut 0; 67 | let _ = Uleb128::read(source, offset)?; 68 | let count = source 69 | .iter() 70 | .skip(*offset) 71 | .take_while(|c| **c != b'\0') 72 | .count(); 73 | let bytes = &source[*offset..*offset + count]; 74 | let size = *offset + bytes.len(); 75 | Ok(( 76 | DexString { 77 | string: Rc::new( 78 | from_java_cesu8(bytes) 79 | .map_err(|e| Error::MalFormed(format!("Malformed string: {:?}", e)))? 80 | .into_owned(), 81 | ), 82 | }, 83 | size, 84 | )) 85 | } 86 | } 87 | 88 | /// To prevent encoding/decoding Java strings to Rust strings 89 | /// every time, we cache the strings in memory. This also potentially 90 | /// reduces I/O because strings are used in a lot of places. 91 | #[derive(Debug)] 92 | pub(crate) struct Strings { 93 | source: Source, 94 | /// Offset into the strings section. 95 | offset: uint, 96 | endian: super::Endian, 97 | /// Length of the strings section. 98 | len: uint, 99 | cache: Cache, 100 | data_section: Range, 101 | } 102 | 103 | impl Strings 104 | where 105 | T: AsRef<[u8]>, 106 | { 107 | /// Returns a new instance of the string cache 108 | pub(crate) fn new( 109 | source: Source, 110 | endian: super::Endian, 111 | offset: uint, 112 | len: uint, 113 | cache_size: NonZeroUsize, 114 | data_section: Range, 115 | ) -> Self { 116 | Self { 117 | source, 118 | offset, 119 | endian, 120 | len, 121 | cache: Cache::new(cache_size), 122 | data_section, 123 | } 124 | } 125 | 126 | fn parse(&self, id: StringId) -> Result { 127 | let source = &self.source; 128 | let offset = self.offset as usize + id as usize * 4; 129 | let string_data_off: uint = source.pread_with(offset, self.endian)?; 130 | if !self.data_section.contains(&string_data_off) { 131 | return Err(error::Error::BadOffset( 132 | string_data_off as usize, 133 | format!("string_data_off not in data section for StringId: {}", id), 134 | )); 135 | } 136 | source.pread(string_data_off as usize) 137 | } 138 | 139 | /// Get the string at `id` updating the cache with the new item 140 | pub(crate) fn get(&self, id: StringId) -> Result { 141 | if id >= self.len { 142 | return Err(Error::InvalidId(format!("Invalid string id: {}", id))); 143 | } 144 | if let Some(string) = self.cache.get(&id) { 145 | Ok(string) 146 | } else { 147 | self.cache.put(id, self.parse(id)?); 148 | Ok(self.cache.get(&id).unwrap()) 149 | } 150 | } 151 | 152 | pub(crate) fn get_id(&self, string: &str) -> Result> { 153 | use crate::search::Section; 154 | let java_string = to_java_cesu8(string); 155 | let (offset, len) = (self.offset as usize, self.len as usize); 156 | let string_section = &self.source[offset..offset + len * std::mem::size_of::()]; 157 | let section = Section::new(string_section); 158 | let source = self.source.clone(); 159 | let index = section.binary_search( 160 | &java_string, 161 | self.endian, 162 | move |data_offset: &uint, element: &std::borrow::Cow<[u8]>| { 163 | let mut data_offset = *data_offset as usize; 164 | let _ = Uleb128::read(source.as_ref(), &mut data_offset) 165 | .map_err(crate::error::Error::from)?; 166 | let value = &source[data_offset..data_offset + element.len()]; 167 | Ok((**element).cmp(value)) 168 | }, 169 | )?; 170 | Ok(index.map(|i| i as StringId)) 171 | } 172 | } 173 | 174 | impl Clone for Strings { 175 | fn clone(&self) -> Self { 176 | Self { 177 | source: self.source.clone(), 178 | offset: self.offset, 179 | endian: self.endian, 180 | len: self.len, 181 | cache: self.cache.clone(), 182 | data_section: self.data_section.clone(), 183 | } 184 | } 185 | } 186 | 187 | /// Iterator over the strings in the strings section. 188 | pub struct StringsIter { 189 | /// String cache shared by the parent `Dex` 190 | cache: Strings, 191 | current: usize, 192 | len: usize, 193 | } 194 | 195 | impl> StringsIter { 196 | pub(crate) fn new(cache: Strings, len: usize) -> Self { 197 | Self { 198 | cache, 199 | current: 0, 200 | len, 201 | } 202 | } 203 | } 204 | 205 | impl> Iterator for StringsIter { 206 | type Item = super::Result; 207 | 208 | // NOTE: iteration may cause cache thrashing, introduce a new 209 | // method to get but not update cache if needed 210 | fn next(&mut self) -> Option { 211 | if self.current >= self.len { 212 | return None; 213 | } 214 | let next = self.cache.get(self.current as uint); 215 | self.current += 1; 216 | Some(next) 217 | } 218 | } 219 | 220 | #[cfg(test)] 221 | mod tests { 222 | #[test] 223 | fn test_get_string() { 224 | let dex = crate::DexReader::from_file("resources/classes.dex").expect("failed to open dex"); 225 | let value = dex.strings.get_id("Lorg/adw/launcher/Launcher;"); 226 | assert!(value.is_ok()); 227 | let value = value.unwrap(); 228 | assert!(value.is_some()); 229 | let string_id = value.unwrap(); 230 | assert_eq!( 231 | dex.get_string(string_id) 232 | .expect("string id doesn't exist") 233 | .to_string(), 234 | "Lorg/adw/launcher/Launcher;" 235 | ); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/method.rs: -------------------------------------------------------------------------------- 1 | //! Dex `Method` and supporting structures 2 | use getset::{CopyGetters, Getters}; 3 | use num_derive::FromPrimitive; 4 | use num_traits::FromPrimitive; 5 | use scroll::{ctx, Pread, Uleb128}; 6 | 7 | use crate::{ 8 | annotation::{AnnotationSetItem, AnnotationSetRefList}, 9 | code::CodeItem, 10 | encoded_item::{EncodedItem, EncodedItemArray}, 11 | error::Error, 12 | field::FieldId, 13 | jtype::{Type, TypeId}, 14 | string::{DexString, StringId}, 15 | uint, ulong, ushort, utils, 16 | }; 17 | 18 | bitflags! { 19 | /// Access flags of a `Dex` Method 20 | pub struct AccessFlags: ulong { 21 | const PUBLIC = 0x1; 22 | const PRIVATE = 0x2; 23 | const PROTECTED = 0x4; 24 | const STATIC = 0x8; 25 | const FINAL = 0x10; 26 | const SYNCHRONIZED = 0x20; 27 | const BRIDGE = 0x40; 28 | const VARARGS = 0x80; 29 | const NATIVE = 0x100; 30 | const ABSTRACT = 0x400; 31 | const STRICT = 0x800; 32 | const SYNTHETIC = 0x1000; 33 | const CONSTRUCTOR = 0x10000; 34 | const DECLARED_SYNCHRONIZED = 0x20000; 35 | } 36 | } 37 | 38 | /// Represents a `Class` method. 39 | #[derive(Debug, Getters, CopyGetters)] 40 | pub struct Method { 41 | /// Parent class of the method. 42 | #[get = "pub"] 43 | class: Type, 44 | /// Name of the method. 45 | #[get = "pub"] 46 | name: DexString, 47 | /// Access flags of the method. 48 | #[get_copy = "pub"] 49 | access_flags: AccessFlags, 50 | /// Types of the parameters of the method. 51 | #[get = "pub"] 52 | params: Vec, 53 | /// Shorty descriptor of the method, as described 54 | /// [here](https://source.android.com/devices/tech/dalvik/dex-format#shortydescriptor) 55 | #[get = "pub"] 56 | shorty: DexString, 57 | /// Return type of the method. 58 | #[get = "pub"] 59 | return_type: Type, 60 | /// Code and DebugInfo of the method. 61 | code: Option, 62 | /// Annotations of the method. 63 | #[get = "pub"] 64 | annotations: AnnotationSetItem, 65 | /// Annotations of the params. 66 | #[get = "pub"] 67 | param_annotations: AnnotationSetRefList, 68 | /// `MethodId` of the method. 69 | #[get_copy = "pub"] 70 | id: MethodId, 71 | } 72 | 73 | impl Method { 74 | gen_is_flag_set!(is_public, PUBLIC); 75 | gen_is_flag_set!(is_private, PRIVATE); 76 | gen_is_flag_set!(is_protected, PROTECTED); 77 | gen_is_flag_set!(is_static, STATIC); 78 | gen_is_flag_set!(is_final, FINAL); 79 | gen_is_flag_set!(is_synchronized, SYNCHRONIZED); 80 | gen_is_flag_set!(is_bridge, BRIDGE); 81 | gen_is_flag_set!(is_varargs, VARARGS); 82 | gen_is_flag_set!(is_native, NATIVE); 83 | gen_is_flag_set!(is_abstract, ABSTRACT); 84 | gen_is_flag_set!(is_strict, STRICT); 85 | gen_is_flag_set!(is_synthetic, SYNTHETIC); 86 | gen_is_flag_set!(is_constructor, CONSTRUCTOR); 87 | gen_is_flag_set!(is_declared_synchronized, DECLARED_SYNCHRONIZED); 88 | 89 | /// Returns the value of `dalvik.annotation.Signature`. 90 | pub fn signature(&self) -> super::Result> { 91 | utils::get_signature(self.annotations()) 92 | } 93 | 94 | /// Code and DebugInfo of the method. 95 | pub fn code(&self) -> Option<&CodeItem> { 96 | self.code.as_ref() 97 | } 98 | } 99 | 100 | /// Index into the `ProtoId`s list. 101 | pub type ProtoId = ulong; 102 | 103 | /// Method Prototypes. 104 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#proto-id-item) 105 | #[derive(Pread, Debug, CopyGetters, PartialEq)] 106 | #[get_copy = "pub"] 107 | pub struct ProtoIdItem { 108 | /// Index into the string_ids list for the short-form descriptor string of this prototype 109 | shorty: StringId, 110 | /// Index into the type_ids list for the return type of this prototype. 111 | return_type: TypeId, 112 | /// Offset from the start of the file to the list of parameter types for this prototype, or `0` 113 | /// if this prototype has no params. The data at the location should be a list of types. 114 | params_off: uint, 115 | } 116 | 117 | impl ProtoIdItem { 118 | pub(crate) fn try_from_dex>( 119 | dex: &super::Dex, 120 | offset: ulong, 121 | ) -> super::Result { 122 | let source = dex.source.as_ref(); 123 | Ok(source.pread_with(offset as usize, dex.get_endian())?) 124 | } 125 | } 126 | 127 | impl Method { 128 | pub(crate) fn try_from_dex>( 129 | dex: &super::Dex, 130 | encoded_method: &EncodedMethod, 131 | annotations: AnnotationSetItem, 132 | param_annotations: AnnotationSetRefList, 133 | ) -> super::Result { 134 | debug!(target: "method", "encoded method: {:?}", encoded_method); 135 | let source = &dex.source; 136 | let method_item = dex.get_method_item(encoded_method.method_id)?; 137 | let name = dex.get_string(method_item.name_idx)?; 138 | debug!(target: "method", "name: {}, method id item: {:?}", name, method_item); 139 | let proto_item = dex.get_proto_item(ProtoId::from(method_item.proto_idx))?; 140 | debug!(target: "method", "method proto_item: {:?}", proto_item); 141 | let shorty = dex.get_string(proto_item.shorty)?; 142 | let return_type = dex.get_type(proto_item.return_type)?; 143 | let params = if proto_item.params_off != 0 { 144 | if !dex.is_offset_in_data_section(proto_item.params_off) { 145 | return Err(Error::BadOffset( 146 | proto_item.params_off as usize, 147 | format!( 148 | "Params offset not in data section for proto_item: {:?}", 149 | proto_item 150 | ), 151 | )); 152 | } 153 | let offset = &mut (proto_item.params_off as usize); 154 | let endian = dex.get_endian(); 155 | let len = source.gread_with::(offset, endian)?; 156 | let type_ids: Vec = try_gread_vec_with!(source, offset, len, endian); 157 | utils::get_types(dex, &type_ids)? 158 | } else { 159 | Default::default() 160 | }; 161 | debug!(target: "method", "code item offset: {}", encoded_method.code_offset); 162 | let code = dex.get_code_item(encoded_method.code_offset)?; 163 | Ok(Self { 164 | name, 165 | class: dex.get_type(TypeId::from(method_item.class_idx))?, 166 | access_flags: AccessFlags::from_bits(encoded_method.access_flags).ok_or_else(|| { 167 | Error::InvalidId(format!( 168 | "Invalid access flags for method {}", 169 | method_item.name_idx 170 | )) 171 | })?, 172 | shorty, 173 | return_type, 174 | params, 175 | code, 176 | annotations, 177 | param_annotations, 178 | id: encoded_method.method_id, 179 | }) 180 | } 181 | } 182 | 183 | #[derive(Pread)] 184 | struct MethodIdData { 185 | class_idx: ushort, 186 | proto_idx: ushort, 187 | name_idx: StringId, 188 | } 189 | 190 | /// Method identifier. 191 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#method-id-item) 192 | #[derive(Debug, CopyGetters, PartialEq)] 193 | #[get_copy = "pub"] 194 | pub struct MethodIdItem { 195 | /// Index into the `TypeId`s list for the definer of this method. 196 | class_idx: ushort, 197 | /// Index into the `ProtoId`s list for the prototype of this method. 198 | proto_idx: ushort, 199 | /// Index into the `StringId`s list for the name of this method. 200 | name_idx: StringId, 201 | /// `MethodId` of this method. 202 | id: MethodId, 203 | } 204 | 205 | impl MethodIdItem { 206 | pub(crate) fn try_from_dex>( 207 | dex: &super::Dex, 208 | offset: ulong, 209 | method_id: MethodId, 210 | ) -> super::Result { 211 | let source = &dex.source; 212 | let method: MethodIdData = source.pread_with(offset as usize, dex.get_endian())?; 213 | Ok(MethodIdItem { 214 | class_idx: method.class_idx, 215 | proto_idx: method.proto_idx, 216 | name_idx: method.name_idx, 217 | id: method_id, 218 | }) 219 | } 220 | } 221 | 222 | /// Index into the `MethodId`s list. 223 | pub type MethodId = ulong; 224 | 225 | /// Index into the `MethodHandleItem`s list. 226 | pub type MethodHandleId = uint; 227 | 228 | /// Contains a `MethodId` along with its access flags and code. 229 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#encoded-method) 230 | #[derive(Debug, Getters, CopyGetters)] 231 | pub struct EncodedMethod { 232 | /// Index into the `MethodId`s list for the identity of this method represented as 233 | /// a difference from the index of previous element in the list. 234 | #[get_copy = "pub(crate)"] 235 | pub(crate) method_id: MethodId, 236 | /// Access flags for this method. 237 | #[get = "pub"] 238 | access_flags: ulong, 239 | /// Offset from the start of the file to the code structure for this method, or `0` if this 240 | /// method is either abstract or native. The format of the data is specified by `CodeItem`. 241 | #[get = "pub"] 242 | code_offset: ulong, 243 | } 244 | 245 | impl EncodedItem for EncodedMethod { 246 | fn id(&self) -> ulong { 247 | self.method_id 248 | } 249 | } 250 | 251 | /// List of `EncodedMethod`s 252 | pub type EncodedMethodArray = EncodedItemArray; 253 | 254 | impl<'a> ctx::TryFromCtx<'a, ulong> for EncodedMethod { 255 | type Error = Error; 256 | type Size = usize; 257 | 258 | fn try_from_ctx(source: &'a [u8], prev_id: ulong) -> super::Result<(Self, Self::Size)> { 259 | let offset = &mut 0; 260 | let id = Uleb128::read(source, offset)?; 261 | let access_flags = Uleb128::read(source, offset)?; 262 | let code_offset = Uleb128::read(source, offset)?; 263 | Ok(( 264 | Self { 265 | method_id: prev_id + id, 266 | code_offset, 267 | access_flags, 268 | }, 269 | *offset, 270 | )) 271 | } 272 | } 273 | 274 | /// Type of the method handle. 275 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#method-handle-type-codes) 276 | #[derive(FromPrimitive, Debug, Clone, Copy, PartialEq)] 277 | pub enum MethodHandleType { 278 | StaticPut = 0x00, 279 | StaticGet = 0x01, 280 | InstancePut = 0x02, 281 | InstanceGet = 0x03, 282 | InvokeStatic = 0x04, 283 | InvokeInstance = 0x05, 284 | InvokeConstructor = 0x06, 285 | InvokeDirect = 0x07, 286 | InvokeInterface = 0x08, 287 | } 288 | 289 | #[derive(Debug, Clone, Copy, PartialEq)] 290 | pub enum FieldOrMethodId { 291 | Field(FieldId), 292 | Method(MethodId), 293 | } 294 | 295 | /// A method handle. 296 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#method-handle-item) 297 | #[derive(Debug, CopyGetters, PartialEq)] 298 | #[get_copy = "pub"] 299 | pub struct MethodHandleItem { 300 | /// The type of this MethodHandleItem. 301 | handle_type: MethodHandleType, 302 | /// `FieldId` or `MethodId` depending on whether the method handle type is an accessor or 303 | /// a method invoker 304 | id: FieldOrMethodId, 305 | } 306 | 307 | impl<'a, S: AsRef<[u8]>> ctx::TryFromCtx<'a, &super::Dex> for MethodHandleItem { 308 | type Error = Error; 309 | type Size = usize; 310 | 311 | fn try_from_ctx(source: &'a [u8], dex: &super::Dex) -> super::Result<(Self, Self::Size)> { 312 | let endian = dex.get_endian(); 313 | let offset = &mut 0; 314 | let handle_type: ushort = source.gread_with(offset, endian)?; 315 | let handle_type = MethodHandleType::from_u16(handle_type) 316 | .ok_or_else(|| Error::InvalidId(format!("Invalid handle type {}", handle_type)))?; 317 | let _: ushort = source.gread_with(offset, endian)?; 318 | let id: ushort = source.gread_with(offset, endian)?; 319 | let _: ushort = source.gread_with(offset, endian)?; 320 | let id = match handle_type { 321 | MethodHandleType::StaticPut 322 | | MethodHandleType::StaticGet 323 | | MethodHandleType::InstancePut 324 | | MethodHandleType::InstanceGet => FieldOrMethodId::Field(FieldId::from(id)), 325 | _ => FieldOrMethodId::Method(MethodId::from(id)), 326 | }; 327 | 328 | Ok((Self { handle_type, id }, *offset)) 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/code.rs: -------------------------------------------------------------------------------- 1 | //! Structures defining the contents of a `Method`'s code. 2 | use scroll::{ctx, Pread, Sleb128, Uleb128}; 3 | use std::{fmt, ops::Deref}; 4 | 5 | use crate::jtype::TypeId; 6 | use crate::string::StringId; 7 | use crate::{ 8 | encoded_item::EncodedCatchHandlers, error::Error, jtype::Type, string::DexString, uint, ulong, 9 | ushort, 10 | }; 11 | use getset::{CopyGetters, Getters}; 12 | 13 | /// Debug Info of a method. 14 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#debug-info-item) 15 | #[derive(Debug, Getters, CopyGetters)] 16 | pub struct DebugInfoItem { 17 | /// Initial value for the state machines's line register. 18 | #[get_copy = "pub"] 19 | line_start: usize, 20 | /// Names of the incoming parameters. 21 | #[get = "pub"] 22 | parameter_names: Vec>, 23 | /// State machine bytecodes 24 | #[get = "pub"] 25 | bytecodes: Vec, 26 | } 27 | 28 | #[derive(Debug, PartialEq, Getters, CopyGetters)] 29 | pub struct DebugInfoLocal { 30 | /// Register that will contain local 31 | #[get_copy = "pub"] 32 | register_num: u64, 33 | /// String index of the name 34 | #[get_copy = "pub"] 35 | name_idx: StringId, 36 | /// Type index of the type 37 | #[get_copy = "pub"] 38 | type_idx: TypeId, 39 | /// String index of the type signature 40 | #[get_copy = "pub"] 41 | sig_idx: Option, 42 | } 43 | 44 | #[derive(Debug, PartialEq, Getters, CopyGetters)] 45 | pub struct DebugInfoSpecial { 46 | // How many lines to move 47 | #[get_copy = "pub"] 48 | line_off: i64, 49 | // How many instructions to move 50 | #[get_copy = "pub"] 51 | address_off: u64, 52 | } 53 | 54 | #[derive(Debug, PartialEq)] 55 | pub enum DebugInfoBytecode { 56 | /// Ends the debug info item 57 | EndSequence, 58 | /// Move to the next instruction 59 | AdvancePc(u64), 60 | /// Move to the next line 61 | AdvanceLine(i64), 62 | /// Creates a new variable 63 | StartLocal(DebugInfoLocal), 64 | /// Destroys a variable 65 | EndLocal(u64), 66 | /// Recreates a variable 67 | RestartLocal(u64), 68 | /// Ends a method prologue 69 | SetPrologueEnd, 70 | /// Begins a method prologue 71 | SetEpilogueBegin, 72 | /// Sets the file name 73 | SetFile(DexString), 74 | /// Moves to a new instruction and line and emit both 75 | Special(DebugInfoSpecial), 76 | } 77 | 78 | /// Code and Debug Info of a method. 79 | #[derive(Getters, CopyGetters)] 80 | pub struct CodeItem { 81 | /// The number of registers the method must use. 82 | #[get_copy = "pub"] 83 | registers_size: ushort, 84 | /// Line number and source file information. 85 | debug_info_item: Option, 86 | /// Number of words for incoming arguments to this method. 87 | #[get_copy = "pub"] 88 | ins_size: ushort, 89 | /// Number of words for outgoing arguments required for invocation. 90 | #[get_copy = "pub"] 91 | outs_size: ushort, 92 | /// Code instructions for this method. 93 | #[get = "pub"] 94 | insns: Vec, 95 | /// Try, Exception handling information of this method. 96 | #[get = "pub"] 97 | tries: Tries, 98 | } 99 | 100 | impl CodeItem { 101 | /// Line number and source file information. 102 | pub fn debug_info_item(&self) -> Option<&DebugInfoItem> { 103 | self.debug_info_item.as_ref() 104 | } 105 | } 106 | 107 | impl fmt::Debug for CodeItem { 108 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 109 | write!(f, "CodeItem {{ registers_size: {}, debug_info: {}, ins_size: {}, outs_size: {}, tries: {} }}", 110 | self.registers_size, self.debug_info_item.is_some(), self.ins_size, self.outs_size, self.tries.len()) 111 | } 112 | } 113 | 114 | /// Represents a Try-Catch block 115 | #[derive(Pread, Clone, Copy, Debug, Getters, CopyGetters)] 116 | pub(crate) struct TryItem { 117 | /// The instruction at which the try block starts. 118 | #[get_copy = "pub"] 119 | start_addr: uint, 120 | /// Number of instructions the try block covers. 121 | #[get_copy = "pub"] 122 | insn_count: ushort, 123 | /// Exception handler offset. 124 | #[get_copy = "pub"] 125 | handler_off: ushort, 126 | } 127 | 128 | #[derive(Debug, Clone)] 129 | pub enum ExceptionType { 130 | /// The `Exception` class. 131 | BaseException, 132 | /// Sub-types of the `Exception` class. 133 | Ty(Type), 134 | } 135 | 136 | #[derive(Debug, Clone, Getters, CopyGetters)] 137 | pub struct CatchHandler { 138 | /// Type of the exception handled by this handler. 139 | #[get = "pub"] 140 | pub(crate) exception: ExceptionType, 141 | /// Start address of the catch handler. 142 | #[get_copy = "pub"] 143 | pub(crate) addr: ulong, 144 | } 145 | 146 | /// Represents Try and catch blocks. 147 | #[derive(Debug, Getters, CopyGetters)] 148 | pub struct TryCatchHandlers { 149 | /// Start of the try block. 150 | #[get_copy = "pub"] 151 | start_addr: uint, 152 | /// Number of instructions covered by this try block. 153 | #[get_copy = "pub"] 154 | insn_count: ushort, 155 | /// List of catch handlers for this try block. 156 | #[get = "pub"] 157 | catch_handlers: Vec, 158 | } 159 | 160 | /// List of try-catch blocks found in this method. 161 | #[derive(Debug, Default, Getters, CopyGetters)] 162 | pub struct Tries { 163 | #[get = "pub"] 164 | try_catch_blocks: Vec, 165 | } 166 | 167 | impl Deref for Tries { 168 | type Target = Vec; 169 | 170 | fn deref(&self) -> &Self::Target { 171 | &self.try_catch_blocks 172 | } 173 | } 174 | 175 | impl<'a, S> ctx::TryFromCtx<'a, (usize, &super::Dex)> for Tries 176 | where 177 | S: AsRef<[u8]>, 178 | { 179 | type Error = Error; 180 | type Size = usize; 181 | 182 | fn try_from_ctx( 183 | source: &'a [u8], 184 | (tries_size, dex): (usize, &super::Dex), 185 | ) -> Result<(Self, Self::Size), Self::Error> { 186 | let offset = &mut 0; 187 | let endian = dex.get_endian(); 188 | let tries: Vec = try_gread_vec_with!(source, offset, tries_size, endian); 189 | let encoded_catch_handlers: EncodedCatchHandlers = source.gread_with(offset, dex)?; 190 | let tries: super::Result> = tries 191 | .into_iter() 192 | .map(|c| { 193 | let encoded_handler = 194 | encoded_catch_handlers.find(c.handler_off).ok_or_else(|| { 195 | Error::InvalidId(format!("Invalid catch handler: {}", c.handler_off)) 196 | })?; 197 | Ok(TryCatchHandlers { 198 | start_addr: c.start_addr, 199 | insn_count: c.insn_count, 200 | catch_handlers: encoded_handler.handlers(), 201 | }) 202 | }) 203 | .collect(); 204 | Ok(( 205 | Self { 206 | try_catch_blocks: tries?, 207 | }, 208 | *offset, 209 | )) 210 | } 211 | } 212 | 213 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for DebugInfoItem 214 | where 215 | S: AsRef<[u8]>, 216 | { 217 | type Error = Error; 218 | type Size = usize; 219 | 220 | fn try_from_ctx( 221 | source: &'a [u8], 222 | dex: &super::Dex, 223 | ) -> Result<(Self, Self::Size), Self::Error> { 224 | let offset = &mut 0; 225 | let line_start = Uleb128::read(source, offset)? as usize; 226 | let parameters_size = Uleb128::read(source, offset)?; 227 | let mut parameter_names = Vec::with_capacity(parameters_size as usize); 228 | for _ in 0..parameters_size { 229 | let string_id = (Uleb128::read(source, offset)? as u32).overflowing_sub(1).0; 230 | parameter_names.push(if string_id != u32::from(crate::NO_INDEX) { 231 | Some(dex.get_string(string_id as uint)?) 232 | } else { 233 | None 234 | }); 235 | } 236 | let mut bytecodes = Vec::new(); 237 | loop { 238 | let opcode: u8 = source.gread(offset)?; 239 | 240 | let byte_code = match opcode { 241 | 0x00 => DebugInfoBytecode::EndSequence, 242 | 0x01 => DebugInfoBytecode::AdvancePc(Uleb128::read(source, offset)?), 243 | 0x02 => DebugInfoBytecode::AdvanceLine(Sleb128::read(source, offset)?), 244 | 0x03 => DebugInfoBytecode::StartLocal(DebugInfoLocal { 245 | register_num: Uleb128::read(source, offset)?, 246 | name_idx: (Uleb128::read(source, offset)? as StringId) 247 | .overflowing_sub(1) 248 | .0, 249 | type_idx: (Uleb128::read(source, offset)? as TypeId) 250 | .overflowing_sub(1) 251 | .0, 252 | sig_idx: None, 253 | }), 254 | 0x04 => DebugInfoBytecode::StartLocal(DebugInfoLocal { 255 | register_num: Uleb128::read(source, offset)?, 256 | name_idx: (Uleb128::read(source, offset)? as StringId) 257 | .overflowing_sub(1) 258 | .0, 259 | type_idx: (Uleb128::read(source, offset)? as TypeId) 260 | .overflowing_sub(1) 261 | .0, 262 | sig_idx: Some( 263 | (Uleb128::read(source, offset)? as StringId) 264 | .overflowing_sub(1) 265 | .0, 266 | ), 267 | }), 268 | 0x05 => DebugInfoBytecode::EndLocal(Uleb128::read(source, offset)?), 269 | 0x06 => DebugInfoBytecode::RestartLocal(Uleb128::read(source, offset)?), 270 | 0x07 => DebugInfoBytecode::SetPrologueEnd, 271 | 0x08 => DebugInfoBytecode::SetEpilogueBegin, 272 | 0x09 => DebugInfoBytecode::SetFile( 273 | dex.get_string( 274 | (Uleb128::read(source, offset)? as StringId) 275 | .overflowing_sub(1) 276 | .0 as uint, 277 | )?, 278 | ), 279 | _ => { 280 | const DBG_FIRST_SPECIAL: u64 = 0x0a; // the smallest special opcode 281 | const DBG_LINE_BASE: i64 = -4; // the smallest line number increment 282 | const DBG_LINE_RANGE: u64 = 15; // the number of line increments represented 283 | 284 | let adjusted_opcode = opcode as u64 - DBG_FIRST_SPECIAL; 285 | 286 | DebugInfoBytecode::Special(DebugInfoSpecial { 287 | line_off: DBG_LINE_BASE + (adjusted_opcode % DBG_LINE_RANGE) as i64, 288 | address_off: (adjusted_opcode / DBG_LINE_RANGE), 289 | }) 290 | } 291 | }; 292 | 293 | bytecodes.push(byte_code); 294 | if bytecodes.last() == Some(&DebugInfoBytecode::EndSequence) { 295 | break; 296 | } 297 | } 298 | Ok(( 299 | Self { 300 | line_start, 301 | parameter_names, 302 | bytecodes, 303 | }, 304 | *offset, 305 | )) 306 | } 307 | } 308 | 309 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for CodeItem 310 | where 311 | S: AsRef<[u8]>, 312 | { 313 | type Error = Error; 314 | type Size = usize; 315 | 316 | fn try_from_ctx( 317 | source: &'a [u8], 318 | dex: &super::Dex, 319 | ) -> Result<(Self, Self::Size), Self::Error> { 320 | let offset = &mut 0; 321 | let endian = dex.get_endian(); 322 | let registers_size: ushort = source.gread_with(offset, endian)?; 323 | let ins_size = source.gread_with(offset, endian)?; 324 | let outs_size = source.gread_with(offset, endian)?; 325 | let tries_size: ushort = source.gread_with(offset, endian)?; 326 | let debug_info_off = source.gread_with(offset, endian)?; 327 | let debug_info_item = if debug_info_off != 0 { 328 | Some(dex.get_debug_info_item(debug_info_off)?) 329 | } else { 330 | None 331 | }; 332 | let insns_size: uint = source.gread_with(offset, endian)?; 333 | let insns: Vec = try_gread_vec_with!(source, offset, insns_size, endian); 334 | if insns_size % 2 != 0 && tries_size != 0 { 335 | source.gread_with::(offset, endian)?; 336 | } 337 | let tries: Tries = if tries_size != 0 { 338 | source.gread_with(offset, (tries_size as usize, dex))? 339 | } else { 340 | Default::default() 341 | }; 342 | Ok(( 343 | Self { 344 | registers_size, 345 | debug_info_item, 346 | ins_size, 347 | outs_size, 348 | insns, 349 | tries, 350 | }, 351 | *offset, 352 | )) 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/annotation.rs: -------------------------------------------------------------------------------- 1 | //! Structures for Annotations on a `Class`, `Method`, `MethodParams` and `Field`s. 2 | use scroll::{ctx, Pread, Uleb128}; 3 | use std::ops::Deref; 4 | 5 | use getset::{CopyGetters, Getters}; 6 | 7 | use crate::{ 8 | encoded_value::EncodedValue, 9 | error::Error, 10 | field::FieldId, 11 | jtype::{Type, TypeId}, 12 | method::MethodId, 13 | string::{DexString, StringId}, 14 | ubyte, uint, 15 | }; 16 | 17 | use num_derive::FromPrimitive; 18 | use num_traits::FromPrimitive; 19 | 20 | /// Contains the type and parameters of an Annotation. 21 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#encoded-annotation) 22 | #[derive(Debug, Getters, PartialEq)] 23 | #[get = "pub"] 24 | pub struct EncodedAnnotation { 25 | /// Type of the annotation. Should be a class type. 26 | jtype: Type, 27 | /// Elements of the annotation 28 | elements: Vec, 29 | } 30 | 31 | impl EncodedAnnotation { 32 | /// Find element with the `name` 33 | pub fn find_element(&self, name: &str) -> Option<&AnnotationElement> { 34 | self.elements().iter().find(|e| e.name() == name) 35 | } 36 | } 37 | 38 | impl Deref for EncodedAnnotation { 39 | type Target = Vec; 40 | 41 | fn deref(&self) -> &Self::Target { 42 | &self.elements 43 | } 44 | } 45 | 46 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for EncodedAnnotation 47 | where 48 | S: AsRef<[u8]>, 49 | { 50 | type Error = Error; 51 | type Size = usize; 52 | 53 | fn try_from_ctx(source: &'a [u8], ctx: &super::Dex) -> super::Result<(Self, Self::Size)> { 54 | let offset = &mut 0; 55 | let type_idx = Uleb128::read(source, offset)?; 56 | let jtype = ctx.get_type(type_idx as TypeId)?; 57 | let size = Uleb128::read(source, offset)?; 58 | debug!(target: "encoded-annotation", "type: {}, size: {}", jtype, size); 59 | let elements = try_gread_vec_with!(source, offset, size, ctx); 60 | Ok((Self { jtype, elements }, *offset)) 61 | } 62 | } 63 | 64 | /// Represents a parameter of an annotation. For example, if `@Author(name = "Benjamin Franklin")`, is 65 | /// the annotation, this structure represents `name = "Benjamin Franklin"`. 66 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#annotation-element) 67 | #[derive(Debug, Getters, PartialEq)] 68 | #[get = "pub"] 69 | pub struct AnnotationElement { 70 | /// Name of the element. Should conform to the syntax defined 71 | /// [here](https://source.android.com/devices/tech/dalvik/dex-format#membername) 72 | name: DexString, 73 | /// Value corresponding to the name. 74 | value: EncodedValue, 75 | } 76 | 77 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for AnnotationElement 78 | where 79 | S: AsRef<[u8]>, 80 | { 81 | type Error = Error; 82 | type Size = usize; 83 | 84 | fn try_from_ctx(source: &'a [u8], ctx: &super::Dex) -> super::Result<(Self, Self::Size)> { 85 | let offset = &mut 0; 86 | let name_idx = Uleb128::read(source, offset)?; 87 | let name = ctx.get_string(name_idx as StringId)?; 88 | debug!(target: "annotation-element", "annotation element: {}", name_idx); 89 | let value = source.gread_with(offset, ctx)?; 90 | Ok((Self { name, value }, *offset)) 91 | } 92 | } 93 | 94 | /// Visibility of an annotation. 95 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#visibility) 96 | #[derive(Debug, FromPrimitive, Copy, Clone, PartialEq)] 97 | pub enum Visibility { 98 | /// Visible only to the Build system. 99 | Build = 0x0, 100 | /// Visible at the Runtime. 101 | Runtime = 0x1, 102 | /// Visible only to the virtual machine. 103 | System = 0x2, 104 | } 105 | 106 | /// An Annotation along with its visibility. 107 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#annotation-item) 108 | #[derive(Debug, Getters, CopyGetters)] 109 | pub struct AnnotationItem { 110 | /// Visibility of this annotation. 111 | #[get_copy = "pub"] 112 | visibility: Visibility, 113 | /// Type and parameters of this annotation. 114 | #[get = "pub"] 115 | annotation: EncodedAnnotation, 116 | } 117 | 118 | impl Deref for AnnotationItem { 119 | type Target = EncodedAnnotation; 120 | 121 | fn deref(&self) -> &Self::Target { 122 | &self.annotation 123 | } 124 | } 125 | 126 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for AnnotationItem 127 | where 128 | S: AsRef<[u8]>, 129 | { 130 | type Error = Error; 131 | type Size = usize; 132 | 133 | fn try_from_ctx(source: &'a [u8], ctx: &super::Dex) -> super::Result<(Self, Self::Size)> { 134 | let offset = &mut 0; 135 | let visibility: ubyte = source.gread_with(offset, ctx.get_endian())?; 136 | debug!(target: "annotation-item", "visibility: {:?}", visibility); 137 | let visibility: Visibility = FromPrimitive::from_u8(visibility) 138 | .ok_or_else(|| Error::InvalidId("Invalid visibility for annotation".to_owned()))?; 139 | let annotation = source.gread_with(offset, ctx)?; 140 | Ok(( 141 | Self { 142 | visibility, 143 | annotation, 144 | }, 145 | *offset, 146 | )) 147 | } 148 | } 149 | 150 | /// List of Annotation Sets. Used for method parameter annotations. 151 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#set-ref-list) 152 | #[derive(Debug, Default, Getters)] 153 | #[get = "pub"] 154 | pub struct AnnotationSetRefList { 155 | annotation_set_list: Vec, 156 | } 157 | 158 | impl Deref for AnnotationSetRefList { 159 | type Target = Vec; 160 | 161 | fn deref(&self) -> &Self::Target { 162 | &self.annotation_set_list 163 | } 164 | } 165 | 166 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for AnnotationSetRefList 167 | where 168 | S: AsRef<[u8]>, 169 | { 170 | type Error = Error; 171 | type Size = usize; 172 | 173 | fn try_from_ctx(source: &'a [u8], ctx: &super::Dex) -> super::Result<(Self, Self::Size)> { 174 | let offset = &mut 0; 175 | let endian = ctx.get_endian(); 176 | let size: uint = source.gread_with(offset, endian)?; 177 | debug!(target: "annotation-set-ref-list", "annotation set ref list size: {}", size); 178 | let annotation_ref_items: Vec = try_gread_vec_with!(source, offset, size, endian); 179 | Ok(( 180 | Self { 181 | annotation_set_list: annotation_ref_items 182 | .iter() 183 | .map(|annotation_set_item_off| { 184 | ctx.get_annotation_set_item(*annotation_set_item_off) 185 | }) 186 | .collect::>()?, 187 | }, 188 | *offset, 189 | )) 190 | } 191 | } 192 | 193 | /// A set of annotations on an element. 194 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#annotation-set-item) 195 | #[derive(Debug, Default, Getters)] 196 | #[get = "pub"] 197 | pub struct AnnotationSetItem { 198 | annotations: Vec, 199 | } 200 | 201 | impl Deref for AnnotationSetItem { 202 | type Target = Vec; 203 | 204 | fn deref(&self) -> &Self::Target { 205 | &self.annotations 206 | } 207 | } 208 | 209 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for AnnotationSetItem 210 | where 211 | S: AsRef<[u8]>, 212 | { 213 | type Error = Error; 214 | type Size = usize; 215 | 216 | fn try_from_ctx(source: &'a [u8], ctx: &super::Dex) -> super::Result<(Self, Self::Size)> { 217 | let offset = &mut 0; 218 | let endian = ctx.get_endian(); 219 | let size: uint = source.gread_with(offset, endian)?; 220 | debug!(target: "annotation-set-item", "annotation set items size: {}", size); 221 | let annotation_items_offs: Vec = try_gread_vec_with!(source, offset, size, endian); 222 | Ok(( 223 | Self { 224 | annotations: annotation_items_offs 225 | .iter() 226 | .map(|annotation_off| ctx.get_annotation_item(*annotation_off)) 227 | .collect::>()?, 228 | }, 229 | *offset, 230 | )) 231 | } 232 | } 233 | 234 | /// Annotations of a `Method`'s parameters. 235 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#parameter-annotation) 236 | #[derive(Debug, Getters, CopyGetters)] 237 | pub struct ParameterAnnotations { 238 | /// The method this parameter belongs to. 239 | #[get_copy = "pub"] 240 | method_idx: MethodId, 241 | /// The list of annotation sets for the parameters. 242 | pub(crate) annotations: AnnotationSetRefList, 243 | } 244 | 245 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for ParameterAnnotations 246 | where 247 | S: AsRef<[u8]>, 248 | { 249 | type Error = Error; 250 | type Size = usize; 251 | 252 | fn try_from_ctx(source: &'a [u8], ctx: &super::Dex) -> super::Result<(Self, Self::Size)> { 253 | let offset = &mut 0; 254 | let endian = ctx.get_endian(); 255 | let method_idx: uint = source.gread_with(offset, endian)?; 256 | let annotation_set_ref_list_off: uint = source.gread_with(offset, endian)?; 257 | debug!(target: "parameter-annotation", "annotation set ref list offset: {}", annotation_set_ref_list_off); 258 | Ok(( 259 | Self { 260 | method_idx: MethodId::from(method_idx), 261 | annotations: ctx.get_annotation_set_ref_list(annotation_set_ref_list_off)?, 262 | }, 263 | *offset, 264 | )) 265 | } 266 | } 267 | 268 | /// Annotations of a `Method`. 269 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#method-annotation) 270 | #[derive(Debug, Getters, CopyGetters)] 271 | pub struct MethodAnnotations { 272 | #[get_copy = "pub"] 273 | method_idx: MethodId, 274 | pub(crate) annotations: AnnotationSetItem, 275 | } 276 | 277 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for MethodAnnotations 278 | where 279 | S: AsRef<[u8]>, 280 | { 281 | type Error = Error; 282 | type Size = usize; 283 | 284 | fn try_from_ctx(source: &'a [u8], ctx: &super::Dex) -> super::Result<(Self, Self::Size)> { 285 | let offset = &mut 0; 286 | let method_idx: uint = source.gread_with(offset, ctx.get_endian())?; 287 | let annotation_set_item_off: uint = source.gread_with(offset, ctx.get_endian())?; 288 | debug!(target: "method-annotation", "annotation set item offset: {}", annotation_set_item_off); 289 | Ok(( 290 | Self { 291 | method_idx: MethodId::from(method_idx), 292 | annotations: ctx.get_annotation_set_item(annotation_set_item_off)?, 293 | }, 294 | *offset, 295 | )) 296 | } 297 | } 298 | 299 | /// Annotations of a `Field`. 300 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#field-annotation) 301 | #[derive(Debug, Getters, CopyGetters)] 302 | pub struct FieldAnnotations { 303 | #[get_copy = "pub"] 304 | field_idx: FieldId, 305 | #[get = "pub"] 306 | pub(crate) annotations: AnnotationSetItem, 307 | } 308 | 309 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for FieldAnnotations 310 | where 311 | S: AsRef<[u8]>, 312 | { 313 | type Error = Error; 314 | type Size = usize; 315 | 316 | fn try_from_ctx(source: &'a [u8], ctx: &super::Dex) -> super::Result<(Self, Self::Size)> { 317 | let offset = &mut 0; 318 | let field_idx: uint = source.gread_with(offset, ctx.get_endian())?; 319 | let annotation_set_item_off: uint = source.gread_with(offset, ctx.get_endian())?; 320 | debug!(target: "field-annotation", "annotation set item offset: {}", annotation_set_item_off); 321 | Ok(( 322 | Self { 323 | field_idx: FieldId::from(field_idx), 324 | annotations: ctx.get_annotation_set_item(annotation_set_item_off)?, 325 | }, 326 | *offset, 327 | )) 328 | } 329 | } 330 | 331 | /// Annotations of the fields, methods and parameters of a class and the class itself. 332 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#annotations-directory) 333 | #[derive(Debug, Default, Getters)] 334 | pub struct AnnotationsDirectoryItem { 335 | pub(crate) class_annotations: AnnotationSetItem, 336 | pub(crate) field_annotations: Vec, 337 | pub(crate) method_annotations: Vec, 338 | pub(crate) parameter_annotations: Vec, 339 | } 340 | 341 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for AnnotationsDirectoryItem 342 | where 343 | S: AsRef<[u8]>, 344 | { 345 | type Error = Error; 346 | type Size = usize; 347 | 348 | fn try_from_ctx(source: &'a [u8], ctx: &super::Dex) -> super::Result<(Self, Self::Size)> { 349 | let offset = &mut 0; 350 | let endian = ctx.get_endian(); 351 | let class_annotations_off: uint = source.gread_with(offset, endian)?; 352 | let fields_size: uint = source.gread_with(offset, endian)?; 353 | let annotated_method_size: uint = source.gread_with(offset, endian)?; 354 | let annotated_parameters_size: uint = source.gread_with(offset, endian)?; 355 | debug!(target: "annotations directory", "fields size: {}, annotated method size: {}, annotated params size: {}", 356 | fields_size, annotated_method_size, annotated_parameters_size); 357 | let class_annotations = ctx.get_annotation_set_item(class_annotations_off)?; 358 | let field_annotations = try_gread_vec_with!(source, offset, fields_size, ctx); 359 | let method_annotations = try_gread_vec_with!(source, offset, annotated_method_size, ctx); 360 | let parameter_annotations = 361 | try_gread_vec_with!(source, offset, annotated_parameters_size, ctx); 362 | Ok(( 363 | Self { 364 | class_annotations, 365 | field_annotations, 366 | method_annotations, 367 | parameter_annotations, 368 | }, 369 | *offset, 370 | )) 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/class.rs: -------------------------------------------------------------------------------- 1 | //! Dex `Class` and supporting structures. 2 | use std::clone::Clone; 3 | 4 | use getset::{CopyGetters, Getters}; 5 | use scroll::{ctx, Pread, Uleb128}; 6 | 7 | use crate::{ 8 | annotation::{AnnotationSetItem, AnnotationsDirectoryItem}, 9 | encoded_item::EncodedItemArrayCtx, 10 | error::Error, 11 | field::{EncodedFieldArray, Field}, 12 | jtype::Type, 13 | method::{EncodedMethodArray, Method}, 14 | source::Source, 15 | string::DexString, 16 | uint, utils, 17 | }; 18 | 19 | /// `ClassId` is an index into the Types section. The corresponding `Type` denotes the type of 20 | /// this class. The `Type` must be a class type, not a primitive or an array. 21 | pub type ClassId = uint; 22 | 23 | bitflags! { 24 | /// Access flags of a `Class`. 25 | pub struct AccessFlags: uint { 26 | const PUBLIC = 0x1; 27 | const PRIVATE = 0x2; 28 | const PROTECTED = 0x4; 29 | const STATIC = 0x8; 30 | const FINAL = 0x10; 31 | const INTERFACE = 0x200; 32 | const ABSTRACT = 0x400; 33 | const SYNTHETIC = 0x1000; 34 | const ANNOTATION = 0x2000; 35 | const ENUM = 0x4000; 36 | } 37 | } 38 | 39 | /// A `Dex` Class. This is constructed from a `ClassDefItem` and a `ClassDataItem`. 40 | #[derive(Debug, Getters, CopyGetters)] 41 | pub struct Class { 42 | /// Index into `TypeId`s. TypeId should refer to a class type. 43 | #[get_copy = "pub"] 44 | pub(crate) id: ClassId, 45 | /// Type of this class. 46 | #[get = "pub"] 47 | pub(crate) jtype: Type, 48 | /// Access flags for the class (public, final etc.) 49 | /// Check [here](https://source.android.com/devices/tech/dalvik/dex-format#access-flags) for 50 | /// full reference. 51 | #[get_copy = "pub"] 52 | pub(crate) access_flags: AccessFlags, 53 | /// Index into the `TypeId`s for the super class, if there is one. 54 | #[get_copy = "pub"] 55 | pub(crate) super_class: Option, 56 | /// List of the interfaces implemented by this class. 57 | #[get = "pub"] 58 | pub(crate) interfaces: Vec, 59 | /// The file in which this class is found in the source code. 60 | pub(crate) source_file: Option, 61 | /// Static fields defined in the class. 62 | #[get = "pub"] 63 | pub(crate) static_fields: Vec, 64 | /// Instance fields defined in the class. 65 | #[get = "pub"] 66 | pub(crate) instance_fields: Vec, 67 | /// List of static, private methods and constructors defined in the class. 68 | #[get = "pub"] 69 | pub(crate) direct_methods: Vec, 70 | /// List of parent class methods overriden by this class. 71 | #[get = "pub"] 72 | pub(crate) virtual_methods: Vec, 73 | /// Annotations of the class. 74 | #[get = "pub"] 75 | pub(crate) annotations: AnnotationSetItem, 76 | } 77 | 78 | impl Class { 79 | gen_is_flag_set!(is_public, PUBLIC); 80 | gen_is_flag_set!(is_private, PRIVATE); 81 | gen_is_flag_set!(is_protected, PROTECTED); 82 | gen_is_flag_set!(is_static, STATIC); 83 | gen_is_flag_set!(is_final, FINAL); 84 | gen_is_flag_set!(is_interface, INTERFACE); 85 | gen_is_flag_set!(is_abstract, ABSTRACT); 86 | gen_is_flag_set!(is_synthetic, SYNTHETIC); 87 | gen_is_flag_set!(is_annotation, ANNOTATION); 88 | gen_is_flag_set!(is_enum, ENUM); 89 | 90 | /// Returns the value of `dalvik.annotation.Signature`. 91 | pub fn signature(&self) -> super::Result> { 92 | utils::get_signature(self.annotations()) 93 | } 94 | 95 | /// The file in which this class is found in the source code. 96 | pub fn source_file(&self) -> Option<&DexString> { 97 | self.source_file.as_ref() 98 | } 99 | 100 | /// List of fields defined in this class. 101 | pub fn fields(&self) -> impl Iterator + '_ { 102 | self.static_fields() 103 | .iter() 104 | .chain(self.instance_fields().iter()) 105 | } 106 | 107 | /// List of methods defined in this class. 108 | pub fn methods(&self) -> impl Iterator + '_ { 109 | self.direct_methods() 110 | .iter() 111 | .chain(self.virtual_methods().iter()) 112 | } 113 | 114 | pub(crate) fn try_from_dex>( 115 | dex: &super::Dex, 116 | class_def: &ClassDefItem, 117 | ) -> super::Result { 118 | debug!(target: "class", "trying to load class: {}", class_def.class_idx); 119 | let jtype = dex.get_type(class_def.class_idx)?; 120 | 121 | debug!(target: "class", "class: {}, jtype: {}", class_def.class_idx, jtype); 122 | 123 | let data_off = class_def.class_data_off; 124 | 125 | let AnnotationsDirectoryItem { 126 | class_annotations, 127 | mut field_annotations, 128 | mut method_annotations, 129 | mut parameter_annotations, 130 | } = dex.get_annotations_directory_item(class_def.annotations_off)?; 131 | let static_values = dex.get_static_values(class_def.static_values_off)?; 132 | let (static_fields, instance_fields, direct_methods, virtual_methods) = dex 133 | .get_class_data(data_off)? 134 | .map(move |c| { 135 | let mut static_values = static_values.into_inner(); 136 | // the order of static values corresponds to the fields list. 137 | // reversing the values so that the pop below returns values in 138 | // correct order. 139 | static_values.reverse(); 140 | Ok(( 141 | try_from_item!(c.static_fields, |encoded_field| { 142 | dex.get_field( 143 | &encoded_field, 144 | static_values.pop(), 145 | field_annotations 146 | .binary_search_by_key(&encoded_field.field_id(), |f| f.field_idx()) 147 | .map(|index| field_annotations.remove(index).annotations) 148 | .unwrap_or_else(|_| Default::default()), 149 | ) 150 | }), 151 | try_from_item!(c.instance_fields, |encoded_field| { 152 | dex.get_field( 153 | &encoded_field, 154 | None, 155 | field_annotations 156 | .binary_search_by_key(&encoded_field.field_id(), |f| f.field_idx()) 157 | .map(|index| field_annotations.remove(index).annotations) 158 | .unwrap_or_else(|_| Default::default()), 159 | ) 160 | }), 161 | try_from_item!(c.direct_methods, |encoded_method| { 162 | let method_annotations = method_annotations 163 | .binary_search_by_key(&encoded_method.method_id(), |m| m.method_idx()) 164 | .map(|index| method_annotations.remove(index).annotations) 165 | .unwrap_or_else(|_| Default::default()); 166 | let parameter_annotations = parameter_annotations 167 | .binary_search_by_key(&encoded_method.method_id(), |m| m.method_idx()) 168 | .map(|index| parameter_annotations.remove(index).annotations) 169 | .unwrap_or_else(|_| Default::default()); 170 | dex.get_method(&encoded_method, method_annotations, parameter_annotations) 171 | }), 172 | try_from_item!(c.virtual_methods, |encoded_method| { 173 | let method_annotations = method_annotations 174 | .binary_search_by_key(&encoded_method.method_id(), |m| m.method_idx()) 175 | .map(|index| method_annotations.remove(index).annotations) 176 | .unwrap_or_else(|_| Default::default()); 177 | let parameter_annotations = parameter_annotations 178 | .binary_search_by_key(&encoded_method.method_id(), |m| m.method_idx()) 179 | .map(|index| parameter_annotations.remove(index).annotations) 180 | .unwrap_or_else(|_| Default::default()); 181 | dex.get_method(&encoded_method, method_annotations, parameter_annotations) 182 | }), 183 | )) 184 | }) 185 | .unwrap_or_else(|| Ok::<_, Error>(Default::default()))?; 186 | 187 | debug!(target: "class", "super class id: {}", class_def.superclass_idx); 188 | let super_class = if class_def.superclass_idx != super::NO_INDEX { 189 | Some(class_def.superclass_idx) 190 | } else { 191 | None 192 | }; 193 | debug!(target: "class", "access flags: {}", class_def.access_flags); 194 | 195 | Ok(Class { 196 | id: class_def.class_idx, 197 | jtype, 198 | super_class, 199 | interfaces: dex.get_interfaces(class_def.interfaces_off)?, 200 | access_flags: AccessFlags::from_bits(class_def.access_flags).ok_or_else(|| { 201 | Error::InvalidId(format!( 202 | "Invalid Access flags in class {}", 203 | class_def.class_idx 204 | )) 205 | })?, 206 | source_file: dex.get_source_file(class_def.source_file_idx)?, 207 | static_fields, 208 | instance_fields, 209 | direct_methods, 210 | virtual_methods, 211 | annotations: class_annotations, 212 | }) 213 | } 214 | } 215 | 216 | /// Contains the details about fields and methods of a class. 217 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#class-data-item) 218 | #[derive(Getters)] 219 | pub struct ClassDataItem { 220 | /// The list of static fields in this class. 221 | static_fields: Option, 222 | /// The list of instance fields in this class. 223 | instance_fields: Option, 224 | /// The list of direct methods in this class. 225 | direct_methods: Option, 226 | /// Overriden methods from the super class. 227 | virtual_methods: Option, 228 | } 229 | 230 | impl ClassDataItem { 231 | /// The list of static fields in this class. 232 | pub fn static_fields(&self) -> Option<&EncodedFieldArray> { 233 | self.static_fields.as_ref() 234 | } 235 | 236 | /// The list of instance fields in this class. 237 | pub fn instance_fields(&self) -> Option<&EncodedFieldArray> { 238 | self.instance_fields.as_ref() 239 | } 240 | 241 | /// The list of direct methods in this class. 242 | pub fn direct_methods(&self) -> Option<&EncodedMethodArray> { 243 | self.direct_methods.as_ref() 244 | } 245 | 246 | /// Overriden methods from the super class. 247 | pub fn virtual_methods(&self) -> Option<&EncodedMethodArray> { 248 | self.virtual_methods.as_ref() 249 | } 250 | } 251 | 252 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for ClassDataItem 253 | where 254 | S: AsRef<[u8]>, 255 | { 256 | type Error = Error; 257 | type Size = usize; 258 | 259 | fn try_from_ctx(source: &'a [u8], dex: &super::Dex) -> super::Result<(Self, Self::Size)> { 260 | let offset = &mut 0; 261 | let static_field_size = Uleb128::read(source, offset)?; 262 | let instance_field_size = Uleb128::read(source, offset)?; 263 | let direct_methods_size = Uleb128::read(source, offset)?; 264 | let virtual_methods_size = Uleb128::read(source, offset)?; 265 | 266 | debug!(target: "class data", "static-fields: {}, instance-fields: {}, direct-methods: {}, virtual-methods: {}", 267 | static_field_size, instance_field_size, direct_methods_size, virtual_methods_size); 268 | 269 | Ok(( 270 | ClassDataItem { 271 | static_fields: encoded_array!(source, dex, offset, static_field_size), 272 | instance_fields: encoded_array!(source, dex, offset, instance_field_size), 273 | direct_methods: encoded_array!(source, dex, offset, direct_methods_size), 274 | virtual_methods: encoded_array!(source, dex, offset, virtual_methods_size), 275 | }, 276 | *offset, 277 | )) 278 | } 279 | } 280 | 281 | /// Defines the locations of the contents of a `Class`. 282 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#class-def-item) 283 | #[derive(Copy, Clone, Debug, Pread, CopyGetters)] 284 | #[get_copy = "pub"] 285 | pub struct ClassDefItem { 286 | /// `TypeId` of the class defined by this `ClassDefItem` 287 | pub(crate) class_idx: uint, 288 | /// Access flags of the class defined by this `ClassDefItem` 289 | pub(crate) access_flags: uint, 290 | /// Index into the `TypeId`s list or `NO_INDEX` there is no super class. 291 | pub(crate) superclass_idx: uint, 292 | /// Offset from the start of the file to the location of a list of `TypeId`s which 293 | /// represent the interfaces implemented by this class. 294 | pub(crate) interfaces_off: uint, 295 | /// Index into the `StringId`s list which gives the source file name or `NO_INDEX`. 296 | pub(crate) source_file_idx: uint, 297 | /// Offset from the start of the file to the location of an `AnntotationsDirectoryItem` where 298 | /// the class annotations can be found. 0 if there are no annotations. 299 | pub(crate) annotations_off: uint, 300 | /// Offset from the start of the file to the associated class data or `0` if there is none. The 301 | /// data at the location should be defined in the `ClassDataItem` format. 302 | pub(crate) class_data_off: uint, 303 | /// Offset from the start of the file to the list of initial values for static fields or `0` if 304 | /// all values are to be initialized to `0` or `null`. The data at the location should be 305 | /// defined in the `EncodedArrayItem` format. 306 | pub(crate) static_values_off: uint, 307 | } 308 | 309 | /// Iterator over the class_def_items in the class_defs section. 310 | pub(crate) struct ClassDefItemIter { 311 | /// Source file of the parent `Dex`. 312 | source: Source, 313 | offset: usize, 314 | len: uint, 315 | endian: super::Endian, 316 | } 317 | 318 | impl ClassDefItemIter { 319 | pub(crate) fn new(source: Source, offset: uint, len: uint, endian: super::Endian) -> Self { 320 | Self { 321 | source, 322 | offset: offset as usize, 323 | len, 324 | endian, 325 | } 326 | } 327 | } 328 | 329 | impl> Iterator for ClassDefItemIter { 330 | type Item = super::Result; 331 | 332 | fn next(&mut self) -> Option { 333 | if self.len == 0 { 334 | return None; 335 | } 336 | let class_item: super::Result = self 337 | .source 338 | .as_ref() 339 | .gread_with(&mut self.offset, self.endian) 340 | .map_err(Error::from); 341 | self.len -= 1; 342 | Some(class_item) 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /src/encoded_value.rs: -------------------------------------------------------------------------------- 1 | //! Contains structures defining values in a `Dex`. 2 | use num_derive::FromPrimitive; 3 | use num_traits::FromPrimitive; 4 | use scroll::{self, ctx, Pread, Uleb128, LE}; 5 | 6 | use crate::{ 7 | annotation::EncodedAnnotation, 8 | byte, 9 | error::Error, 10 | field::{FieldId, FieldIdItem}, 11 | int, 12 | jtype::{Type, TypeId}, 13 | long, 14 | method::{MethodHandleId, MethodHandleItem, MethodId, MethodIdItem, ProtoId, ProtoIdItem}, 15 | short, 16 | string::{DexString, StringId}, 17 | ubyte, uint, ushort, Result, 18 | }; 19 | 20 | /// Used to represent values of fields, annotations etc. 21 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#encoding) 22 | #[derive(Debug, PartialEq)] 23 | pub enum EncodedValue { 24 | Byte(byte), 25 | Short(short), 26 | Char(ushort), 27 | Int(int), 28 | Long(long), 29 | Type(Type), 30 | Float(f32), 31 | Double(f64), 32 | MethodType(ProtoIdItem), 33 | MethodHandle(MethodHandleItem), 34 | String(DexString), 35 | Field(FieldIdItem), 36 | Method(MethodIdItem), 37 | Annotation(EncodedAnnotation), 38 | Array(Vec), 39 | Enum(FieldIdItem), 40 | Null, 41 | Boolean(bool), 42 | } 43 | 44 | impl PartialEq for EncodedValue { 45 | fn eq(&self, other: &ushort) -> bool { 46 | match self { 47 | EncodedValue::Char(us) => us == other, 48 | _ => false, 49 | } 50 | } 51 | } 52 | 53 | impl PartialEq for EncodedValue { 54 | fn eq(&self, other: &int) -> bool { 55 | match self { 56 | EncodedValue::Int(val) => val == other, 57 | _ => false, 58 | } 59 | } 60 | } 61 | 62 | impl PartialEq for EncodedValue { 63 | fn eq(&self, other: &long) -> bool { 64 | match self { 65 | EncodedValue::Long(l) => l == other, 66 | _ => false, 67 | } 68 | } 69 | } 70 | 71 | impl PartialEq for EncodedValue { 72 | fn eq(&self, other: &short) -> bool { 73 | match self { 74 | EncodedValue::Short(b) => b == other, 75 | _ => false, 76 | } 77 | } 78 | } 79 | 80 | impl PartialEq for EncodedValue { 81 | fn eq(&self, other: &byte) -> bool { 82 | match self { 83 | EncodedValue::Byte(b) => b == other, 84 | _ => false, 85 | } 86 | } 87 | } 88 | 89 | impl PartialEq for EncodedValue { 90 | fn eq(&self, other: &f64) -> bool { 91 | match self { 92 | EncodedValue::Double(d) => d == other, 93 | _ => false, 94 | } 95 | } 96 | } 97 | 98 | impl PartialEq for EncodedValue { 99 | fn eq(&self, other: &f32) -> bool { 100 | match self { 101 | EncodedValue::Float(f) => f == other, 102 | _ => false, 103 | } 104 | } 105 | } 106 | 107 | impl PartialEq for EncodedValue { 108 | fn eq(&self, other: &bool) -> bool { 109 | match self { 110 | EncodedValue::Boolean(b) => b == other, 111 | _ => false, 112 | } 113 | } 114 | } 115 | 116 | impl PartialEq for EncodedValue { 117 | fn eq(&self, other: &Type) -> bool { 118 | match self { 119 | EncodedValue::Type(t) => t == other, 120 | _ => false, 121 | } 122 | } 123 | } 124 | 125 | impl PartialEq for EncodedValue { 126 | fn eq(&self, other: &DexString) -> bool { 127 | match self { 128 | EncodedValue::String(s) => s == other, 129 | _ => false, 130 | } 131 | } 132 | } 133 | 134 | impl PartialEq for EncodedValue { 135 | fn eq(&self, other: &str) -> bool { 136 | match self { 137 | EncodedValue::String(s) => s == other, 138 | _ => false, 139 | } 140 | } 141 | } 142 | 143 | macro_rules! gen_is_type_method { 144 | ($name: ident, $match_value: pat, $doc: literal) => { 145 | #[doc = $doc] 146 | pub fn $name(&self) -> bool { 147 | match self { 148 | $match_value => true, 149 | _ => false 150 | } 151 | } 152 | } 153 | } 154 | 155 | impl EncodedValue { 156 | gen_is_type_method!( 157 | is_byte, 158 | EncodedValue::Byte(_), 159 | "Returns `true` if the value is a byte" 160 | ); 161 | gen_is_type_method!( 162 | is_short, 163 | EncodedValue::Short(_), 164 | "Returns `true` if the value is a short" 165 | ); 166 | gen_is_type_method!( 167 | is_char, 168 | EncodedValue::Char(_), 169 | "Returns `true` if the value is a char" 170 | ); 171 | gen_is_type_method!( 172 | is_int, 173 | EncodedValue::Int(_), 174 | "Returns `true` if the value is a int" 175 | ); 176 | gen_is_type_method!( 177 | is_long, 178 | EncodedValue::Long(_), 179 | "Returns `true` if the value is a long" 180 | ); 181 | gen_is_type_method!( 182 | is_type, 183 | EncodedValue::Type(_), 184 | "Returns `true` if the value is a `Type`" 185 | ); 186 | gen_is_type_method!( 187 | is_float, 188 | EncodedValue::Float(_), 189 | "Returns `true` if the value is a float" 190 | ); 191 | gen_is_type_method!( 192 | is_double, 193 | EncodedValue::Double(_), 194 | "Returns `true` if the value is a double" 195 | ); 196 | gen_is_type_method!( 197 | is_method_handle, 198 | EncodedValue::MethodHandle(_), 199 | "Returns `true` if the value is a method handle" 200 | ); 201 | gen_is_type_method!( 202 | is_method_type, 203 | EncodedValue::MethodType(_), 204 | "Returns `true` if the value is a method type" 205 | ); 206 | gen_is_type_method!( 207 | is_string, 208 | EncodedValue::String(_), 209 | "Returns `true` if the value is a string" 210 | ); 211 | gen_is_type_method!( 212 | is_field, 213 | EncodedValue::Field(_), 214 | "Returns `true` if the value is a field" 215 | ); 216 | gen_is_type_method!( 217 | is_method, 218 | EncodedValue::Method(_), 219 | "Returns `true` if the value is a method" 220 | ); 221 | gen_is_type_method!( 222 | is_annotation, 223 | EncodedValue::Annotation(_), 224 | "Returns `true` if the value is a annotation" 225 | ); 226 | gen_is_type_method!( 227 | is_array, 228 | EncodedValue::Array(_), 229 | "Returns `true` if the value is an array" 230 | ); 231 | gen_is_type_method!( 232 | is_enum, 233 | EncodedValue::Enum(_), 234 | "Returns `true` if the value is an enum" 235 | ); 236 | gen_is_type_method!( 237 | is_bool, 238 | EncodedValue::Boolean(_), 239 | "Returns `true` if the value is a bool" 240 | ); 241 | gen_is_type_method!( 242 | is_null, 243 | EncodedValue::Null, 244 | "Returns `true` if the value is null" 245 | ); 246 | } 247 | 248 | /// [Android docs](https://source.android.com/devices/tech/dalvik/dex-format#value-formats) 249 | #[derive(FromPrimitive, Debug)] 250 | enum ValueType { 251 | Byte = 0x00, 252 | Short = 0x02, 253 | Char = 0x03, 254 | Int = 0x04, 255 | Long = 0x06, 256 | Float = 0x10, 257 | Double = 0x11, 258 | MethodType = 0x15, 259 | MethodHandle = 0x16, 260 | String = 0x17, 261 | Type = 0x18, 262 | Field = 0x19, 263 | Method = 0x1a, 264 | Enum = 0x1b, 265 | Array = 0x1c, 266 | Annotation = 0x1d, 267 | Null = 0x1e, 268 | Boolean = 0x1f, 269 | } 270 | 271 | macro_rules! try_extended_gread { 272 | ($source:expr,$offset:expr,$value_arg:expr,$size:expr,$sign_extended:literal) => {{ 273 | if *$offset + $value_arg >= $source.len() { 274 | return Err(Error::Scroll(scroll::Error::TooBig { 275 | size: *$offset + $value_arg, 276 | len: $source.len() 277 | })); 278 | } 279 | let mut bytes = [0x0; $size]; 280 | let (mut i, mut last_byte_is_neg) = (0, false); 281 | for value in $source[*$offset..=*$offset+$value_arg].iter() { 282 | bytes[i] = *value; 283 | i += 1; 284 | last_byte_is_neg = (*value as byte) < 0; 285 | } 286 | // fill the rest of the bytes with the value of the sign bit 287 | // if the last byte is negative, sign bit is 1. so we fill it 288 | // with 0xFF, for positive values sign bit is 0, so we don't need 289 | // to do anything 290 | // ref. https://en.wikipedia.org/wiki/Sign_extension 291 | if $sign_extended && last_byte_is_neg { 292 | while i < $size { 293 | bytes[i] = 0xFF; 294 | i += 1; 295 | } 296 | } 297 | debug!(target: "encoded-value", "bytes: {:?}", bytes); 298 | let value = bytes.pread_with(0, LE)?; 299 | *$offset += 1 + $value_arg; 300 | value 301 | }}; 302 | ($source:expr, $offset:expr, $value_arg:expr, $size:expr, ZERO) => {{ 303 | try_extended_gread!($source, $offset, $value_arg, $size, false) 304 | }}; 305 | ($source:expr, $offset:expr, $value_arg:expr, $size:expr, SIGN) => {{ 306 | try_extended_gread!($source, $offset, $value_arg, $size, true) 307 | }}; 308 | ($source:expr, $offset:expr, $value_arg:expr, $size:expr) => {{ 309 | try_extended_gread!($source, $offset, $value_arg, $size, ZERO) 310 | }}; 311 | 312 | } 313 | 314 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for EncodedValue 315 | where 316 | S: AsRef<[u8]>, 317 | { 318 | type Error = Error; 319 | type Size = usize; 320 | 321 | #[allow(clippy::cognitive_complexity)] 322 | fn try_from_ctx(source: &'a [u8], dex: &super::Dex) -> Result<(Self, Self::Size)> { 323 | let offset = &mut 0; 324 | let header: ubyte = source.gread(offset)?; 325 | let value_arg = (header >> 5) as usize; 326 | let value_type = 0b0001_1111 & header; 327 | let value_type = ValueType::from_u8(value_type) 328 | .ok_or_else(|| Error::InvalidId(format!("Invalid value type {}", value_type)))?; 329 | debug!(target: "encoded-value", "encoded value type: {:?}, value_arg: {}", value_type, value_arg); 330 | let value = match value_type { 331 | ValueType::Byte => { 332 | debug_assert_eq!(value_arg, 0); 333 | EncodedValue::Byte(try_extended_gread!(source, offset, value_arg, 1)) 334 | } 335 | ValueType::Short => { 336 | debug_assert!(value_arg < 2); 337 | EncodedValue::Short(try_extended_gread!(source, offset, value_arg, 2, SIGN)) 338 | } 339 | ValueType::Char => { 340 | debug_assert!(value_arg < 2); 341 | EncodedValue::Char(try_extended_gread!(source, offset, value_arg, 2)) 342 | } 343 | ValueType::Int => { 344 | debug_assert!(value_arg < 4); 345 | EncodedValue::Int(try_extended_gread!(source, offset, value_arg, 4, SIGN)) 346 | } 347 | ValueType::Long => { 348 | debug_assert!(value_arg < 8); 349 | EncodedValue::Long(try_extended_gread!(source, offset, value_arg, 8, SIGN)) 350 | } 351 | ValueType::Float => { 352 | debug_assert!(value_arg < 4); 353 | EncodedValue::Float(try_extended_gread!(source, offset, value_arg, 4)) 354 | } 355 | ValueType::Double => { 356 | debug_assert!(value_arg < 8); 357 | EncodedValue::Double(try_extended_gread!(source, offset, value_arg, 8)) 358 | } 359 | ValueType::MethodType => { 360 | debug_assert!(value_arg < 4); 361 | let proto_id: uint = try_extended_gread!(source, offset, value_arg, 4); 362 | EncodedValue::MethodType(dex.get_proto_item(ProtoId::from(proto_id))?) 363 | } 364 | ValueType::MethodHandle => { 365 | debug_assert!(value_arg < 4); 366 | let index: MethodHandleId = try_extended_gread!(source, offset, value_arg, 4); 367 | EncodedValue::MethodHandle(dex.get_method_handle_item(index)?) 368 | } 369 | ValueType::String => { 370 | debug_assert!(value_arg < 4); 371 | let string_id: StringId = try_extended_gread!(source, offset, value_arg, 4); 372 | EncodedValue::String(dex.get_string(string_id)?) 373 | } 374 | ValueType::Type => { 375 | debug_assert!(value_arg < 4); 376 | let type_id: TypeId = try_extended_gread!(source, offset, value_arg, 4); 377 | EncodedValue::Type(dex.get_type(type_id)?) 378 | } 379 | ValueType::Field => { 380 | debug_assert!(value_arg < 4); 381 | let index: uint = try_extended_gread!(source, offset, value_arg, 4); 382 | EncodedValue::Field(dex.get_field_item(FieldId::from(index))?) 383 | } 384 | ValueType::Method => { 385 | debug_assert!(value_arg < 4); 386 | let index: uint = try_extended_gread!(source, offset, value_arg, 4); 387 | EncodedValue::Method(dex.get_method_item(MethodId::from(index))?) 388 | } 389 | ValueType::Enum => { 390 | debug_assert!(value_arg < 4); 391 | let index: uint = try_extended_gread!(source, offset, value_arg, 4); 392 | EncodedValue::Enum(dex.get_field_item(FieldId::from(index))?) 393 | } 394 | ValueType::Array => { 395 | debug_assert!(value_arg == 0); 396 | let encoded_array: EncodedArray = source.gread_with(offset, dex)?; 397 | EncodedValue::Array(encoded_array.into_inner()) 398 | } 399 | ValueType::Annotation => { 400 | debug_assert!(value_arg == 0); 401 | EncodedValue::Annotation(source.gread_with(offset, dex)?) 402 | } 403 | ValueType::Null => { 404 | debug_assert!(value_arg == 0); 405 | EncodedValue::Null 406 | } 407 | ValueType::Boolean => { 408 | debug_assert!(value_arg < 2); 409 | EncodedValue::Boolean(value_arg == 1) 410 | } 411 | }; 412 | Ok((value, *offset)) 413 | } 414 | } 415 | 416 | /// Array of `EncodedValue`s 417 | #[derive(Debug, Default)] 418 | pub struct EncodedArray { 419 | values: Vec, 420 | } 421 | 422 | impl EncodedArray { 423 | pub(crate) fn into_inner(self) -> Vec { 424 | self.values 425 | } 426 | } 427 | 428 | impl<'a, S> ctx::TryFromCtx<'a, &super::Dex> for EncodedArray 429 | where 430 | S: AsRef<[u8]>, 431 | { 432 | type Error = Error; 433 | type Size = usize; 434 | 435 | fn try_from_ctx(source: &'a [u8], ctx: &super::Dex) -> super::Result<(Self, Self::Size)> { 436 | let offset = &mut 0; 437 | let size = Uleb128::read(source, offset)?; 438 | // TODO: find out why try_gread_vec_with! doesn't work here: fails in scroll 439 | debug!(target: "encoded-array", "encoded array size: {}", size); 440 | let mut values = Vec::with_capacity(size as usize); 441 | for _ in 0..size { 442 | values.push(source.gread_with(offset, ctx)?); 443 | } 444 | Ok((Self { values }, *offset)) 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /src/dex.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::BufReader, num::NonZeroUsize, ops::Range}; 2 | 3 | use adler32; 4 | use getset::{CopyGetters, Getters}; 5 | use memmap2::{Mmap, MmapOptions}; 6 | use num_derive::FromPrimitive; 7 | use num_traits::FromPrimitive; 8 | use scroll::{ctx, Pread}; 9 | 10 | use super::Result; 11 | use crate::{ 12 | annotation::{ 13 | AnnotationItem, AnnotationSetItem, AnnotationSetRefList, AnnotationsDirectoryItem, 14 | }, 15 | class::{Class, ClassDataItem, ClassDefItem, ClassDefItemIter}, 16 | code::{CodeItem, DebugInfoItem}, 17 | encoded_value::{EncodedArray, EncodedValue}, 18 | error::{self, Error}, 19 | field::{EncodedField, Field, FieldId, FieldIdItem}, 20 | jtype::{Type, TypeId}, 21 | method::{ 22 | EncodedMethod, Method, MethodHandleId, MethodHandleItem, MethodId, MethodIdItem, ProtoId, 23 | ProtoIdItem, 24 | }, 25 | search::Section, 26 | source::Source, 27 | string::{DexString, StringId, Strings, StringsIter}, 28 | ubyte, uint, ulong, ushort, utils, Endian, ENDIAN_CONSTANT, NO_INDEX, REVERSE_ENDIAN_CONSTANT, 29 | }; 30 | use std::path::Path; 31 | 32 | /// Dex file header 33 | #[derive(Debug, Pread, CopyGetters)] 34 | #[get_copy = "pub"] 35 | pub struct Header { 36 | /// Magic value that must appear at the beginning of the header section 37 | /// Contains dex\n\0 38 | magic: [ubyte; 8], 39 | /// Adler32 checksum of the rest of the file (everything but magic and this field); 40 | /// Used to detect file corruption. 41 | checksum: uint, 42 | /// SHA-1 signature (hash) of the rest of the file (everything but magic, checksum, and this field); 43 | /// Used to uniquely identify files. 44 | signature: [ubyte; 20], 45 | /// Size of the entire file (including the header), in bytes. 46 | file_size: uint, 47 | /// Size of the header in bytes. Usually 0x70. 48 | header_size: uint, 49 | /// Endianness tag 50 | /// A value of 0x12345678 denotes little-endian, 0x78563412 denotes byte-swapped form. 51 | endian_tag: [ubyte; 4], 52 | /// Size of the link section, or 0 if this file isn't statically linked 53 | link_size: uint, 54 | /// Offset from the start of the file to the link section 55 | /// The offset, if non-zero, should be into the link_data section. 56 | link_off: uint, 57 | /// Offset from the start of the file to the map item. 58 | /// Must be non-zero and into the data section. 59 | map_off: uint, 60 | /// Count of strings in the string identifiers list. 61 | string_ids_size: uint, 62 | /// Offset from the start of the file to the string identifiers list 63 | /// The offset, if non-zero, should be to the start of the string_ids section. 64 | string_ids_off: uint, 65 | /// Count of elements in the type identifiers list, at most 65535. 66 | type_ids_size: uint, 67 | /// Offset from the start of the file to the type identifiers list 68 | /// The offset, if non-zero, should be to the start of the type_ids section. 69 | type_ids_off: uint, 70 | /// Count of elements in the prototype identifiers list, at most 65535. 71 | proto_ids_size: uint, 72 | /// Offset from the start of the file to the prototype identifiers list. 73 | /// The offset, if non-zero, should be to the start of the proto_ids section. 74 | proto_ids_off: uint, 75 | /// Count of elements in the field identifiers list 76 | field_ids_size: uint, 77 | /// Offset from the start of the file to the field identifiers list 78 | /// The offset, if non-zero, should be to the start of the field_ids section 79 | field_ids_off: uint, 80 | /// Count of elements in the method identifiers list 81 | method_ids_size: uint, 82 | /// Offset from the start of the file to the method identifiers list. 83 | /// The offset, if non-zero, should be to the start of the method_ids section. 84 | method_ids_off: uint, 85 | /// Count of elements in the class definitions list 86 | class_defs_size: uint, 87 | /// Offset from the start of the file to the class definitions list. 88 | /// The offset, if non-zero, should be to the start of the class_defs section. 89 | class_defs_off: uint, 90 | /// Size of data section in bytes. Must be an even multiple of sizeof(uint). 91 | data_size: uint, 92 | /// Offset from the start of the file to the start of the data section. 93 | data_off: uint, 94 | } 95 | 96 | impl Header { 97 | fn data_section(&self) -> Range { 98 | self.data_off..self.data_off + self.data_size 99 | } 100 | } 101 | 102 | /// Wrapper type for Dex 103 | #[derive(Debug, Getters, CopyGetters)] 104 | pub(crate) struct DexInner { 105 | /// The header 106 | #[get = "pub"] 107 | header: Header, 108 | /// Contents of the map_list section 109 | #[get = "pub"] 110 | map_list: MapList, 111 | #[get_copy = "pub"] 112 | endian: Endian, 113 | } 114 | 115 | impl DexInner { 116 | pub(crate) fn strings_offset(&self) -> uint { 117 | self.header.string_ids_off 118 | } 119 | 120 | pub(crate) fn strings_len(&self) -> uint { 121 | self.header.string_ids_size 122 | } 123 | 124 | pub(crate) fn field_ids_len(&self) -> uint { 125 | self.header.field_ids_size 126 | } 127 | 128 | pub(crate) fn field_ids_offset(&self) -> uint { 129 | self.header.field_ids_off 130 | } 131 | 132 | pub(crate) fn class_defs_offset(&self) -> uint { 133 | self.header.class_defs_off 134 | } 135 | 136 | pub(crate) fn class_defs_len(&self) -> uint { 137 | self.header.class_defs_size 138 | } 139 | 140 | pub(crate) fn method_ids_offset(&self) -> uint { 141 | self.header.method_ids_off 142 | } 143 | 144 | pub(crate) fn method_ids_len(&self) -> uint { 145 | self.header.method_ids_size 146 | } 147 | 148 | pub(crate) fn proto_ids_offset(&self) -> uint { 149 | self.header.proto_ids_off 150 | } 151 | 152 | pub(crate) fn proto_ids_len(&self) -> uint { 153 | self.header.proto_ids_size 154 | } 155 | 156 | pub(crate) fn type_ids_offset(&self) -> uint { 157 | self.header.type_ids_off 158 | } 159 | 160 | pub(crate) fn type_ids_len(&self) -> uint { 161 | self.header.type_ids_size 162 | } 163 | 164 | fn data_section(&self) -> Range { 165 | self.header.data_section() 166 | } 167 | 168 | fn method_handles_offset(&self) -> Option { 169 | self.map_list.get_offset(ItemType::MethodHandleItem) 170 | } 171 | 172 | fn method_handles_len(&self) -> Option { 173 | self.map_list.get_len(ItemType::MethodHandleItem) 174 | } 175 | } 176 | 177 | // TODO: this should be try_from_dex 178 | impl<'a> ctx::TryFromCtx<'a, ()> for DexInner { 179 | type Error = error::Error; 180 | type Size = usize; 181 | 182 | fn try_from_ctx(source: &'a [u8], _: ()) -> Result<(Self, Self::Size)> { 183 | if source.len() <= 44 { 184 | debug!("malformed dex: size < minimum header size"); 185 | return Err(Error::MalFormed("Invalid dex file".to_string())); 186 | } 187 | let endian_tag = &source[40..44]; 188 | let endian = match (endian_tag[0], endian_tag[1], endian_tag[2], endian_tag[3]) { 189 | ENDIAN_CONSTANT => scroll::BE, 190 | REVERSE_ENDIAN_CONSTANT => scroll::LE, 191 | _ => return Err(error::Error::MalFormed("Bad endian tag".to_string())), 192 | }; 193 | let header = source.pread_with::
(0, endian)?; 194 | if !header.data_section().contains(&header.map_off) { 195 | return Err(error::Error::BadOffset( 196 | header.map_off as usize, 197 | "map_list not in data section".to_string(), 198 | )); 199 | } 200 | let found = header.checksum(); 201 | let computed = adler32::adler32(BufReader::new(&source[12..]))?; 202 | if computed != found { 203 | return Err(Error::MalFormed(format!( 204 | "File corrupted, adler32 checksum doesn't match: computed: {}, found: {}", 205 | computed, found 206 | ))); 207 | } 208 | 209 | let map_list = source.pread_with(header.map_off as usize, endian)?; 210 | debug!(target: "initialization", "header: {:?}, endian-ness: {:?}", header, endian); 211 | debug!(target: "initialization", "map_list: {:?}", map_list); 212 | Ok(( 213 | DexInner { 214 | header, 215 | map_list, 216 | endian, 217 | }, 218 | 0, 219 | )) 220 | } 221 | } 222 | 223 | /// List of the entire contents of a file, in order. A given type must appear at most 224 | /// once in a map, entries must be ordered by initial offset and must not overlap. 225 | #[derive(Debug)] 226 | pub struct MapList { 227 | map_items: Vec, 228 | } 229 | 230 | impl<'a> ctx::TryFromCtx<'a, Endian> for MapList { 231 | type Error = error::Error; 232 | type Size = usize; 233 | 234 | fn try_from_ctx(source: &'a [u8], endian: Endian) -> Result<(Self, Self::Size)> { 235 | let offset = &mut 0; 236 | let size: uint = source.gread_with(offset, endian)?; 237 | Ok(( 238 | Self { 239 | map_items: try_gread_vec_with!(source, offset, size, endian), 240 | }, 241 | *offset, 242 | )) 243 | } 244 | } 245 | 246 | impl MapList { 247 | /// Returns the `MapItem` corresponding to the `ItemType`. 248 | pub fn get(&self, item_type: ItemType) -> Option { 249 | self.map_items 250 | .iter() 251 | .find(|map_item| map_item.item_type == item_type) 252 | .cloned() 253 | } 254 | 255 | /// Returns the offset of the item corresponding to the `ItemType`. 256 | pub fn get_offset(&self, item_type: ItemType) -> Option { 257 | self.get(item_type).map(|map_item| map_item.offset) 258 | } 259 | 260 | /// Returns the length of the item corresponding to the `ItemType`. 261 | pub fn get_len(&self, item_type: ItemType) -> Option { 262 | self.get(item_type).map(|map_item| map_item.size) 263 | } 264 | } 265 | 266 | /// ItemType that appear in MapList 267 | #[derive(FromPrimitive, Debug, Clone, Copy, Eq, PartialEq)] 268 | pub enum ItemType { 269 | Header = 0x0, 270 | StringIdItem = 0x1, 271 | TypeIdItem = 0x2, 272 | ProtoIdItem = 0x3, 273 | FieldIdItem = 0x4, 274 | MethodIdItem = 0x5, 275 | ClassDefItem = 0x6, 276 | CallSiteIdItem = 0x7, 277 | MethodHandleItem = 0x8, 278 | MapList = 0x1000, 279 | TypeList = 0x1001, 280 | AnnotationSetRefList = 0x1002, 281 | AnnotationSetItem = 0x1003, 282 | ClassDataItem = 0x2000, 283 | CodeItem = 0x2001, 284 | StringDataItem = 0x2002, 285 | DebugInfoItem = 0x2003, 286 | AnnotationItem = 0x2004, 287 | EncodedArrayItem = 0x2005, 288 | AnnotationsDirectoryItem = 0x2006, 289 | HiddenAPIClassDataItem = 0xF000, 290 | } 291 | 292 | /// Single item of the MapList. 293 | #[derive(Debug, Clone, Copy, CopyGetters)] 294 | #[get_copy = "pub"] 295 | pub struct MapItem { 296 | /// Type of the current item 297 | item_type: ItemType, 298 | /// Count of the number of items to be found at the indicated offset 299 | size: uint, 300 | /// Offset from the start of the file to the current item type 301 | offset: uint, 302 | } 303 | 304 | impl<'a> ctx::TryFromCtx<'a, Endian> for MapItem { 305 | type Error = error::Error; 306 | type Size = usize; 307 | 308 | fn try_from_ctx(source: &'a [u8], endian: Endian) -> Result<(Self, Self::Size)> { 309 | let offset = &mut 0; 310 | let item_type: ushort = source.gread_with(offset, endian)?; 311 | let item_type = ItemType::from_u16(item_type).ok_or_else(|| { 312 | Error::InvalidId(format!("Invalid item type in map_list: {}", item_type)) 313 | })?; 314 | let _: ushort = source.gread_with(offset, endian)?; 315 | let size: uint = source.gread_with(offset, endian)?; 316 | let item_offset: uint = source.gread_with(offset, endian)?; 317 | Ok(( 318 | Self { 319 | item_type, 320 | size, 321 | offset: item_offset, 322 | }, 323 | *offset, 324 | )) 325 | } 326 | } 327 | 328 | /// Represents a Dex file 329 | #[derive(Debug)] 330 | pub struct Dex { 331 | /// Source from which this Dex file is loaded from. 332 | pub(crate) source: Source, 333 | /// Items in string_ids section are cached here. 334 | pub(crate) strings: Strings, 335 | pub(crate) inner: DexInner, 336 | } 337 | 338 | impl Dex 339 | where 340 | T: AsRef<[u8]>, 341 | { 342 | /// The Header section 343 | pub fn header(&self) -> &Header { 344 | self.inner.header() 345 | } 346 | 347 | pub fn map_list(&self) -> &MapList { 348 | &self.inner.map_list 349 | } 350 | 351 | pub(crate) fn is_offset_in_data_section(&self, offset: uint) -> bool { 352 | self.inner.data_section().contains(&offset) 353 | } 354 | 355 | /// Source file name in which a class is defined. 356 | pub fn get_source_file(&self, file_id: StringId) -> Result> { 357 | Ok(if file_id == NO_INDEX { 358 | None 359 | } else { 360 | Some(self.get_string(file_id)?) 361 | }) 362 | } 363 | 364 | /// Returns a reference to the `DexString` represented by the given id. 365 | pub fn get_string(&self, string_id: StringId) -> Result { 366 | if self.inner.strings_len() <= string_id { 367 | return Err(Error::InvalidId(format!( 368 | "Invalid string id: {}", 369 | string_id 370 | ))); 371 | } 372 | self.strings.get(string_id) 373 | } 374 | 375 | /// Returns the `Type` corresponding to the descriptor. 376 | pub fn get_type_from_descriptor(&self, descriptor: &str) -> Result> { 377 | if let Some(string_id) = self.strings.get_id(descriptor)? { 378 | if let Some(type_id) = self.get_type_id(string_id)? { 379 | return Ok(Some(self.get_type(type_id)?)); 380 | } 381 | } 382 | Ok(None) 383 | } 384 | 385 | /// Returns the `Type` represented by the give type_id. 386 | pub fn get_type(&self, type_id: TypeId) -> Result { 387 | let max_offset = self.inner.type_ids_offset() + (self.inner.type_ids_len() - 1) * 4; 388 | let offset = self.inner.type_ids_offset() + type_id * 4; 389 | if offset > max_offset { 390 | return Err(Error::InvalidId(format!("Invalid type id: {}", type_id))); 391 | } 392 | let string_id = self 393 | .source 394 | .as_ref() 395 | .pread_with(offset as usize, self.get_endian())?; 396 | self.get_string(string_id).map(|type_descriptor| Type { 397 | id: type_id, 398 | type_descriptor, 399 | }) 400 | } 401 | 402 | pub(crate) fn get_type_id(&self, string_id: StringId) -> Result> { 403 | let types_section = self.type_ids_section(); 404 | Ok(types_section 405 | .binary_search( 406 | &string_id, 407 | self.get_endian(), 408 | |value: &StringId, element| Ok((element).cmp(value)), 409 | )? 410 | .map(|s| s as TypeId)) 411 | } 412 | 413 | pub(crate) fn type_ids_section(&self) -> Section { 414 | let type_ids_offset = self.inner.type_ids_offset() as usize; 415 | let (start, end) = ( 416 | type_ids_offset, 417 | type_ids_offset + self.inner.type_ids_len() as usize * 4, 418 | ); 419 | let type_ids_section = &self.source[start..end]; 420 | Section::new(type_ids_section) 421 | } 422 | 423 | #[allow(unused)] 424 | pub(crate) fn class_defs_section(&self) -> Section { 425 | let class_defs_offset = self.inner.class_defs_offset() as usize; 426 | let (start, end) = ( 427 | class_defs_offset, 428 | class_defs_offset + self.inner.class_defs_len() as usize * 32, 429 | ); 430 | let class_defs_section = &self.source[start..end]; 431 | Section::new(class_defs_section) 432 | } 433 | 434 | pub(crate) fn find_class_by_type(&self, type_id: TypeId) -> Result> { 435 | for class_def in self.class_defs() { 436 | let class_def = class_def?; 437 | if class_def.class_idx == type_id { 438 | return Ok(Some(Class::try_from_dex(self, &class_def)?)); 439 | } 440 | } 441 | Ok(None) 442 | } 443 | 444 | /// Finds `Class` by the given class name. The name should be in smali format. 445 | /// This method uses binary search to find the class definition using the property 446 | /// that the strings, type ids and class defs sections are in sorted. 447 | pub fn find_class_by_name(&self, type_descriptor: &str) -> Result> { 448 | let string_id = self.strings.get_id(type_descriptor)?; 449 | if string_id.is_none() { 450 | debug!(target: "find-class-by-name", "class name: {} not found in strings", type_descriptor); 451 | return Ok(None); 452 | } 453 | let type_id = self.get_type_id(string_id.unwrap())?; 454 | if type_id.is_none() { 455 | debug!(target: "find-class-by-name", "no type id found for string id: {}", string_id.unwrap()); 456 | return Ok(None); 457 | } 458 | self.find_class_by_type(type_id.unwrap()) 459 | } 460 | 461 | /// Returns the list of types which represent the interfaces of a class. 462 | pub fn get_interfaces(&self, offset: uint) -> Result> { 463 | debug!(target: "interfaces", "interfaces offset: {}", offset); 464 | if offset == 0 { 465 | return Ok(Default::default()); 466 | } 467 | if !self.is_offset_in_data_section(offset) { 468 | return Err(Error::BadOffset( 469 | offset as usize, 470 | "Interfaces offset not in data section".to_string(), 471 | )); 472 | } 473 | let mut offset = offset as usize; 474 | let source = &self.source; 475 | let endian = self.get_endian(); 476 | let len = source.gread_with::(&mut offset, endian)?; 477 | debug!(target: "interfaces", "interfaces length: {}", len); 478 | let offset = &mut offset; 479 | let type_ids: Vec = try_gread_vec_with!(source, offset, len, endian); 480 | utils::get_types(self, &type_ids) 481 | } 482 | 483 | /// Returns the `FieldIdItem` represented by a `FieldId`. 484 | pub fn get_field_item(&self, field_id: FieldId) -> Result { 485 | let offset = ulong::from(self.inner.field_ids_offset()) + field_id * 8; 486 | let max_offset = self.inner.field_ids_offset() + (self.inner.field_ids_len() - 1) * 8; 487 | let max_offset = ulong::from(max_offset); 488 | debug!(target: "field-id-item", "current offset: {}, min_offset: {}, max_offset: {}", 489 | offset, self.inner.field_ids_offset(), max_offset); 490 | if offset > max_offset { 491 | return Err(error::Error::InvalidId(format!( 492 | "Invalid field id: {}", 493 | field_id 494 | ))); 495 | } 496 | FieldIdItem::try_from_dex(self, offset, field_id) 497 | } 498 | 499 | /// Returns the `ProtoIdItem` represented by `ProtoId`. 500 | pub fn get_proto_item(&self, proto_id: ProtoId) -> Result { 501 | let offset = ulong::from(self.inner.proto_ids_offset()) + proto_id * 12; 502 | let max_offset = ulong::from(self.inner.proto_ids_offset()) 503 | + ulong::from((self.inner.proto_ids_len() - 1) * 12); 504 | debug!(target: "proto-item", "proto item current offset: {}, min_offset: {}, max_offset: {}", 505 | offset, self.inner.proto_ids_offset(), max_offset); 506 | if offset > max_offset { 507 | return Err(error::Error::InvalidId(format!( 508 | "Invalid proto id: {}", 509 | proto_id 510 | ))); 511 | } 512 | ProtoIdItem::try_from_dex(self, offset) 513 | } 514 | 515 | /// Returns the `MethodIdItem` represented by `MethodId`. 516 | pub fn get_method_item(&self, method_id: MethodId) -> Result { 517 | let offset = ulong::from(self.inner.method_ids_offset()) + method_id * 8; 518 | let max_offset = self.inner.method_ids_offset() + (self.inner.method_ids_len() - 1) * 8; 519 | let max_offset = ulong::from(max_offset); 520 | debug!(target: "method-item", "method item current offset: {}, min_offset: {}, max_offset: {}", 521 | offset, self.inner.method_ids_offset(), max_offset); 522 | if offset > max_offset { 523 | return Err(error::Error::InvalidId(format!( 524 | "Invalid method id: {}", 525 | method_id 526 | ))); 527 | } 528 | MethodIdItem::try_from_dex(self, offset, method_id) 529 | } 530 | 531 | /// Iterator over the strings 532 | pub fn strings(&self) -> impl Iterator> { 533 | StringsIter::new(self.strings.clone(), self.inner.strings_len() as usize) 534 | } 535 | 536 | /// Returns a `Field` given its component items. 537 | pub fn get_field( 538 | &self, 539 | encoded_field: &EncodedField, 540 | initial_value: Option, 541 | annotations: AnnotationSetItem, 542 | ) -> Result { 543 | Field::try_from_dex(self, encoded_field, initial_value, annotations) 544 | } 545 | 546 | /// Returns a `Method` given its component items. 547 | pub fn get_method( 548 | &self, 549 | encoded_method: &EncodedMethod, 550 | method_annotations: AnnotationSetItem, 551 | parameter_annotations: AnnotationSetRefList, 552 | ) -> Result { 553 | Method::try_from_dex( 554 | self, 555 | encoded_method, 556 | method_annotations, 557 | parameter_annotations, 558 | ) 559 | } 560 | 561 | /// Returns the `ClassDataItem` at the given offset. 562 | pub fn get_class_data(&self, offset: uint) -> Result> { 563 | debug!(target: "class-data", "class data offset: {}", offset); 564 | if offset == 0 { 565 | return Ok(None); 566 | } 567 | if !self.is_offset_in_data_section(offset) { 568 | return Err(Error::BadOffset( 569 | offset as usize, 570 | "ClassData offset not in data section".to_string(), 571 | )); 572 | } 573 | Ok(Some(self.source.pread_with(offset as usize, self)?)) 574 | } 575 | 576 | /// Returns the `MethodHandleItem` represented by the `MethodHandleId`. 577 | pub fn get_method_handle_item( 578 | &self, 579 | method_handle_id: MethodHandleId, 580 | ) -> Result { 581 | let err = || Error::InvalidId(format!("Invalid method handle id: {}", method_handle_id)); 582 | let offset = self.inner.method_handles_offset().ok_or_else(err)?; 583 | let len = self.inner.method_handles_len().ok_or_else(err)?; 584 | let max_offset = offset + (len - 1) * 8; 585 | let offset = offset + method_handle_id * 8; 586 | if offset > max_offset { 587 | return Err(err()); 588 | } 589 | self.source.gread_with(&mut (offset as usize), self) 590 | } 591 | 592 | /// Returns the endianness in the header section. 593 | pub fn get_endian(&self) -> Endian { 594 | self.inner.endian() 595 | } 596 | 597 | /// Iterator over the class_defs section. 598 | pub fn class_defs(&self) -> impl Iterator> + '_ { 599 | let defs_len = self.inner.class_defs_len(); 600 | let defs_offset = self.inner.class_defs_offset(); 601 | let source = self.source.clone(); 602 | let endian = self.get_endian(); 603 | ClassDefItemIter::new(source, defs_offset, defs_len, endian) 604 | } 605 | 606 | /// Iterator over the type_ids section. 607 | pub fn types(&self) -> impl Iterator> + '_ { 608 | let type_ids_len = self.inner.type_ids_len(); 609 | (0..type_ids_len).map(move |type_id| self.get_type(type_id)) 610 | } 611 | 612 | /// Iterator over the proto_ids section. 613 | pub fn proto_ids(&self) -> impl Iterator> + '_ { 614 | let proto_ids_len = self.inner.proto_ids_len(); 615 | (0..proto_ids_len).map(move |proto_id| self.get_proto_item(ProtoId::from(proto_id))) 616 | } 617 | 618 | /// Iterator over the field_ids section. 619 | pub fn field_ids(&self) -> impl Iterator> + '_ { 620 | let field_ids_len = self.inner.field_ids_len(); 621 | (0..field_ids_len).map(move |field_id| self.get_field_item(FieldId::from(field_id))) 622 | } 623 | 624 | /// Iterator over the method_ids section. 625 | pub fn method_ids(&self) -> impl Iterator> + '_ { 626 | let method_ids_len = self.inner.method_ids_len(); 627 | (0..method_ids_len).map(move |method_id| self.get_method_item(MethodId::from(method_id))) 628 | } 629 | 630 | /// Iterator over the method_handles section. 631 | pub fn method_handles(&self) -> impl Iterator> + '_ { 632 | let method_handles_len = self.inner.method_handles_len().unwrap_or(0); 633 | (0..method_handles_len).map(move |method_handle_id| { 634 | self.get_method_handle_item(MethodHandleId::from(method_handle_id)) 635 | }) 636 | } 637 | 638 | /// Iterator over the classes 639 | pub fn classes(&self) -> impl Iterator> + '_ { 640 | self.class_defs() 641 | .map(move |class_def_item| Class::try_from_dex(&self, &class_def_item?)) 642 | } 643 | 644 | /// Returns the `CodeItem` at the offset. 645 | pub fn get_code_item(&self, code_off: ulong) -> Result> { 646 | if code_off == 0 { 647 | return Ok(None); 648 | } 649 | if !self.is_offset_in_data_section(code_off as uint) { 650 | return Err(Error::BadOffset( 651 | code_off as usize, 652 | "CodeItem offset not in data section".to_string(), 653 | )); 654 | } 655 | Ok(Some(self.source.pread_with(code_off as usize, self)?)) 656 | } 657 | 658 | /// Returns the `AnnotationItem` at the offset. 659 | pub fn get_annotation_item(&self, annotation_off: uint) -> Result { 660 | debug!(target: "annotaion-item", "annotation item offset: {}", annotation_off); 661 | if !self.is_offset_in_data_section(annotation_off) { 662 | return Err(Error::BadOffset( 663 | annotation_off as usize, 664 | "AnnotationItem offset not in data section".to_string(), 665 | )); 666 | } 667 | Ok(self.source.pread_with(annotation_off as usize, self)?) 668 | } 669 | 670 | /// Returns the `AnnotationSetItem` at the offset. 671 | pub fn get_annotation_set_item( 672 | &self, 673 | annotation_set_item_off: uint, 674 | ) -> Result { 675 | debug!(target: "annotation-set-item", "annotation set item offset: {}", annotation_set_item_off); 676 | if annotation_set_item_off == 0 { 677 | return Ok(Default::default()); 678 | } 679 | if !self.is_offset_in_data_section(annotation_set_item_off) { 680 | return Err(Error::BadOffset( 681 | annotation_set_item_off as usize, 682 | "AnnotationSetItem offset not in data section".to_string(), 683 | )); 684 | } 685 | self.source 686 | .pread_with(annotation_set_item_off as usize, self) 687 | } 688 | 689 | /// Returns the `AnnotationSetRefList` at the offset. 690 | pub fn get_annotation_set_ref_list( 691 | &self, 692 | annotation_set_ref_list_off: uint, 693 | ) -> Result { 694 | if !self.is_offset_in_data_section(annotation_set_ref_list_off) { 695 | return Err(Error::BadOffset( 696 | annotation_set_ref_list_off as usize, 697 | "AnnotationSetRefList offset not in data section".to_string(), 698 | )); 699 | } 700 | Ok(self 701 | .source 702 | .pread_with(annotation_set_ref_list_off as usize, self)?) 703 | } 704 | 705 | /// Returns the `EncodedArray` representing the static values of a class at the given offset. 706 | pub fn get_static_values(&self, static_values_off: uint) -> Result { 707 | debug!(target: "class", "static values offset: {}", static_values_off); 708 | if static_values_off == 0 { 709 | return Ok(Default::default()); 710 | } 711 | if !self.is_offset_in_data_section(static_values_off) { 712 | return Err(Error::BadOffset( 713 | static_values_off as usize, 714 | "Class static values offset not in data section".to_string(), 715 | )); 716 | } 717 | self.source.pread_with(static_values_off as usize, self) 718 | } 719 | 720 | /// Returns the `AnnotationsDirectoryItem` at the offset. 721 | pub fn get_annotations_directory_item( 722 | &self, 723 | annotations_directory_item_off: uint, 724 | ) -> Result { 725 | debug!(target: "class", "annotations directory offset: {}", annotations_directory_item_off); 726 | if annotations_directory_item_off == 0 { 727 | return Ok(Default::default()); 728 | } 729 | if !self.is_offset_in_data_section(annotations_directory_item_off) { 730 | return Err(Error::BadOffset( 731 | annotations_directory_item_off as usize, 732 | "Annotations directory offset not in data section".to_string(), 733 | )); 734 | } 735 | self.source 736 | .pread_with(annotations_directory_item_off as usize, self) 737 | } 738 | 739 | /// Returns the `DebugInfoItem` at the offset. 740 | pub fn get_debug_info_item(&self, debug_info_off: uint) -> Result { 741 | if !self.is_offset_in_data_section(debug_info_off) { 742 | return Err(Error::BadOffset( 743 | debug_info_off as usize, 744 | "DebugInfoItem offset not in data section".to_string(), 745 | )); 746 | } 747 | 748 | Ok(self.source.pread_with(debug_info_off as usize, self)?) 749 | } 750 | } 751 | 752 | /// Reader facade for loading a `Dex` 753 | pub struct DexReader; 754 | 755 | impl DexReader { 756 | /// Try to read a `Dex` from the given path, returns error if 757 | /// the file is not a dex or in case of I/O errors 758 | pub fn from_file>(file: P) -> Result> { 759 | let map = unsafe { MmapOptions::new().map(&File::open(file.as_ref())?)? }; 760 | let inner: DexInner = map.pread(0)?; 761 | let endian = inner.endian(); 762 | let source = Source::new(map); 763 | let cache = Strings::new( 764 | source.clone(), 765 | endian, 766 | inner.strings_offset(), 767 | inner.strings_len(), 768 | NonZeroUsize::new(4096).unwrap(), 769 | inner.data_section(), 770 | ); 771 | Ok(Dex { 772 | source: source.clone(), 773 | strings: cache, 774 | inner, 775 | }) 776 | } 777 | 778 | /// Loads a `Dex` from a `Vec` 779 | pub fn from_vec>(buf: B) -> Result> { 780 | let inner: DexInner = buf.as_ref().pread(0)?; 781 | let endian = inner.endian(); 782 | let source = Source::new(buf); 783 | let cache = Strings::new( 784 | source.clone(), 785 | endian, 786 | inner.strings_offset(), 787 | inner.strings_len(), 788 | NonZeroUsize::new(4096).unwrap(), 789 | inner.data_section(), 790 | ); 791 | Ok(Dex { 792 | source: source.clone(), 793 | strings: cache, 794 | inner, 795 | }) 796 | } 797 | } 798 | 799 | #[cfg(test)] 800 | mod tests { 801 | 802 | use memmap2::MmapOptions; 803 | use std::fs::File; 804 | use super::Result; 805 | use std::path::Path; 806 | 807 | #[test] 808 | fn test_find_class_by_name() { 809 | let dex = 810 | super::DexReader::from_file("resources/classes.dex").expect("cannot open dex file"); 811 | let mut count = 0; 812 | for class_def in dex.class_defs() { 813 | let class_def = class_def.expect("can't load class"); 814 | let jtype = dex.get_type(class_def.class_idx()).expect("bad type"); 815 | let result = dex.find_class_by_name(&jtype.type_descriptor().to_string()); 816 | assert!(result.is_ok()); 817 | assert!(result.unwrap().is_some()); 818 | count += 1; 819 | } 820 | assert!(count > 0); 821 | } 822 | 823 | fn load_example_dex_as_vec>(file: P) -> Result> { 824 | let map = unsafe { MmapOptions::new().map(&File::open(file.as_ref())?)? }; 825 | let data = map.to_vec(); 826 | Ok(data) 827 | } 828 | 829 | #[test] 830 | fn test_find_class_by_name_from_vec() { 831 | let data: Vec = load_example_dex_as_vec("resources/classes.dex") 832 | .expect("Cannot load example file to a vec"); 833 | let dex = super::DexReader::from_vec(data).expect("Cannot parse dex from vec"); 834 | let mut count = 0; 835 | for class_def in dex.class_defs() { 836 | let class_def = class_def.expect("can't load class"); 837 | let jtype = dex.get_type(class_def.class_idx()).expect("bad type"); 838 | let result = dex.find_class_by_name(&jtype.type_descriptor().to_string()); 839 | assert!(result.is_ok()); 840 | assert!(result.unwrap().is_some()); 841 | count += 1; 842 | } 843 | assert!(count > 0); 844 | } 845 | 846 | #[test] 847 | fn test_get_type_from_descriptor() { 848 | let dex = 849 | super::DexReader::from_file("resources/classes.dex").expect("cannot open dex file"); 850 | let jtype = dex.get_type_from_descriptor("Lorg/adw/launcher/Launcher;"); 851 | assert!(jtype.is_ok()); 852 | let jtype = jtype.unwrap(); 853 | assert!(jtype.is_some()); 854 | let jtype = jtype.unwrap(); 855 | assert_eq!(jtype.type_descriptor(), "Lorg/adw/launcher/Launcher;") 856 | } 857 | } 858 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, fs, 3 | path::{Path, PathBuf}, 4 | process::Command, 5 | }; 6 | 7 | use tempfile::TempDir; 8 | use dex::code::DebugInfoBytecode; 9 | use dex::field::FieldId; 10 | use dex::method::MethodId; 11 | use dex::string::DexString; 12 | 13 | struct TestBuilder { 14 | root: TempDir, 15 | sources: Vec, 16 | } 17 | 18 | impl TestBuilder { 19 | /// Initialize a new tmp directory 20 | pub fn new() -> Self { 21 | Self { 22 | root: TempDir::new().expect("cannot create temporary directory"), 23 | sources: Vec::new(), 24 | } 25 | } 26 | 27 | #[allow(unused)] 28 | pub fn add_file, P: AsRef>(&mut self, src: P, dest: Q) { 29 | let dest = self.root.path().join(dest); 30 | let src_display = src.as_ref().display(); 31 | let dest_display: &Path = dest.as_ref(); 32 | fs::copy(&src, &dest).expect(&format!( 33 | "unable to copy {} to {}", 34 | src_display, 35 | dest_display.display() 36 | )); 37 | self.sources.push(dest); 38 | } 39 | 40 | pub fn add_src>(&mut self, path: P, code: &str) { 41 | let dest = self.root.path().join(path); 42 | fs::write(&dest, code).expect(&format!("unable to write code to path: {}", dest.display())); 43 | self.sources.push(dest); 44 | } 45 | 46 | fn get_class_names(&self) -> Vec { 47 | // TODO: check case for inner classes 48 | self.sources 49 | .iter() 50 | .filter_map(|p| { 51 | let filename = p.to_str().unwrap(); 52 | if filename.ends_with(".java") { 53 | Some(filename.trim_end_matches(".java").to_owned() + ".class") 54 | } else { 55 | None 56 | } 57 | }) 58 | .collect() 59 | } 60 | 61 | fn compile(&self) -> PathBuf { 62 | let android_lib_path = env::var("ANDROID_LIB_PATH").expect("$ANDROID_LIB_PATH not set"); 63 | let _javac = Command::new("javac") 64 | .args(&self.sources) 65 | .arg("-g") 66 | .current_dir(self.root.path()) 67 | .status() 68 | .expect("javac failed"); 69 | let classes = self.get_class_names(); 70 | assert!(classes.len() > 0); 71 | let _d8 = Command::new("d8") 72 | .args(&classes) 73 | .args(&["--lib", &android_lib_path]) 74 | .args(&["--output", &self.root.path().display().to_string()]) 75 | .current_dir(self.root.path()) 76 | .status() 77 | .expect(&format!("'d8 {:?}' failed", &classes)); 78 | self.root.path().join("classes.dex") 79 | } 80 | } 81 | 82 | macro_rules! assert_has_access_flags { 83 | ($item: ident, [ $($flag: ident),+ ], $msg:expr) => { 84 | $( 85 | assert!($item.$flag(), $msg); 86 | )* 87 | }; 88 | 89 | ($item: ident, [ $($flag: ident),+ ]) => { 90 | assert_has_access_flags!($item, [$($flag),+], "") 91 | } 92 | } 93 | 94 | // TODO: support test attributes if necessary 95 | macro_rules! test { 96 | ($test_name: ident, $({ $fname:expr => $code:expr });+,$test_func:expr) => { 97 | #[test] 98 | fn $test_name() { 99 | use dex::DexReader; 100 | let mut builder = TestBuilder::new(); 101 | $( 102 | builder.add_src($fname, $code); 103 | )* 104 | let dex_path = builder.compile(); 105 | let dex = DexReader::from_file(dex_path.as_path()); 106 | assert!(dex.is_ok()); 107 | $test_func(dex.unwrap()); 108 | } 109 | }; 110 | 111 | ($test_name: ident, $({ $fname:expr => $code:expr }),+) => { 112 | test!($test_name, $({$fname => $code},)+ |_| {}); 113 | } 114 | } 115 | 116 | test!( 117 | test_dex_from_file_works, 118 | { 119 | "Main.java" => 120 | r#" 121 | class Main { 122 | public static void main(String[] args) { 123 | System.out.println("1 + 1 = " + 1 + 1); 124 | } 125 | } 126 | "# 127 | } 128 | ); 129 | 130 | test!( 131 | test_find_class_by_name, 132 | { 133 | "Main.java" => r#" 134 | class Main {} 135 | "# 136 | }; 137 | { 138 | "Day.java" => r#" 139 | public enum Day { 140 | SUNDAY, MONDAY, TUESDAY, WEDNESDAY, 141 | THURSDAY, FRIDAY, SATURDAY 142 | } 143 | "# 144 | }; 145 | { 146 | "SuperClass.java" => r#" 147 | class SuperClass {} 148 | "# 149 | }; 150 | { 151 | "MyInterface.java" => r#" 152 | interface MyInterface { 153 | String interfaceMethod(int x, String y); 154 | } 155 | "# 156 | }, 157 | |dex: dex::Dex<_>| { 158 | assert_eq!(dex.header().class_defs_size(), 4); 159 | let find = |name| { 160 | let class = dex.find_class_by_name(name); 161 | assert!(class.is_ok()); 162 | let class = class.unwrap(); 163 | assert!(class.is_some()); 164 | class.unwrap() 165 | }; 166 | let interface = find("LMyInterface;"); 167 | assert!(interface.is_interface()); 168 | 169 | let enum_class = find("LDay;"); 170 | assert!(enum_class.is_enum()); 171 | } 172 | ); 173 | 174 | test!( 175 | test_class_exists, 176 | { 177 | "Main.java" => 178 | r#" 179 | class Main {} 180 | "# 181 | }, 182 | |dex: dex::Dex<_>| { 183 | let class = dex.find_class_by_name("LMain;"); 184 | assert!(class.is_ok()); 185 | let class = class.unwrap(); 186 | assert!(class.is_some()); 187 | } 188 | ); 189 | 190 | test!( 191 | test_annotations, 192 | { 193 | "Annotation.java" => r#" 194 | public @interface Annotation { 195 | public String name(); 196 | public int value(); 197 | } 198 | "# 199 | }; 200 | { 201 | "RuntimeAnnotation.java" => r#" 202 | import java.lang.annotation.Retention; 203 | import java.lang.annotation.RetentionPolicy; 204 | @Retention(RetentionPolicy.RUNTIME) 205 | @interface RuntimeAnnotation {} 206 | "# 207 | }; 208 | { 209 | "Main.java" => r#" 210 | @RuntimeAnnotation 211 | public class Main { 212 | @RuntimeAnnotation @Annotation(name = "name", value = 5) private T field; 213 | @RuntimeAnnotation public void myMethod(@RuntimeAnnotation final int param1, T param2) {} 214 | } 215 | "# 216 | }, 217 | |dex: dex::Dex<_>| { 218 | use dex::annotation::Visibility; 219 | use dex::encoded_value::EncodedValue; 220 | 221 | let annotation_class = dex.find_class_by_name("LAnnotation;"); 222 | assert!(annotation_class.is_ok()); 223 | let annotation_class = annotation_class.unwrap(); 224 | assert!(annotation_class.is_some()); 225 | let annotation_class = annotation_class.unwrap(); 226 | assert_has_access_flags!(annotation_class, [is_public, is_annotation]); 227 | assert_eq!(annotation_class.methods().count(), 2); 228 | 229 | let runtime_annotation_class = dex.find_class_by_name("LRuntimeAnnotation;"); 230 | assert!(runtime_annotation_class.is_ok()); 231 | let runtime_annotation_class = runtime_annotation_class.unwrap(); 232 | assert!(runtime_annotation_class.is_some()); 233 | let runtime_annotation_class = runtime_annotation_class.unwrap(); 234 | assert_has_access_flags!(runtime_annotation_class, [is_annotation]); 235 | 236 | 237 | let class = dex.find_class_by_name("LMain;"); 238 | assert!(class.is_ok()); 239 | let class = class.unwrap(); 240 | assert!(class.is_some()); 241 | let class = class.unwrap(); 242 | let field = class.fields().find(|f| f.name() == "field"); 243 | assert!(field.is_some()); 244 | let field = field.unwrap(); 245 | 246 | let annotation_item = field.annotations().iter().find(|i| i.jtype() == "LAnnotation;"); 247 | assert!(annotation_item.is_some()); 248 | let annotation_item = annotation_item.unwrap(); 249 | assert_eq!(annotation_item.visibility(), Visibility::Build); 250 | let name = annotation_item.find_element("name"); 251 | assert!(name.is_some()); 252 | assert_eq!(name.unwrap().value(), "name"); 253 | 254 | let value = annotation_item.find_element("value"); 255 | assert!(value.is_some()); 256 | assert_eq!(*value.unwrap().value(), EncodedValue::Int(5)); 257 | 258 | 259 | let signature = field.signature(); 260 | assert!(signature.is_ok()); 261 | let signature = signature.unwrap(); 262 | assert!(signature.is_some()); 263 | let signature = signature.unwrap(); 264 | assert_eq!(signature, "TT;".to_string()); 265 | 266 | let method = class.methods().find(|n| n.name() == "myMethod"); 267 | assert!(method.is_some()); 268 | let method = method.unwrap(); 269 | 270 | let signature = method.signature(); 271 | assert!(signature.is_ok()); 272 | let signature = signature.unwrap(); 273 | assert!(signature.is_some()); 274 | let signature = signature.unwrap(); 275 | assert_eq!(signature, "(ITT;)V".to_string()); 276 | 277 | let annotation_set_ref_list: Vec<_> = method.param_annotations().iter().collect(); 278 | assert_eq!(annotation_set_ref_list.len(), 2); 279 | let first = &annotation_set_ref_list[0].annotations()[0]; 280 | assert_eq!(first.jtype(), runtime_annotation_class.jtype()); 281 | assert!(&annotation_set_ref_list[1].annotations().is_empty()); 282 | 283 | let class_annotation = class.annotations().iter().find(|item| item.jtype() == "LRuntimeAnnotation;"); 284 | assert!(class_annotation.is_some()); 285 | 286 | let signature = class.signature(); 287 | assert!(signature.is_ok()); 288 | let signature = signature.unwrap(); 289 | assert!(signature.is_some()); 290 | let signature = signature.unwrap(); 291 | assert_eq!(signature, "Ljava/lang/Object;".to_string()); 292 | 293 | } 294 | ); 295 | 296 | test!( 297 | test_fields, 298 | { 299 | "Main.java" => r#" 300 | class Main { 301 | boolean booleanVar; 302 | byte byteVar; 303 | short shortVar; 304 | char charVar; 305 | long longVar; 306 | float floatVar; 307 | public static int staticVar = 42; 308 | final double finalVar = 32.0d; 309 | private String privateField; 310 | public String publicField; 311 | protected String protectedField; 312 | int[] arrayField; 313 | Day enumField; 314 | T genericField; 315 | K genericField2; 316 | } 317 | "# 318 | }; 319 | { 320 | "Day.java" => r#" 321 | public enum Day { 322 | SUNDAY, MONDAY, TUESDAY, WEDNESDAY, 323 | THURSDAY, FRIDAY, SATURDAY 324 | } 325 | "# 326 | }, 327 | |dex: dex::Dex<_>| { 328 | let class = dex.find_class_by_name("LMain;").unwrap().unwrap(); 329 | assert_eq!(class.static_fields().len(), 1); 330 | assert_eq!(class.instance_fields().len(), 14); 331 | let find = |name, jtype| { 332 | let field = class.fields().find(|f| f.name() == name); 333 | assert!(field.is_some(), "name: {}, type: {}", name, jtype); 334 | let field = field.unwrap(); 335 | assert_eq!(field.jtype(), jtype); 336 | field 337 | }; 338 | 339 | let find_id_item = |id: FieldId| { 340 | dex.field_ids().find(|f| if let Ok(f) = f { 341 | f.id() == id 342 | } else { 343 | false 344 | }) 345 | }; 346 | 347 | let boolean = find("booleanVar", "Z"); 348 | assert!(boolean.jtype().is_bool()); 349 | assert!(find_id_item(boolean.id()).is_some()); 350 | 351 | let char_var = find("charVar", "C"); 352 | assert!(char_var.jtype().is_char()); 353 | assert!(find_id_item(char_var.id()).is_some()); 354 | 355 | let short = find("shortVar", "S"); 356 | assert!(short.jtype().is_short()); 357 | assert!(find_id_item(short.id()).is_some()); 358 | 359 | let float = find("floatVar", "F"); 360 | assert!(float.jtype().is_float()); 361 | assert!(find_id_item(float.id()).is_some()); 362 | 363 | let long = find("longVar", "J"); 364 | assert!(long.jtype().is_long()); 365 | assert!(find_id_item(long.id()).is_some()); 366 | 367 | let static_field = find("staticVar", "I"); 368 | assert!(!static_field.jtype().is_reference()); 369 | assert!(static_field.jtype().is_int()); 370 | assert_has_access_flags!(static_field, [is_static, is_public]); 371 | assert!(find_id_item(static_field.id()).is_some()); 372 | 373 | let final_field = find("finalVar", "D"); 374 | assert!(!final_field.jtype().is_reference()); 375 | assert!(final_field.jtype().is_double()); 376 | assert_has_access_flags!(final_field, [is_final]); 377 | assert!(find_id_item(final_field.id()).is_some()); 378 | 379 | let protected_field = find("protectedField", "Ljava/lang/String;"); 380 | assert!(protected_field.jtype().is_reference()); 381 | assert!(protected_field.jtype().is_class()); 382 | assert_has_access_flags!(protected_field, [is_protected]); 383 | assert!(find_id_item(protected_field.id()).is_some()); 384 | 385 | let private_field = find("privateField", "Ljava/lang/String;"); 386 | assert_has_access_flags!(private_field, [is_private]); 387 | assert!(find_id_item(private_field.id()).is_some()); 388 | 389 | let public_field = find("publicField", "Ljava/lang/String;"); 390 | assert_has_access_flags!(public_field, [is_public]); 391 | assert!(find_id_item(public_field.id()).is_some()); 392 | 393 | let array_field = find("arrayField", "[I"); 394 | assert!(array_field.jtype().is_array()); 395 | assert_eq!(array_field.jtype().array_dimensions(), Some(1)); 396 | assert!(array_field.jtype().is_reference()); 397 | assert!(array_field.access_flags().is_empty()); 398 | assert!(find_id_item(array_field.id()).is_some()); 399 | 400 | let generic_field = find("genericField", "Ljava/lang/Object;"); 401 | assert!(generic_field.jtype().is_reference()); 402 | assert_eq!(generic_field.jtype().array_dimensions(), None); 403 | assert!(generic_field.access_flags().is_empty()); 404 | assert!(find_id_item(generic_field.id()).is_some()); 405 | 406 | let generic_field = find("genericField2", "LMain;"); 407 | assert!(generic_field.access_flags().is_empty()); 408 | assert!(find_id_item(generic_field.id()).is_some()); 409 | 410 | let enum_field = find("enumField", "LDay;"); 411 | assert!(enum_field.access_flags().is_empty()); 412 | assert!(find_id_item(enum_field.id()).is_some()); 413 | 414 | } 415 | ); 416 | 417 | test!( 418 | test_field_values, 419 | { 420 | "FieldValues.java" => r#" 421 | import java.util.function.BiFunction; 422 | class FieldValues { 423 | private final static byte b = 120; 424 | private final static byte nb = -100; 425 | private final static short s = 10000; 426 | private final static short ns = -12048; 427 | private final static int i = 12540; 428 | private final static int ni = -12540; 429 | private final static long l = 43749374797L; 430 | private final static long nl = -43749374797L; 431 | private final static float f = 9.30f; 432 | private final static float nf = -9.34f; 433 | private final static double d = 4374.9437493749374d; 434 | private final static double nd = -1257.9374937493d; 435 | private final static boolean bo = true; 436 | private final static boolean nbo = false; 437 | private final static char c = 'm'; 438 | private final static String nullString = null; 439 | private final static String nonNullString = "fjdljfdlj"; 440 | private final static BiFunction remapper = (k, v) -> v == null ? 42 : v + 41; 441 | private final static Runnable r = () -> { System.out.println("runnable"); }; 442 | private final static int[] array = new int[]{1, 2, 3}; 443 | } 444 | "# 445 | }, 446 | |dex: dex::Dex<_>| { 447 | use dex::string::DexString; 448 | use dex::encoded_value::EncodedValue; 449 | let class = dex.find_class_by_name("LFieldValues;").expect("error getting FieldValues.class").expect("class not found"); 450 | assert_eq!(class.fields().count(), 20); 451 | let get_value = |name| { 452 | let field = class.fields().find(|f| f.name() == name); 453 | assert!(field.is_some(), "field: {}", name); 454 | let field = field.unwrap(); 455 | field.initial_value() 456 | }; 457 | assert_eq!(get_value("b"), Some(&EncodedValue::Byte(120))); 458 | assert_eq!(get_value("nb"), Some(&EncodedValue::Byte(-100))); 459 | assert_eq!(get_value("s"), Some(&EncodedValue::Short(10000))); 460 | assert_eq!(get_value("ns"), Some(&EncodedValue::Short(-12048))); 461 | assert_eq!(get_value("i"), Some(&EncodedValue::Int(12540))); 462 | assert_eq!(get_value("ni"), Some(&EncodedValue::Int(-12540))); 463 | assert_eq!(get_value("l"), Some(&EncodedValue::Long(43749374797))); 464 | assert_eq!(get_value("nl"), Some(&EncodedValue::Long(-43749374797))); 465 | assert_eq!(get_value("f"), Some(&EncodedValue::Float(9.30))); 466 | assert_eq!(get_value("nf"), Some(&EncodedValue::Float(-9.34))); 467 | assert_eq!(get_value("d"), Some(&EncodedValue::Double(4374.943749374937))); 468 | assert_eq!(get_value("nd"), Some(&EncodedValue::Double(-1257.9374937493))); 469 | assert_eq!(get_value("bo"), Some(&EncodedValue::Boolean(true))); 470 | assert_eq!(get_value("nbo"), Some(&EncodedValue::Boolean(false))); 471 | assert_eq!(get_value("c"), Some(&EncodedValue::Char(b'm'.into()))); 472 | assert_eq!(get_value("nullString"), Some(&EncodedValue::Null)); 473 | assert_eq!(get_value("nonNullString"), Some(&EncodedValue::String(DexString::from("fjdljfdlj".to_string())))); 474 | assert_eq!(get_value("remapper"), Some(&EncodedValue::Null)); 475 | assert_eq!(get_value("r"), Some(&EncodedValue::Null)); 476 | assert_eq!(get_value("array"), Some(&EncodedValue::Null)); 477 | } 478 | ); 479 | 480 | test!( 481 | test_interface, 482 | { 483 | "MyInterface.java" => r#" 484 | interface MyInterface { 485 | int x = 115; 486 | String j = "MyString"; 487 | int interfaceMethod(String x, char y); 488 | default int interfaceMethod3(String y) { 489 | System.out.println(y); 490 | return 0; 491 | } 492 | } 493 | "# 494 | }; 495 | { 496 | "MyInterface2.java" => r#" 497 | public interface MyInterface2 extends MyInterface { 498 | int interfaceMethod2(String y); 499 | default int interfaceMethod(String x, char y) { 500 | System.out.println(x + y); 501 | return 0; 502 | } 503 | } 504 | "# 505 | }; 506 | { 507 | "MyInterface3.java" => r#" 508 | interface MyInterface3 { 509 | static int interfaceMethod3() { 510 | return 3; 511 | } 512 | } 513 | "# 514 | }, 515 | |dex: dex::Dex<_>| { 516 | use dex::class::Class; 517 | let validate_interface_methods = |interface: &Class| { 518 | interface.methods().for_each(|m| { 519 | assert_has_access_flags!(m, [is_public, is_abstract], format!("interface method: {} doesn't have all attributes", m.name())); 520 | assert!(m.code().is_none(), "interface method: {} shouldn't have code item", m.name()); 521 | }); 522 | }; 523 | let validate_interface_fields = |interface: &Class| { 524 | interface.fields().for_each(|f| { 525 | assert_has_access_flags!(f, [is_public, is_static, is_final], format!("interface field: {} doesn't have all attributes", f.name())); 526 | }); 527 | }; 528 | 529 | let interface = dex.find_class_by_name("LMyInterface;"); 530 | assert!(interface.is_ok()); 531 | let interface = interface.unwrap(); 532 | assert!(interface.is_some()); 533 | let interface = interface.unwrap(); 534 | assert_has_access_flags!(interface, [is_interface]); 535 | assert_eq!(interface.fields().count(), 2); 536 | assert_eq!(interface.methods().count(), 2); 537 | validate_interface_fields(&interface); 538 | validate_interface_methods(&interface); 539 | 540 | let interface2 = dex.find_class_by_name("LMyInterface2;"); 541 | assert!(interface2.is_ok()); 542 | let interface2 = interface2.unwrap(); 543 | assert!(interface2.is_some()); 544 | let interface2 = interface2.unwrap(); 545 | assert_has_access_flags!(interface2, [is_public, is_interface]); 546 | assert_eq!(interface2.methods().count(), 2); 547 | assert_eq!(interface2.fields().count(), 0); 548 | validate_interface_fields(&interface2); 549 | validate_interface_methods(&interface2); 550 | assert_eq!(interface2.interfaces(), &[interface.jtype().clone()]); 551 | 552 | // static methods in an interface are moved to a generated class marked SYNTHETIC. 553 | // HACK: name can be anything. but the current d8 generates a name that starts 554 | // with the original interface's name. 555 | let interface3 = dex.classes().find(|c| { 556 | let c = c.as_ref().expect("error finding synthetic class"); 557 | c.jtype().type_descriptor().starts_with("LMyInterface3") && 558 | c.is_synthetic() 559 | }); 560 | assert!(interface3.is_some()); 561 | let interface3 = interface3.unwrap(); 562 | assert!(interface3.is_ok()); 563 | let interface3 = interface3.unwrap(); 564 | assert_eq!(interface3.methods().count(), 1); 565 | let method = interface3.methods().take(1).next(); 566 | assert!(method.is_some()); 567 | let method = method.unwrap(); 568 | assert_has_access_flags!(method, [is_static]); 569 | } 570 | ); 571 | 572 | test!( 573 | test_abstract_classes, 574 | { 575 | "AbstractClass.java" => r#" 576 | abstract class AbstractClass { 577 | public static void staticMethod() {} 578 | public final void finalMethod() {} 579 | public abstract int abstractMethod(); 580 | } 581 | "# 582 | }, 583 | |dex: dex::Dex<_>| { 584 | let abstract_class = dex.find_class_by_name("LAbstractClass;"); 585 | assert!(abstract_class.is_ok()); 586 | let abstract_class = abstract_class.unwrap(); 587 | assert!(abstract_class.is_some()); 588 | let abstract_class = abstract_class.unwrap(); 589 | assert_has_access_flags!(abstract_class, [is_abstract]); 590 | assert_eq!(abstract_class.methods().count(), 4); 591 | let mut methods = abstract_class.methods().map(|m| m.name()).collect::>(); 592 | methods.sort(); 593 | let expected = &mut ["", "staticMethod", "abstractMethod", "finalMethod"]; 594 | expected.sort(); 595 | assert_eq!(&methods, expected); 596 | let abstract_method = abstract_class.methods().find(|m| m.name() == "abstractMethod").unwrap(); 597 | assert!(abstract_method.code().is_none()); 598 | assert_has_access_flags!(abstract_method, [is_public, is_abstract]); 599 | } 600 | ); 601 | 602 | test!( 603 | test_enums, 604 | { 605 | "EnumClass.java" => r#" 606 | public enum EnumClass { 607 | SUNDAY, MONDAY, TUESDAY, WEDNESDAY, 608 | THURSDAY, FRIDAY, SATURDAY 609 | } 610 | "# 611 | }, 612 | |dex: dex::Dex<_>| { 613 | let enum_class = dex.find_class_by_name("LEnumClass;"); 614 | assert!(enum_class.is_ok()); 615 | let enum_class = enum_class.unwrap(); 616 | assert!(enum_class.is_some()); 617 | let enum_class = enum_class.unwrap(); 618 | assert_has_access_flags!(enum_class, [is_enum]); 619 | 620 | let sunday = enum_class.fields().find(|f| f.name() == "SUNDAY"); 621 | assert!(sunday.is_some()); 622 | let sunday = sunday.unwrap(); 623 | assert_has_access_flags!(sunday, [is_enum]); 624 | } 625 | ); 626 | 627 | test!( 628 | test_methods, 629 | { 630 | "Main.java" => r#" 631 | import java.util.List; 632 | abstract class Main extends SuperClass implements MyInterface { 633 | // constructor 634 | Main() {} 635 | 636 | // attributes 637 | void defaultMethod() {} 638 | final void finalMethod() {} 639 | static void staticMethod() {} 640 | public void publicMethod() {} 641 | private void privateMethod() {} 642 | protected void protectedMethod() {} 643 | 644 | // return values 645 | int primitiveReturnMethod() { return 0; } 646 | String classReturnMethod() { return null; } 647 | long[] arrayReturnMethod() { return new long[10]; } 648 | String[] objectArrayReturnMethod() { return new String[10]; } 649 | Day enumReturnMethod() { return Day.SUNDAY; } 650 | 651 | // params 652 | int primitiveParams(char u, short v, byte w, int x, long y, boolean z, double a, float b) { return 0; } 653 | String classParams(String x, String y) { return "22"; } 654 | void enumParam(Day day) {} 655 | void interfaceParam(MyInterface instance) {} 656 | void primitiveArrayParam(long[] instance) {} 657 | void objectArrayParam(String[] instance) {} 658 | private void genericParamsMethod1(List myList, int k) {} 659 | private void genericParamsMethod2(T typeParam, int k) {} 660 | private void genericParamsMethod3(List typeParam, int k) {} 661 | private void genericParamsMethod4(List typeParam, int k) {} 662 | private void genericParamWithExtendsClauseMethod(T typeParam) {} 663 | private void genericParamWithMultipleExtendsClauseMethod(T typeParam) {} 664 | public int varargsMethod(String... args) { return 1; } 665 | 666 | // overriden method 667 | @Override int superMethod(String y) { return 2; } 668 | 669 | // interface method 670 | public String interfaceMethod(int x, String y) { return y + x; } 671 | 672 | // native method 673 | public native String nativeMethod(int x, String y); 674 | 675 | // abstract method 676 | abstract int abstractMethod(int x); 677 | 678 | // synchronized method 679 | synchronized int synchronizedMethod(int y) { return 1; } 680 | } 681 | "# 682 | }; 683 | { 684 | "Day.java" => r#" 685 | public enum Day { 686 | SUNDAY, MONDAY, TUESDAY, WEDNESDAY, 687 | THURSDAY, FRIDAY, SATURDAY 688 | } 689 | "# 690 | }; 691 | { 692 | "SuperClass.java" => r#" 693 | class SuperClass { 694 | int superMethod(String x) { return 1; } 695 | final int superMethod2(String x) { return 1; } 696 | } 697 | "# 698 | }; 699 | { 700 | "MyInterface.java" => r#" 701 | interface MyInterface { 702 | String interfaceMethod(int x, String y); 703 | } 704 | "# 705 | }, 706 | |dex: dex::Dex<_>| { 707 | let class = dex.find_class_by_name("LMain;").unwrap().unwrap(); 708 | assert_eq!(class.direct_methods().len(), 9); 709 | assert_eq!(class.virtual_methods().len(), 21); 710 | 711 | let find = |name, params: &[&str], return_type: &str| { 712 | let method = class.methods().find(|m| { 713 | m.name() == name && 714 | m.params().iter().map(|s| s.type_descriptor()).eq(params.iter()) && 715 | m.return_type() == return_type 716 | }); 717 | assert!(method.is_some(), "method: {}, params: {:?}, return_type: {}", name, params, return_type); 718 | let method = method.unwrap(); 719 | method 720 | }; 721 | 722 | let find_id_item = |id: MethodId| { 723 | dex.method_ids().find(|m| if let Ok(m) = m { 724 | m.id() == id 725 | } else { 726 | false 727 | }) 728 | }; 729 | 730 | let default_method = find("defaultMethod", &[], "V"); 731 | assert!(default_method.code().is_some()); 732 | assert!(default_method.access_flags().is_empty()); 733 | assert_eq!(default_method.shorty(), "V"); 734 | assert!(find_id_item(default_method.id()).is_some()); 735 | 736 | let final_method = find("finalMethod", &[], "V"); 737 | assert!(final_method.code().is_some()); 738 | assert_has_access_flags!(final_method, [is_final]); 739 | assert_eq!(final_method.shorty(), "V"); 740 | assert!(find_id_item(final_method.id()).is_some()); 741 | 742 | let static_method = find("staticMethod", &[], "V"); 743 | assert!(static_method.code().is_some()); 744 | assert_has_access_flags!(static_method, [is_static]); 745 | assert_eq!(static_method.shorty(), "V"); 746 | assert!(find_id_item(static_method.id()).is_some()); 747 | 748 | let public_method = find("publicMethod", &[], "V"); 749 | assert!(public_method.code().is_some()); 750 | assert_has_access_flags!(public_method, [is_public]); 751 | assert_eq!(public_method.shorty(), "V"); 752 | assert!(find_id_item(public_method.id()).is_some()); 753 | 754 | let private_method = find("privateMethod", &[], "V"); 755 | assert!(private_method.code().is_some()); 756 | assert_has_access_flags!(private_method, [is_private]); 757 | assert_eq!(private_method.shorty(), "V"); 758 | assert!(find_id_item(private_method.id()).is_some()); 759 | 760 | let protected_method = find("protectedMethod", &[], "V"); 761 | assert!(protected_method.code().is_some()); 762 | assert_has_access_flags!(protected_method, [is_protected]); 763 | assert_eq!(protected_method.shorty(), "V"); 764 | assert!(find_id_item(protected_method.id()).is_some()); 765 | 766 | 767 | let primitive_return_method = find("primitiveReturnMethod", &[], "I"); 768 | assert!(primitive_return_method.code().is_some()); 769 | assert!(primitive_return_method.access_flags().is_empty()); 770 | assert_eq!(primitive_return_method.shorty(), "I"); 771 | assert!(find_id_item(primitive_return_method.id()).is_some()); 772 | 773 | let class_return_method = find("classReturnMethod", &[], "Ljava/lang/String;"); 774 | assert!(primitive_return_method.code().is_some()); 775 | assert!(class_return_method.access_flags().is_empty()); 776 | assert_eq!(class_return_method.shorty(), "L"); 777 | assert!(find_id_item(class_return_method.id()).is_some()); 778 | 779 | let array_return_method = find("arrayReturnMethod", &[], "[J"); 780 | assert!(array_return_method.code().is_some()); 781 | assert!(array_return_method.access_flags().is_empty()); 782 | assert_eq!(array_return_method.shorty(), "L"); 783 | assert!(find_id_item(array_return_method.id()).is_some()); 784 | 785 | let object_array_return_method = find("objectArrayReturnMethod", &[], "[Ljava/lang/String;"); 786 | assert!(object_array_return_method.code().is_some()); 787 | assert!(array_return_method.access_flags().is_empty()); 788 | assert!(object_array_return_method.access_flags().is_empty()); 789 | assert_eq!(object_array_return_method.shorty(), "L"); 790 | assert!(find_id_item(object_array_return_method.id()).is_some()); 791 | 792 | let enum_return_method = find("enumReturnMethod", &[], "LDay;"); 793 | assert!(enum_return_method.code().is_some()); 794 | assert!(enum_return_method.access_flags().is_empty()); 795 | assert_eq!(enum_return_method.shorty(), "L"); 796 | assert!(find_id_item(enum_return_method.id()).is_some()); 797 | 798 | 799 | let primitive_params_method = find("primitiveParams", &["C", "S", "B", "I", "J", "Z", "D", "F"], "I"); 800 | assert!(primitive_params_method.code().is_some()); 801 | assert!(primitive_params_method.access_flags().is_empty()); 802 | assert_eq!(primitive_params_method.shorty(), "ICSBIJZDF"); 803 | assert!(find_id_item(primitive_params_method.id()).is_some()); 804 | 805 | let class_params_method = find("classParams", &["Ljava/lang/String;", "Ljava/lang/String;"], "Ljava/lang/String;"); 806 | assert!(class_params_method.code().is_some()); 807 | assert!(class_params_method.access_flags().is_empty()); 808 | assert_eq!(class_params_method.shorty(), "LLL"); 809 | assert!(find_id_item(class_params_method.id()).is_some()); 810 | 811 | let enum_params_method = find("enumParam", &["LDay;"], "V"); 812 | assert!(enum_params_method.code().is_some()); 813 | assert!(enum_params_method.access_flags().is_empty()); 814 | assert_eq!(enum_params_method.shorty(), "VL"); 815 | assert!(find_id_item(enum_params_method.id()).is_some()); 816 | 817 | let primitive_array_params_method = find("primitiveArrayParam", &["[J"], "V"); 818 | assert!(primitive_array_params_method.code().is_some()); 819 | assert!(primitive_array_params_method.access_flags().is_empty()); 820 | assert_eq!(primitive_array_params_method.shorty(), "VL"); 821 | assert!(find_id_item(primitive_array_params_method.id()).is_some()); 822 | 823 | let object_array_params_method = find("objectArrayParam", &["[Ljava/lang/String;"], "V"); 824 | assert!(object_array_params_method.code().is_some()); 825 | assert!(object_array_params_method.access_flags().is_empty()); 826 | assert_eq!(object_array_params_method.shorty(), "VL"); 827 | assert!(find_id_item(object_array_params_method.id()).is_some()); 828 | 829 | let interface_params_method = find("interfaceParam", &["LMyInterface;"], "V"); 830 | assert!(interface_params_method.code().is_some()); 831 | assert!(interface_params_method.access_flags().is_empty()); 832 | assert_eq!(interface_params_method.shorty(), "VL"); 833 | assert!(find_id_item(interface_params_method.id()).is_some()); 834 | 835 | let generic_params_method = find("genericParamsMethod1", &["Ljava/util/List;", "I"], "V"); 836 | assert!(generic_params_method.code().is_some()); 837 | assert_has_access_flags!(generic_params_method, [is_private]); 838 | assert_eq!(generic_params_method.shorty(), "VLI"); 839 | assert!(find_id_item(generic_params_method.id()).is_some()); 840 | 841 | let generic_params_method = find("genericParamsMethod2", &["Ljava/lang/Object;", "I"], "V"); 842 | assert!(generic_params_method.code().is_some()); 843 | assert_has_access_flags!(generic_params_method, [is_private]); 844 | assert_eq!(generic_params_method.shorty(), "VLI"); 845 | assert!(find_id_item(generic_params_method.id()).is_some()); 846 | 847 | let generic_params_method = find("genericParamsMethod3", &["Ljava/util/List;", "I"], "V"); 848 | assert!(generic_params_method.code().is_some()); 849 | assert_has_access_flags!(generic_params_method, [is_private]); 850 | assert_eq!(generic_params_method.shorty(), "VLI"); 851 | assert!(find_id_item(generic_params_method.id()).is_some()); 852 | 853 | let generic_params_method = find("genericParamsMethod4", &["Ljava/util/List;", "I"], "V"); 854 | assert!(generic_params_method.code().is_some()); 855 | assert_has_access_flags!(generic_params_method, [is_private]); 856 | assert_eq!(generic_params_method.shorty(), "VLI"); 857 | assert!(find_id_item(generic_params_method.id()).is_some()); 858 | 859 | 860 | let generic_params_method = find("genericParamWithExtendsClauseMethod", &["LSuperClass;"], "V"); 861 | assert!(generic_params_method.code().is_some()); 862 | assert_has_access_flags!(generic_params_method, [is_private]); 863 | assert_eq!(generic_params_method.shorty(), "VL"); 864 | assert!(find_id_item(generic_params_method.id()).is_some()); 865 | 866 | let generic_params_method = find("genericParamWithMultipleExtendsClauseMethod", &["LSuperClass;"], "V"); 867 | assert!(generic_params_method.code().is_some()); 868 | assert_has_access_flags!(generic_params_method, [is_private]); 869 | assert_eq!(generic_params_method.shorty(), "VL"); 870 | assert!(find_id_item(generic_params_method.id()).is_some()); 871 | 872 | 873 | let varargs_method = find("varargsMethod", &["[Ljava/lang/String;"], "I"); 874 | assert!(varargs_method.code().is_some()); 875 | assert_has_access_flags!(varargs_method, [is_public, is_varargs]); 876 | assert_eq!(varargs_method.shorty(), "IL"); 877 | assert!(find_id_item(varargs_method.id()).is_some()); 878 | 879 | 880 | let super_method = find("superMethod", &["Ljava/lang/String;"], "I"); 881 | assert!(super_method.code().is_some()); 882 | assert!(super_method.access_flags().is_empty()); 883 | assert_eq!(super_method.shorty(), "IL"); 884 | assert!(find_id_item(super_method.id()).is_some()); 885 | 886 | let super_method2 = class.fields().find(|m| m.name() == "superMethod2"); 887 | assert!(super_method2.is_none(), "super method 2 is not overriden, so it shouldn't be there"); 888 | 889 | 890 | let interface_method = find("interfaceMethod", &["I", "Ljava/lang/String;"], "Ljava/lang/String;"); 891 | assert!(interface_method.code().is_some()); 892 | assert_has_access_flags!(interface_method, [is_public]); 893 | assert_eq!(interface_method.shorty(), "LIL"); 894 | assert!(find_id_item(interface_method.id()).is_some()); 895 | 896 | 897 | let native_method = find("nativeMethod", &["I", "Ljava/lang/String;"], "Ljava/lang/String;"); 898 | assert!(native_method.code().is_none()); 899 | assert_has_access_flags!(native_method, [is_public, is_native]); 900 | assert_eq!(native_method.shorty(), "LIL"); 901 | assert!(find_id_item(native_method.id()).is_some()); 902 | 903 | let abstract_method = find("abstractMethod", &["I"], "I"); 904 | assert!(abstract_method.code().is_none()); 905 | assert_has_access_flags!(abstract_method, [is_abstract]); 906 | assert_eq!(abstract_method.shorty(), "II"); 907 | assert!(find_id_item(abstract_method.id()).is_some()); 908 | 909 | let synchronized_method = find("synchronizedMethod", &["I"], "I"); 910 | assert!(synchronized_method.code().is_some()); 911 | assert_has_access_flags!(synchronized_method, [is_declared_synchronized]); 912 | assert_eq!(synchronized_method.shorty(), "II"); 913 | assert!(find_id_item(synchronized_method.id()).is_some()); 914 | } 915 | ); 916 | 917 | test!( 918 | test_builtin_types, 919 | { 920 | "BuiltInTypes.java" => r#" 921 | public class BuiltInTypes { 922 | boolean returnsBoolean() { return false; } 923 | byte returnsByte() { return 0; } 924 | short returnsShort() { return 0; } 925 | char returnsChar() { return 0; } 926 | int returnsInt() { return 0; } 927 | long returnsLong() { return 0; } 928 | float returnsFloat() { return 0; } 929 | double returnsDouble() { return 0; } 930 | // This is technically not needed, as the void type is always present 931 | void returnsVoid() {} 932 | } 933 | "# 934 | }, 935 | |dex: dex::Dex<_>| { 936 | let builtin_class = dex.find_class_by_name("LBuiltInTypes;").unwrap().unwrap(); 937 | 938 | let find_type = |name: &str| { 939 | dex.types().find(|t| { 940 | if let Ok(t) = t { 941 | t.to_string() == name 942 | } else { 943 | false 944 | } 945 | }) 946 | }; 947 | 948 | let boolean_type = find_type("Z"); 949 | assert!(boolean_type.is_some()); 950 | 951 | let byte_type = find_type("B"); 952 | assert!(byte_type.is_some()); 953 | 954 | let short_type = find_type("S"); 955 | assert!(short_type.is_some()); 956 | 957 | let char_type = find_type("C"); 958 | assert!(char_type.is_some()); 959 | 960 | let int_type = find_type("I"); 961 | assert!(int_type.is_some()); 962 | 963 | let long_type = find_type("J"); 964 | assert!(long_type.is_some()); 965 | 966 | let float_type = find_type("F"); 967 | assert!(float_type.is_some()); 968 | 969 | let double_type = find_type("D"); 970 | assert!(double_type.is_some()); 971 | 972 | let void_type = find_type("V"); 973 | assert!(void_type.is_some()); 974 | 975 | } 976 | ); 977 | 978 | #[test] 979 | fn test_iterators() { 980 | use dex::DexReader; 981 | let dex = DexReader::from_file("resources/classes.dex").expect("can't open dex"); 982 | for jtype in dex.types() { 983 | assert!(jtype.is_ok()); 984 | } 985 | 986 | for proto_id_item in dex.proto_ids() { 987 | assert!(proto_id_item.is_ok()); 988 | } 989 | 990 | for field_id_item in dex.field_ids() { 991 | assert!(field_id_item.is_ok()); 992 | } 993 | 994 | for method_id_item in dex.method_ids() { 995 | assert!(method_id_item.is_ok()); 996 | } 997 | 998 | for method_handle_item in dex.method_handles() { 999 | assert!(method_handle_item.is_ok()); 1000 | } 1001 | } 1002 | 1003 | test!( 1004 | test_debug_info, 1005 | { 1006 | "DebugBytecodes.java" => r#" 1007 | public class DebugBytecodes { 1008 | void testFunc(int param1, int param2) { 1009 | int someVar = 1234; 1010 | int someVar2 = someVar / 123; 1011 | 1012 | if (someVar2 > 20) { 1013 | return; 1014 | } 1015 | 1016 | 1017 | } 1018 | } 1019 | "# 1020 | }, 1021 | |dex: dex::Dex<_>| { 1022 | let builtin_class = dex.find_class_by_name("LDebugBytecodes;").unwrap().unwrap(); 1023 | 1024 | let test_func = builtin_class.methods().find(|m| m.name() == "testFunc").unwrap(); 1025 | 1026 | assert!(test_func.code().is_some()); 1027 | 1028 | let code = test_func.code().unwrap(); 1029 | 1030 | assert!(code.debug_info_item().is_some()); 1031 | 1032 | let debug_info_item = code.debug_info_item().unwrap(); 1033 | 1034 | assert_eq!(debug_info_item.line_start(), 4); 1035 | 1036 | let parameter_names = debug_info_item.parameter_names(); 1037 | let byte_code = debug_info_item.bytecodes(); 1038 | 1039 | assert_eq!(parameter_names.len(), 2); 1040 | assert_eq!(parameter_names.get(0), Some(&Some(DexString::from(String::from("param1"))))); 1041 | assert_eq!(parameter_names.get(1), Some(&Some(DexString::from(String::from("param2"))))); 1042 | 1043 | assert_eq!(byte_code.len(), 8); 1044 | assert_eq!(byte_code.last(), Some(&DebugInfoBytecode::EndSequence)); 1045 | } 1046 | ); 1047 | --------------------------------------------------------------------------------