1use crate::{FindOptions, Result, matched_name, results_from_hits, rows_compiled};
9use asc_dex::Dex;
10use asc_findrefs::{CompiledQuery, FieldAccess, FindRefs, Hit, RefKind};
11use std::collections::hash_map::Entry;
12use std::collections::{HashMap, HashSet};
13use std::ops::Range;
14use std::sync::Arc;
15
16#[derive(Clone, Default)]
19pub struct TextRows {
20 text: Text,
21 ends: Vec<usize>,
24}
25
26#[derive(Clone)]
29enum Text {
30 Owned(String),
31 Shared { bytes: Arc<dyn AsRef<[u8]> + Send + Sync>, range: Range<usize> },
32}
33
34impl Default for Text {
35 fn default() -> Self {
36 Text::Owned(String::new())
37 }
38}
39
40impl TextRows {
41 pub fn as_str(&self) -> &str {
47 match &self.text {
48 Text::Owned(s) => s,
49 Text::Shared { bytes, range } => std::str::from_utf8(&(**bytes).as_ref()[range.clone()]).expect("validated UTF-8"),
51 }
52 }
53
54 pub fn len(&self) -> usize {
56 self.ends.len()
57 }
58
59 pub fn is_empty(&self) -> bool {
61 self.ends.is_empty()
62 }
63
64 pub fn lines(&self) -> impl Iterator<Item = &str> {
66 let text = self.as_str();
67 let starts = std::iter::once(0).chain(self.ends.iter().map(|&e| e + 1));
68 starts.zip(&self.ends).map(move |(start, &end)| &text[start..end])
69 }
70
71 pub(crate) fn ends(&self) -> &[usize] {
72 &self.ends
73 }
74
75 pub(crate) fn shared(bytes: Arc<dyn AsRef<[u8]> + Send + Sync>, range: Range<usize>, ends: Vec<usize>) -> Option<TextRows> {
78 let text = std::str::from_utf8((*bytes).as_ref().get(range.clone())?).ok()?;
79 let valid = ends.windows(2).all(|w| w[0] < w[1])
80 && ends.iter().all(|&e| text.as_bytes().get(e) == Some(&b'\n'))
81 && ends.last().map_or(text.is_empty(), |&e| e + 1 == text.len());
82 valid.then_some(TextRows { text: Text::Shared { bytes, range }, ends })
83 }
84
85 fn owned(&mut self) -> &mut String {
87 if let Text::Shared { .. } = self.text {
88 self.text = Text::Owned(self.as_str().to_string());
89 }
90 match &mut self.text {
91 Text::Owned(s) => s,
92 Text::Shared { .. } => unreachable!("converted above"),
93 }
94 }
95
96 fn end_row(&mut self) {
98 let text = self.owned();
99 let end = text.len();
100 text.push('\n');
101 self.ends.push(end);
102 }
103
104 pub(crate) fn append(&mut self, other: &TextRows) {
106 let base = self.owned().len();
107 self.owned().push_str(other.as_str());
108 self.ends.extend(other.ends.iter().map(|&e| base + e));
109 }
110}
111
112impl std::fmt::Debug for TextRows {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 f.debug_struct("TextRows").field("rows", &self.len()).field("bytes", &self.as_str().len()).finish()
115 }
116}
117
118impl PartialEq for TextRows {
119 fn eq(&self, other: &Self) -> bool {
120 self.ends == other.ends && self.as_str() == other.as_str()
121 }
122}
123
124impl Eq for TextRows {}
125
126pub(crate) fn text_rows(fr: &FindRefs<'_, '_, '_>, query: &CompiledQuery) -> Result<TextRows> {
131 let plain = query.relation().is_none() && query.compound().is_none() && query.defined().is_none();
132 let rows = if plain {
133 let hits = fr.find_ref_compiled(query)?;
134 if hits.iter().all(|h| h.note.is_none()) {
135 return rows_from_hits(fr.dex(), query.kind(), &hits);
136 }
137 results_from_hits(fr.dex(), query.kind(), &hits, true)?
138 } else {
139 rows_compiled(fr, query, FindOptions::default())?
140 };
141 let mut out = TextRows::default();
142 for r in &rows {
143 out.owned().push_str(&r.line());
144 out.end_row();
145 }
146 Ok(out)
147}
148
149fn rows_from_hits(dex: &Dex<'_>, kind: RefKind, hits: &[Hit]) -> Result<TextRows> {
152 let mut grouped = vec![SmallIdxSet::default(); dex.method_count() as usize];
155 for hit in hits {
156 for &m in hit.methods.iter().flat_map(|ms| ms.ids()) {
157 if let Some(idxs) = grouped.get_mut(m as usize) {
158 idxs.insert(hit_key(hit));
159 }
160 }
161 }
162 let mut out = TextRows::default();
163 let mut matched: HashMap<u32, String> = HashMap::new();
165 for (m, idxs) in grouped.into_iter().enumerate() {
166 if let SmallIdxSet::Empty = idxs {
167 continue;
168 }
169 let m = u32::try_from(m).expect("method ids are u32");
170 let text = out.owned();
171 text.push_str(dex.name());
172 text.push_str(" | ");
173 text.push_str(&asc_smali::method_signature(dex, m)?);
174 text.push_str(" | ");
175 for (i, key) in idxs.into_sorted().into_iter().enumerate() {
176 if i != 0 {
177 text.push_str(", ");
178 }
179 let value = match matched.entry(key) {
180 Entry::Occupied(e) => e.into_mut(),
181 Entry::Vacant(e) => {
182 let (idx, access) = split_hit_key(key);
183 let mut value = matched_name(dex, kind, idx)?.to_string();
184 if let Some(access) = access {
185 value.push_str(&format!(" [{}]", access.as_str()));
186 }
187 e.insert(value)
188 }
189 };
190 text.push_str(value);
191 }
192 out.end_row();
193 }
194 Ok(out)
195}
196
197fn hit_key(hit: &Hit) -> u32 {
198 (hit.matched_idx << 2)
201 | match hit.field_access {
202 None => 0,
203 Some(FieldAccess::Read) => 1,
204 Some(FieldAccess::Write) => 2,
205 }
206}
207
208fn split_hit_key(key: u32) -> (u32, Option<FieldAccess>) {
209 let access = match key & 3 {
210 1 => Some(FieldAccess::Read),
211 2 => Some(FieldAccess::Write),
212 _ => None,
213 };
214 (key >> 2, access)
215}
216
217#[derive(Clone, Default)]
220enum SmallIdxSet {
221 #[default]
222 Empty,
223 One(u32),
224 Two(u32, u32),
225 #[allow(clippy::box_collection)]
227 Many(Box<HashSet<u32>>),
228}
229
230impl SmallIdxSet {
231 fn insert(&mut self, idx: u32) {
232 match self {
233 SmallIdxSet::Empty => *self = SmallIdxSet::One(idx),
234 SmallIdxSet::One(a) if *a == idx => {}
235 SmallIdxSet::One(a) => *self = SmallIdxSet::Two(*a, idx),
236 SmallIdxSet::Two(a, b) if *a == idx || *b == idx => {}
237 SmallIdxSet::Two(a, b) => *self = SmallIdxSet::Many(Box::new(HashSet::from([*a, *b, idx]))),
238 SmallIdxSet::Many(set) => {
239 set.insert(idx);
240 }
241 }
242 }
243
244 fn into_sorted(self) -> Vec<u32> {
245 let mut out = match self {
246 SmallIdxSet::Empty => Vec::new(),
247 SmallIdxSet::One(a) => vec![a],
248 SmallIdxSet::Two(a, b) => vec![a, b],
249 SmallIdxSet::Many(set) => set.into_iter().collect(),
250 };
251 out.sort_unstable();
252 out
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 fn rows(lines: &[&str]) -> TextRows {
261 let mut out = TextRows::default();
262 for l in lines {
263 out.owned().push_str(l);
264 out.end_row();
265 }
266 out
267 }
268
269 #[test]
270 fn rows_keep_their_boundaries_through_append_and_sharing() {
271 let mut a = rows(&["x | a", "y\nz | b"]);
272 a.append(&rows(&["w | c"]));
273 assert_eq!(a.lines().collect::<Vec<_>>(), ["x | a", "y\nz | b", "w | c"]);
274 assert_eq!((a.len(), a.as_str()), (3, "x | a\ny\nz | b\nw | c\n"));
275
276 let bytes: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(format!("##{}##", a.as_str()).into_bytes());
277 let range = 2..2 + a.as_str().len();
278 let shared = TextRows::shared(bytes.clone(), range.clone(), a.ends().to_vec()).unwrap();
279 assert_eq!(shared, a);
280 for ends in [vec![0, 9, 15], vec![5, 11, 99], vec![5, 11]] {
283 assert!(TextRows::shared(bytes.clone(), range.clone(), ends).is_none());
284 }
285 assert!(TextRows::shared(bytes, 0..0, Vec::new()).unwrap().is_empty());
286 }
287
288 #[test]
289 fn small_sets_sort_and_dedup() {
290 let mut s = SmallIdxSet::default();
291 for i in [5, 3, 5, 9, 3, 1] {
292 s.insert(i);
293 }
294 assert_eq!(s.into_sorted(), [1, 3, 5, 9]);
295 let mut s = SmallIdxSet::default();
296 s.insert(2);
297 s.insert(2);
298 assert_eq!(s.into_sorted(), [2]);
299 }
300}