use crate::Result;
use asc_dex::Dex;
use asc_dex::encoded::{EncodedValue, VALUE_BYTE, VALUE_CHAR, VALUE_DOUBLE, VALUE_FLOAT, VALUE_INT, VALUE_LONG, VALUE_SHORT};
pub fn escape_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if asc_query::text::must_escape(c) => asc_query::text::push_unicode_escape(&mut out, c),
c => out.push(c),
}
}
out
}
pub fn proto_descriptor(dex: &Dex<'_>, proto_idx: u32) -> Result<String> {
Ok(dex.proto_descriptor(proto_idx)?)
}
pub fn method_signature(dex: &Dex<'_>, method_idx: u32) -> Result<String> {
Ok(dex.method_signature(method_idx)?)
}
pub fn field_signature(dex: &Dex<'_>, field_idx: u32) -> Result<String> {
Ok(dex.field_signature(field_idx)?)
}
pub(crate) fn method_handle(dex: &Dex<'_>, handle_idx: u32) -> Result<String> {
let handle = dex.method_handle(handle_idx)?;
let member = u32::from(handle.field_or_method_idx);
let target = if handle.kind.is_field_accessor() { field_signature(dex, member)? } else { method_signature(dex, member)? };
Ok(format!("{}@{target}", handle.kind))
}
pub(crate) fn call_site(dex: &Dex<'_>, site_idx: u32) -> Result<String> {
let site = dex.call_site(site_idx)?;
let mut out =
format!("call_site_{site_idx}(\"{}\", {}", escape_string(&dex.string(site.method_name)?), proto_descriptor(dex, site.method_type)?);
for arg in &site.extra_args {
out.push_str(", ");
out.push_str(&value(dex, arg)?);
}
let bootstrap = dex.method_handle(site.bootstrap)?;
out.push_str(")@");
out.push_str(&method_signature(dex, bootstrap.field_or_method_idx.into())?);
Ok(out)
}
pub(crate) fn hex_i64(v: i64) -> String {
if v < 0 { format!("-0x{:x}", (v as i128).unsigned_abs()) } else { format!("0x{v:x}") }
}
fn float_literal(f: f32) -> String {
if f.is_nan() {
"NaNf".into()
} else if f.is_infinite() {
if f > 0.0 { "Infinityf".into() } else { "-Infinityf".into() }
} else if f.fract() == 0.0 && f.abs() < 1e16 {
format!("{f:.1}f")
} else {
format!("{f}f")
}
}
fn double_literal(f: f64) -> String {
if f.is_nan() {
"NaN".into()
} else if f.is_infinite() {
if f > 0.0 { "Infinity".into() } else { "-Infinity".into() }
} else if f.fract() == 0.0 && f.abs() < 1e16 {
format!("{f:.1}")
} else {
format!("{f}")
}
}
pub(crate) fn value(dex: &Dex<'_>, v: &EncodedValue) -> Result<String> {
value_indented(dex, v, 0)
}
pub(crate) fn value_indented(dex: &Dex<'_>, v: &EncodedValue, depth: usize) -> Result<String> {
Ok(match v {
EncodedValue::Primitive { kind, .. } => match *kind {
VALUE_BYTE => format!("{}t", hex_i64(v.as_i64().unwrap_or(0))),
VALUE_SHORT => format!("{}s", hex_i64(v.as_i64().unwrap_or(0))),
VALUE_CHAR => {
let c = v.as_i64().unwrap_or(0) as u32;
match char::from_u32(c) {
Some(ch) if (0x20..0x7f).contains(&c) && ch != '\'' && ch != '\\' => format!("'{ch}'"),
_ => format!("'\\u{c:04x}'"),
}
}
VALUE_INT => hex_i64(v.as_i64().unwrap_or(0)),
VALUE_LONG => format!("{}L", hex_i64(v.as_i64().unwrap_or(0))),
VALUE_FLOAT => float_literal(v.as_f32().unwrap_or(0.0)),
VALUE_DOUBLE => double_literal(v.as_f64().unwrap_or(0.0)),
k => format!("<unknown value type {k:#x}>"),
},
EncodedValue::MethodType(p) => proto_descriptor(dex, *p)?,
EncodedValue::MethodHandle(h) => method_handle(dex, *h).unwrap_or_else(|_| format!("method_handle@{h}")),
EncodedValue::String(s) => format!("\"{}\"", escape_string(&dex.string(*s)?)),
EncodedValue::Type(t) => dex.type_descriptor(*t)?,
EncodedValue::Field(f) => field_signature(dex, *f)?,
EncodedValue::Method(m) => method_signature(dex, *m)?,
EncodedValue::Enum(f) => format!(".enum {}", field_signature(dex, *f)?),
EncodedValue::Null => "null".into(),
EncodedValue::Boolean(b) => {
if *b {
"true".into()
} else {
"false".into()
}
}
EncodedValue::Array(items) => {
let indent = " ".repeat(depth);
let mut s = String::from("{\n");
for (i, item) in items.iter().enumerate() {
s.push_str(&indent);
s.push_str(" ");
s.push_str(&value_indented(dex, item, depth + 1)?);
if i + 1 < items.len() {
s.push(',');
}
s.push('\n');
}
s.push_str(&indent);
s.push('}');
s
}
EncodedValue::Annotation(ann) => {
let indent = " ".repeat(depth);
let mut s = format!(".subannotation {}\n", dex.type_descriptor(ann.type_idx)?);
for (name, val) in &ann.elements {
s.push_str(&indent);
s.push_str(" ");
s.push_str(&dex.string(*name)?);
s.push_str(" = ");
s.push_str(&value_indented(dex, val, depth + 1)?);
s.push('\n');
}
s.push_str(&indent);
s.push_str(".end subannotation");
s
}
_ => "<unknown value>".into(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn string_literals_show_what_could_disguise_them() {
assert_eq!(escape_string("a\"b\\\n\u{1b}é"), "a\\\"b\\\\\\n\\u001bé");
assert_eq!(escape_string("x\u{202e}y\u{200b}\u{e0041}"), "x\\u202ey\\u200b\\udb40\\udc41");
}
}