java_asm 0.1.3

Java bytecode reader & writer in rust
Documentation
use crate::dex::{CallSiteId, CallSiteItem, DUInt, DUShort, FieldId, Header, MapList, MethodId, ProtoId, StringData, TypeList};
use crate::dex::{DexFileAccessor, MethodHandle};
use crate::err::{AsmResultExt, AsmResultOkExt};
use crate::impls::jvms::r::{ReadContext, ReadFrom, U32BasedSize};
use crate::impls::{ToStringRef, VecEx};
use crate::{AsmErr, AsmResult, DescriptorRef, StrRef};
use std::fmt;
use std::fmt::{Display, Formatter};

macro_rules! opt_fn {
    ($fn_name:ident, $get_fn:ident, $invalid_prefix:expr, $return_type:ty) => {
        #[inline]
        pub fn $fn_name(&self, idx: impl Into<usize>) -> $return_type {
            let idx = idx.into();
            self.$get_fn(idx).unwrap_or_else(|_| {
                format!("{}_{}", $invalid_prefix, idx).into()
            })
        }
    };
}


impl DexFileAccessor {
    #[inline]
    pub fn get_data_impl<T: ReadFrom>(
        &self, data_off: DUInt,
    ) -> AsmResult<T> {
        Self::get_data_in_bytes(&self.bytes, data_off, self.endian)
    }

    #[inline]
    pub fn get_map_list(
        bytes: &[u8], header: &Header, endian: bool,
    ) -> AsmResult<MapList> {
        Self::get_data_in_bytes(bytes, header.map_off, endian)
    }

    #[inline]
    pub fn get_call_site_ids(
        bytes: &[u8], call_site_offset: DUInt, size: U32BasedSize, endian: bool,
    ) -> Vec<CallSiteId> {
        Self::get_vec_in_bytes(bytes, call_site_offset, size, endian)
            .unwrap_or_default()
    }

    #[inline]
    pub fn get_method_handles(
        bytes: &[u8], method_handle_offset: DUInt, size: U32BasedSize, endian: bool,
    ) -> Vec<MethodHandle> {
        Self::get_vec_in_bytes(bytes, method_handle_offset, size, endian)
            .unwrap_or_default()
    }

    #[inline]
    pub fn get_data_in_bytes<T: ReadFrom>(
        bytes: &[u8], data_off: DUInt, endian: bool,
    ) -> AsmResult<T> {
        let mut read_context = if endian {
            ReadContext::big_endian(bytes)
        } else {
            ReadContext::little_endian(bytes)
        };
        read_context.index = data_off as usize;
        read_context.read()
    }

    #[inline]
    pub fn get_vec_in_bytes<T: ReadFrom>(
        bytes: &[u8], data_off: DUInt, size: impl Into<usize>, endian: bool,
    ) -> AsmResult<Vec<T>> {
        let size = Into::into(size);
        if size == 0 {
            return Ok(Vec::new());
        }
        let mut read_context = if endian {
            ReadContext::big_endian(bytes)
        } else {
            ReadContext::little_endian(bytes)
        };
        read_context.index = data_off as usize;
        read_context.read_vec(size)
    }

    opt_fn!(opt_str, get_str, "invalid_str", StrRef);
    opt_fn!(opt_type, get_type, "invalid_type", DescriptorRef);
    
    #[inline]
    pub fn get_str(&self, str_idx: impl Into<usize>) -> AsmResult<StrRef> {
        let str_idx = str_idx.into();
        let dex_file = &self.file;
        let string_data_off = dex_file.string_ids.get(str_idx)
            .ok_or_error(|| AsmErr::OutOfRange(str_idx).e())?.string_data_off;
        Ok(self.get_data_impl::<StringData>(string_data_off)?.str_ref)
    }

    #[inline]
    pub fn get_type(&self, type_idx: impl Into<usize>) -> AsmResult<DescriptorRef> {
        let dex_file = &self.file;
        let type_idx = type_idx.into();
        let type_id = dex_file.type_ids.get(type_idx)
            .ok_or_error(|| AsmErr::OutOfRange(type_idx).e())?;
        self.get_str(type_id.descriptor_idx)
    }

