add methods

This commit is contained in:
Jean-Marie Mineau 2023-10-06 19:25:23 +02:00
parent 0a112802cd
commit b704e7cbad
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
4 changed files with 300 additions and 73 deletions

View file

@ -88,11 +88,18 @@ impl Apk {
} }
let mut static_fields = vec![]; let mut static_fields = vec![];
let mut instance_fields = vec![]; let mut instance_fields = vec![];
let mut direct_methods = vec![];
let mut virtual_methods = vec![];
let data_off = class_item.class_data_off; let data_off = class_item.class_data_off;
if data_off != 0 { if data_off != 0 {
let data = dex.get_struct_at_offset::<ClassDataItem>(data_off)?; let data = dex.get_struct_at_offset::<ClassDataItem>(data_off)?;
static_fields = Self::get_field_list_from_dex(&data.static_fields, dex)?; static_fields = Self::get_field_list_from_encoded_field_list(&data.static_fields, dex)?;
instance_fields = Self::get_field_list_from_dex(&data.instance_fields, dex)?; instance_fields =
Self::get_field_list_from_encoded_field_list(&data.instance_fields, dex)?;
direct_methods =
Self::get_method_list_from_encoded_field_list(&data.direct_methods, dex)?;
virtual_methods =
Self::get_method_list_from_encoded_field_list(&data.virtual_methods, dex)?;
} }
if class_item.static_values_off != 0 { if class_item.static_values_off != 0 {
@ -130,6 +137,8 @@ impl Apk {
is_enum, is_enum,
instance_fields, instance_fields,
static_fields, static_fields,
direct_methods,
virtual_methods,
}) })
} }
@ -284,7 +293,87 @@ impl Apk {
} }
} }
pub fn get_field_list_from_dex( /// Return a [`Field`] from it's idx (index in `field_ids`) in the dex file and its access flags
/// ([`EncodedField.access_flags`])
pub fn get_field_from_idx(
idx: usize,
Uleb128(access_flags): Uleb128,
dex: &DexFileReader,
) -> Result<Field> {
let descriptor = Self::get_id_field_from_idx(idx, dex)?;
let is_public = (access_flags & ACC_PUBLIC) != 0;
let is_private = (access_flags & ACC_PRIVATE) != 0;
let is_protected = (access_flags & ACC_PROTECTED) != 0;
let is_static = (access_flags & ACC_STATIC) != 0;
let is_final = (access_flags & ACC_FINAL) != 0;
let is_volatile = (access_flags & ACC_VOLATILE) != 0;
let is_transient = (access_flags & ACC_TRANSIENT) != 0;
let is_synthetic = (access_flags & ACC_SYNTHETIC) != 0;
let is_enum = (access_flags & ACC_ENUM) != 0;
let visibility = match (is_public, is_private, is_protected) {
(true, false, false) => FieldVisibility::Public,
(false, true, false) => FieldVisibility::Private,
(false, false, true) => FieldVisibility::Protected,
(false, false, false) => FieldVisibility::None_,
(pbl, prv, prt) => {
let class: String = descriptor.class_.0.into();
let name: String = descriptor.name.into();
return Err(Error::InconsistantStruct(format!(
"Inconsistant visiblity found in {class}.{name}: \
(public: {pbl}, private: {prv}, protected: {prt})"
)));
}
};
if access_flags
& !(ACC_PUBLIC
| ACC_PRIVATE
| ACC_PROTECTED
| ACC_STATIC
| ACC_FINAL
| ACC_VOLATILE
| ACC_TRANSIENT
| ACC_SYNTHETIC
| ACC_ENUM)
!= 0
{
info!(
"Unexpected flags found in {} for : 0x{:x} (unknown flags: 0x{:x})",
descriptor.__repr__(),
access_flags,
access_flags
& !(ACC_PUBLIC
| ACC_PRIVATE
| ACC_PROTECTED
| ACC_STATIC
| ACC_FINAL
| ACC_VOLATILE
| ACC_TRANSIENT
| ACC_SYNTHETIC
| ACC_ENUM)
);
}
Ok(Field {
descriptor,
visibility,
is_static,
is_final,
is_volatile,
is_transient,
is_synthetic,
is_enum,
value: None,
})
}
/// Return a list of field from a list of [`EncodedField`].
///
/// # Warning
///
/// The index of the fields is computed by summing the [`EncodedField.field_idx_diff`] of the
/// previous element of the list the diff of the current field, so the list must be preserved
/// as in the dex file.
pub fn get_field_list_from_encoded_field_list(
encoded_fields: &[EncodedField], encoded_fields: &[EncodedField],
dex: &DexFileReader, dex: &DexFileReader,
) -> Result<Vec<Field>> { ) -> Result<Vec<Field>> {
@ -293,55 +382,135 @@ impl Apk {
for field in encoded_fields { for field in encoded_fields {
idx += field.field_idx_diff.0; idx += field.field_idx_diff.0;
let id_item = dex.get_field_id(idx as usize)?; fields.push(Self::get_field_from_idx(
let class_ty = dex.get_type_id(id_item.class_idx as usize)?; idx as usize,
let class: DexString = dex.get_string(class_ty.descriptor_idx)?.into(); field.access_flags,
let ty = dex.get_type_id(id_item.type_idx as usize)?; dex,
let ty = dex.get_string(ty.descriptor_idx)?.into(); )?);
let name: DexString = dex.get_string(id_item.name_idx)?.into();
let is_public = (field.access_flags.0 & ACC_PUBLIC) != 0;
let is_private = (field.access_flags.0 & ACC_PRIVATE) != 0;
let is_protected = (field.access_flags.0 & ACC_PROTECTED) != 0;
let is_static = (field.access_flags.0 & ACC_STATIC) != 0;
let is_final = (field.access_flags.0 & ACC_FINAL) != 0;
let is_volatile = (field.access_flags.0 & ACC_VOLATILE) != 0;
let is_transient = (field.access_flags.0 & ACC_TRANSIENT) != 0;
let is_synthetic = (field.access_flags.0 & ACC_SYNTHETIC) != 0;
let is_enum = (field.access_flags.0 & ACC_ENUM) != 0;
let visibility = match (is_public, is_private, is_protected) {
(true, false, false) => FieldVisibility::Public,
(false, true, false) => FieldVisibility::Private,
(false, false, true) => FieldVisibility::Protected,
(false, false, false) => FieldVisibility::None_,
(pbl, prv, prt) => {
let class: String = class.into();
let name: String = name.into();
return Err(Error::InconsistantStruct(format!(
"Inconsistant visiblity found in {class}.{name}: \
(public: {pbl}, private: {prv}, protected: {prt})"
)));
}
};
let descriptor = IdField {
name,
type_: IdType(ty),
class_: IdType(class),
};
fields.push(Field {
descriptor,
visibility,
is_static,
is_final,
is_volatile,
is_transient,
is_synthetic,
is_enum,
value: None,
})
} }
Ok(fields) Ok(fields)
} }
/// Return a [`Method`] from it's idx (index in `method_ids`) in the dex file and its access flags
/// ([`EncodedMethod.access_flags`]) and code offset ([`EncodedMethod.code_off`]).
pub fn get_method_from_idx(
idx: usize,
Uleb128(access_flags): Uleb128,
Uleb128(_code_off): Uleb128,
dex: &DexFileReader,
) -> Result<Method> {
let descriptor = Self::get_id_method_from_idx(idx, dex)?;
let is_public = (access_flags & ACC_PUBLIC) != 0;
let is_private = (access_flags & ACC_PRIVATE) != 0;
let is_protected = (access_flags & ACC_PROTECTED) != 0;
let is_static = (access_flags & ACC_STATIC) != 0;
let is_final = (access_flags & ACC_FINAL) != 0;
let is_synchronized = (access_flags & ACC_SYNCHRONIZED) != 0;
let is_bridge = (access_flags & ACC_BRIDGE) != 0;
let is_varargs = (access_flags & ACC_VARARGS) != 0;
let is_native = (access_flags & ACC_NATIVE) != 0;
let is_abstract = (access_flags & ACC_ABSTRACT) != 0;
let is_strictfp = (access_flags & ACC_STRICT) != 0;
let is_synthetic = (access_flags & ACC_SYNTHETIC) != 0;
let is_constructor = (access_flags & ACC_CONSTRUCTOR) != 0;
let is_declared_syncrhonized = (access_flags & ACC_DECLARED_SYNCHRONIZED) != 0;
let visibility = match (is_public, is_private, is_protected) {
(true, false, false) => MethodVisibility::Public,
(false, true, false) => MethodVisibility::Private,
(false, false, true) => MethodVisibility::Protected,
(false, false, false) => MethodVisibility::None_,
(pbl, prv, prt) => {
return Err(Error::InconsistantStruct(format!(
"Inconsistant visiblity found in {}: \
(public: {pbl}, private: {prv}, protected: {prt})",
descriptor.__repr__()
)));
// TODO: replace by public?
}
};
if access_flags
& !(ACC_PUBLIC
| ACC_PRIVATE
| ACC_PROTECTED
| ACC_STATIC
| ACC_FINAL
| ACC_SYNCHRONIZED
| ACC_BRIDGE
| ACC_VARARGS
| ACC_NATIVE
| ACC_ABSTRACT
| ACC_STRICT
| ACC_SYNTHETIC
| ACC_CONSTRUCTOR
| ACC_DECLARED_SYNCHRONIZED)
!= 0
{
info!(
"Unexpected flags found in {} for : 0x{:x} (unknown flags: 0x{:x})",
descriptor.__repr__(),
access_flags,
access_flags
& !(ACC_PUBLIC
| ACC_PRIVATE
| ACC_PROTECTED
| ACC_STATIC
| ACC_FINAL
| ACC_SYNCHRONIZED
| ACC_BRIDGE
| ACC_VARARGS
| ACC_NATIVE
| ACC_ABSTRACT
| ACC_STRICT
| ACC_SYNTHETIC
| ACC_CONSTRUCTOR
| ACC_DECLARED_SYNCHRONIZED)
);
}
Ok(Method {
descriptor,
visibility,
is_static,
is_final,
is_synchronized,
is_bridge,
is_varargs,
is_native,
is_abstract,
is_strictfp,
is_synthetic,
is_constructor,
is_declared_syncrhonized,
code: (),
})
}
/// Return a list of field from a list of [`EncodedMethod`].
///
/// # Warning
///
/// The index of the fields is computed by summing the [`EncodedMethod.field_idx_diff`] of the
/// previous element of the list the diff of the current field, so the list must be preserved
/// as in the dex file.
pub fn get_method_list_from_encoded_field_list(
encoded_methods: &[EncodedMethod],
dex: &DexFileReader,
) -> Result<Vec<Method>> {
let mut idx = 0;
let mut methods = vec![];
for method in encoded_methods {
idx += method.method_idx_diff.0;
methods.push(Self::get_method_from_idx(
idx as usize,
method.access_flags,
method.code_off,
dex,
)?);
}
Ok(methods)
}
} }
#[pymethods] #[pymethods]