    #[inline]
    pub fn get_proto(&self, proto_idx: impl Into<usize>) -> AsmResult<ProtoConst> {
        let dex_file = &self.file;
        let proto_idx = proto_idx.into();
        let proto_id = dex_file.proto_ids.get(proto_idx)
            .ok_or_error(|| AsmErr::OutOfRange(proto_idx).e())?;
        let ProtoId { shorty_idx, return_type_idx, parameters_off } = *proto_id;
        let shorty = self.get_str(shorty_idx)?;
        let return_type = self.get_type(return_type_idx)?;
        let parameters = self.get_type_list(parameters_off)?;
        ProtoConst { shorty, return_type, parameters }.ok()
    }

    #[inline]
    pub fn get_field(&self, field_idx: DUShort) -> AsmResult<FieldConst> {
        let dex_file = &self.file;
        let field_id = dex_file.field_ids.get(field_idx as usize)
            .ok_or_error(|| AsmErr::OutOfRange(field_idx as usize).e())?;
        let FieldId { class_idx, type_idx, name_idx, .. } = *field_id;
        let class_type = self.get_type(class_idx)?;
        let field_type = self.get_type(type_idx)?;
        let field_name = self.get_str(name_idx)?;
        FieldConst { class_type, field_type, field_name }.ok()
    }

    #[inline]
    pub fn get_method(&self, method_idx: impl Into<usize>) -> AsmResult<MethodConst> {
        let dex_file = &self.file;
        let method_idx = method_idx.into();
        let method_id = dex_file.method_ids.get(method_idx)
            .ok_or_error(|| AsmErr::OutOfRange(method_idx).e())?;
        let MethodId { class_idx, proto_idx, name_idx } = *method_id;
        let class_type = self.get_type(class_idx)?;
        let proto_const = self.get_proto(proto_idx)?;
        let method_name = self.get_str(name_idx)?;
        let desc = proto_const.to_string().to_ref();
        MethodConst { class_type, desc, method_name }.ok()
    }
    #[inline]
    pub fn get_call_site(&self, call_site_index: impl Into<usize>) -> AsmResult<CallSiteItem> {
        let call_site_index = call_site_index.into();
        let call_site_off = self.call_site_ids.get(call_site_index)
            .ok_or_error(|| AsmErr::OutOfRange(call_site_index).e())?;
        self.get_data_impl(call_site_off.call_site_off)
    }

    #[inline]
    pub fn get_method_handle(&self, method_handle_index: impl Into<usize>) -> AsmResult<&MethodHandle> {
        let method_handle_index = method_handle_index.into();
        self.method_handles.get(method_handle_index)
            .ok_or_error(|| AsmErr::OutOfRange(method_handle_index).e())
    }

    #[inline]
    pub fn get_type_list(&self, type_list_off: DUInt) -> AsmResult<Vec<DescriptorRef>> {
        if type_list_off == 0 { return Ok(vec![]); }
        let TypeList { type_id_indices, .. } = self.get_data_impl::<TypeList>(type_list_off)?;
        type_id_indices.map_res(|type_idx| self.get_type(*type_idx))
    }
}

pub struct ProtoConst {
    pub shorty: DescriptorRef,
    pub return_type: DescriptorRef,
    pub parameters: Vec<DescriptorRef>,
}

pub struct FieldConst {
    pub class_type: DescriptorRef,
    pub field_type: DescriptorRef,
    pub field_name: StrRef,
}

pub struct MethodConst {
    pub class_type: DescriptorRef,
    pub desc: DescriptorRef,
    pub method_name: StrRef,
}

impl Display for ProtoConst {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(
            f, "({}){}",
            self.parameters.join(""), self.return_type
        )
    }
}

impl Display for FieldConst {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(
            f, "{} {} {}",
            self.class_type, self.field_name, self.field_type
        )
    }
}

impl Display for MethodConst {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(
            f, "{} {} {}",
            self.class_type, self.method_name, self.desc
        )
    }
}