View file

@ -2,7 +2,7 @@
use pyo3::prelude::*; use pyo3::prelude::*;
use crate::{DexString, Field}; use crate::{DexString, Field, Method};
/// Represent an apk /// Represent an apk
#[pyclass] #[pyclass]
@ -46,21 +46,24 @@ pub struct Class {
#[pyo3(get, set)] #[pyo3(get, set)]
pub source_file: Option<DexString>, pub source_file: Option<DexString>,
// TODO: hash map?
/// The static fields /// The static fields
#[pyo3(get, set)] #[pyo3(get, set)]
pub static_fields: Vec<Field>, pub static_fields: Vec<Field>,
/// The instance fields /// The instance fields
#[pyo3(get, set)] #[pyo3(get, set)]
pub instance_fields: Vec<Field>, pub instance_fields: Vec<Field>,
// /// The static methods /// The direct (static, private or constructor) methods of the class
// #[pyo3(get, set)] #[pyo3(get, set)]
// pub methods: Vec<()>, pub direct_methods: Vec<Method>,
// Dont think we need to distinguish direct (static + private) and virtual (all the other) methods /// The virtual (ie non direct) methods of the class
// #[pyo3(get, set)]
pub virtual_methods: Vec<Method>,
// Do we need to distinguish direct and virtual (all the other) methods?
// Maybe overlapping descriptor (same name, class and proto?)
// pub annotations: Option<()> // TODO // pub annotations: Option<()> // TODO
// pub data: Option<()> // TODO // TODO: mix annotation data to fields / methods / class to make it more practicle
// pub static_values: Option<()> // TODO
// TODO: mix annotation data and static values to make it more practical
} }
#[pymethods] #[pymethods]
@ -81,6 +84,8 @@ impl Class {
is_enum: false, is_enum: false,
static_fields: vec![], static_fields: vec![],
instance_fields: vec![], instance_fields: vec![],
direct_methods: vec![],
virtual_methods: vec![],
} }
} }

View file

@ -22,7 +22,7 @@ impl IdMethodType {
#[new] #[new]
pub fn new(return_type: IdType, parameters: Vec<IdType>) -> Self { pub fn new(return_type: IdType, parameters: Vec<IdType>) -> Self {
Self { Self {
shorty: Self::get_shorty(&return_type, &parameters), shorty: Self::compute_shorty(&return_type, &parameters),
return_type, return_type,
parameters, parameters,
} }
@ -43,12 +43,24 @@ impl IdMethodType {
pub fn __repr__(&self) -> String { pub fn __repr__(&self) -> String {
format!("DexMethodType({})", self.__str__()) format!("DexMethodType({})", self.__str__())
} }
pub fn get_shorty(&self) -> DexString {
self.shorty.clone()
}
pub fn get_return_type(&self) -> IdType {
self.return_type.clone()
}
pub fn get_parameters(&self) -> Vec<IdType> {
self.parameters.clone()
}
} }
impl IdMethodType { impl IdMethodType {
/// Compute the format for the shorty as described in /// Compute the format for the shorty as described in
/// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor> /// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
pub fn get_shorty(return_type: &IdType, parameters: &[IdType]) -> DexString { pub fn compute_shorty(return_type: &IdType, parameters: &[IdType]) -> DexString {
let mut shorty: String = return_type.get_shorty().into(); let mut shorty: String = return_type.get_shorty().into();
for ty in parameters { for ty in parameters {
let ty: String = ty.get_shorty().into(); let ty: String = ty.get_shorty().into();
@ -346,8 +358,11 @@ impl IdField {
#[pyclass] #[pyclass]
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct IdMethod { pub struct IdMethod {
#[pyo3(get, set)]
pub class_: IdType, pub class_: IdType,
#[pyo3(get, set)]
pub proto: IdMethodType, pub proto: IdMethodType,
#[pyo3(get, set)]
pub name: DexString, pub name: DexString,
} }

View file

@ -11,39 +11,77 @@ pub struct Method {
/// The structure used to reference this method. /// The structure used to reference this method.
#[pyo3(get, set)] #[pyo3(get, set)]
pub descriptor: IdMethod, pub descriptor: IdMethod,
/* TODO: ACCESS FLAG /// The field visibility.
/// The field visibility
#[pyo3(get, set)] #[pyo3(get, set)]
pub visibility: FieldVisibility, pub visibility: MethodVisibility,
/// If the field is defined for the class globally /// Static methods do not take this in argument.
#[pyo3(get, set)] #[pyo3(get, set)]
pub is_static: bool, pub is_static: bool,
/// If the field is immutable after construction /// Final methods are not averridable.
#[pyo3(get, set)] #[pyo3(get, set)]
pub is_final: bool, pub is_final: bool,
/// For thread safety /// Synchronized method automatically acquire their associated lock around call.
/// Can only be set in native method, (`[Self::is_native] = true`).
#[pyo3(get, set)] #[pyo3(get, set)]
pub is_volatile: bool, pub is_synchronized: bool,
/// If the field should **not** be saved by default serialization /// Bridge are automatically added by the compiler as a type-safe bridge.
#[pyo3(get, set)] #[pyo3(get, set)]
pub is_transient: bool, pub is_bridge: bool,
/// If the field is not defined in the source code /// If the last argument should be treated as a "rest" argument by compiler
/// (for method of variable number of argument).
#[pyo3(get, set)]
pub is_varargs: bool,
/// If the method is a native method.
#[pyo3(get, set)]
pub is_native: bool,
/// Abstract methods are not implemented by the class.
#[pyo3(get, set)]
pub is_abstract: bool,
/// If the method must use strict rules for floating point arithmetic.
#[pyo3(get, set)]
pub is_strictfp: bool,
/// Synthetic method are not directly defined in the source code.
#[pyo3(get, set)] #[pyo3(get, set)]
pub is_synthetic: bool, pub is_synthetic: bool,
/// If the field is an enumerated value /// If the method is a constructor.
#[pyo3(get, set)] #[pyo3(get, set)]
pub is_enum: bool, pub is_constructor: bool,
*/ /// If the method is declared as synchronize (just indicatif)
#[pyo3(get, set)]
pub is_declared_syncrhonized: bool,
/// The code of the method /// The code of the method
pub code: (), pub code: (),
} }
/// Represent the visibility of a field
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MethodVisibility {
Public,
Private,
Protected,
None_, // Actually quite common
}
#[pymethods] #[pymethods]
impl Method { impl Method {
#[new] #[new]
pub fn new(descriptor: IdMethod) -> Self { pub fn new(descriptor: IdMethod) -> Self {
Self { Self {
descriptor, descriptor,
visibility: MethodVisibility::Public,
is_static: false,
is_final: false,
is_synchronized: false,
is_bridge: false,
is_varargs: false,
is_native: false,
is_abstract: false,
is_strictfp: false,
is_synthetic: false,
is_constructor: false,
is_declared_syncrhonized: false,
code: (), code: (),
} }
} }