1const BLOB: &[u8] = include_bytes!("../data/android-api.fwdb");
25
26const MAGIC: u32 = 0x42445746; const REC: usize = 36; const SUPER_NONE: u32 = u32::MAX;
29
30const _: () = assert!(
33 u32::from_le_bytes([BLOB[0], BLOB[1], BLOB[2], BLOB[3]]) == MAGIC
34 && u32::from_le_bytes([BLOB[4], BLOB[5], BLOB[6], BLOB[7]]) == 1
35 && BLOB.len() > 40,
36 "android-api.fwdb: bad magic/version — rerun scripts/extract-android-api.py"
37);
38
39pub const CF_INTERFACE: u32 = 1;
41pub const CF_ABSTRACT: u32 = 2;
42pub const CF_ENUM: u32 = 4;
43pub const CF_UNKNOWN: u32 = 1 << 31; pub const MF_PUBLIC: u32 = 1;
47pub const MF_PROTECTED: u32 = 2;
48pub const MF_PRIVATE: u32 = 4;
49pub const MF_STATIC: u32 = 8;
50pub const MF_ABSTRACT: u32 = 16;
51pub const MF_FINAL: u32 = 32;
52pub const MF_BRIDGE: u32 = 64;
53pub const MF_SYNTHETIC: u32 = 128;
54pub const MF_UNKNOWN: u32 = 1 << 31;
55
56#[inline(always)]
57fn u32_at(off: usize) -> u32 {
58 u32::from_le_bytes([BLOB[off], BLOB[off + 1], BLOB[off + 2], BLOB[off + 3]])
59}
60
61#[inline(always)]
62fn off_pool() -> usize {
63 u32_at(16) as usize
64}
65
66#[inline(always)]
67fn class_count() -> u32 {
68 u32_at(8)
69}
70
71#[inline(always)]
72fn off_classes() -> usize {
73 u32_at(20) as usize
74}
75
76#[inline(always)]
77fn off_impls() -> usize {
78 u32_at(24) as usize
79}
80
81#[inline(always)]
82fn off_methods() -> usize {
83 u32_at(28) as usize
84}
85
86#[inline(always)]
87fn off_fields() -> usize {
88 u32_at(32) as usize
89}
90
91#[inline]
95fn s_at(off: u32) -> &'static str {
96 let start = off_pool() + off as usize;
97 let mut end = start;
98 while BLOB[end] != 0 {
99 end += 1;
100 }
101 std::str::from_utf8(&BLOB[start..end]).unwrap_or("")
102}
103
104#[inline]
106fn cmp_name(off: u32, name: &[u8]) -> std::cmp::Ordering {
107 let start = off_pool() + off as usize;
108 let mut i = 0usize;
109 loop {
110 let b = BLOB[start + i];
111 match (b, name.get(i)) {
112 (0, None) => return std::cmp::Ordering::Equal,
113 (0, Some(_)) => return std::cmp::Ordering::Less, (_, None) => return std::cmp::Ordering::Greater,
115 (x, Some(&y)) => {
116 if x != y {
117 return x.cmp(&y);
118 }
119 i += 1;
120 }
121 }
122 }
123}
124
125pub fn find(name: &str) -> Option<u32> {
127 let n = class_count();
128 let base = off_classes();
129 let nb = name.as_bytes();
130 let (mut lo, mut hi) = (0u32, n);
131 while lo < hi {
132 let mid = lo + (hi - lo) / 2;
133 let name_off = u32_at(base + REC * mid as usize);
134 match cmp_name(name_off, nb) {
135 std::cmp::Ordering::Equal => return Some(mid),
136 std::cmp::Ordering::Less => lo = mid + 1,
137 std::cmp::Ordering::Greater => hi = mid,
138 }
139 }
140 None
141}
142
143#[inline]
144fn rec(i: u32) -> [u32; 9] {
145 let b = off_classes() + REC * i as usize;
146 [
147 u32_at(b),
148 u32_at(b + 4),
149 u32_at(b + 8),
150 u32_at(b + 12),
151 u32_at(b + 16),
152 u32_at(b + 20),
153 u32_at(b + 24),
154 u32_at(b + 28),
155 u32_at(b + 32),
156 ]
157}
158
159pub fn exists(name: &str) -> bool {
160 find(name).is_some()
161}
162
163pub fn class_flags(name: &str) -> u32 {
164 find(name).map(|i| rec(i)[2]).unwrap_or(0)
165}
166
167pub fn super_of(name: &str) -> Option<&'static str> {
168 let i = find(name)?;
169 let so = rec(i)[1];
170 if so == SUPER_NONE {
171 None
172 } else {
173 Some(s_at(so))
174 }
175}
176
177pub fn for_each_interface(name: &str, mut f: impl FnMut(&'static str)) {
179 let Some(i) = find(name) else { return };
180 let r = rec(i);
181 let (start, len) = (r[3] as usize, r[4] as usize);
182 let base = off_impls();
183 for k in 0..len {
184 f(s_at(u32_at(base + 4 * (start + k))));
185 }
186}
187
188pub fn for_each_method(name: &str, mut f: impl FnMut(&'static str, u32)) {
191 let Some(i) = find(name) else { return };
192 let r = rec(i);
193 let (start, len) = (r[5] as usize, r[6] as usize);
194 let base = off_methods();
195 for k in 0..len {
196 let b = base + 8 * (start + k);
197 f(s_at(u32_at(b)), u32_at(b + 4));
198 }
199}
200
201pub fn for_each_field(name: &str, mut f: impl FnMut(&'static str, u32)) {
203 let Some(i) = find(name) else { return };
204 let r = rec(i);
205 let (start, len) = (r[7] as usize, r[8] as usize);
206 let base = off_fields();
207 for k in 0..len {
208 let b = base + 8 * (start + k);
209 f(s_at(u32_at(b)), u32_at(b + 4));
210 }
211}
212
213pub fn is_subtype(sub: &str, sup: &str) -> bool {
216 if sub == sup {
217 return true;
218 }
219 if find(sub).is_none() {
220 return false;
221 }
222 if sup == "java/lang/Object" {
225 return true;
226 }
227 let mut stack: Vec<&str> = vec![sub];
228 let mut visited: jdc_core::FxHashSet<&str> = jdc_core::FxHashSet::default();
229 let mut hops = 0usize;
230 while let Some(c) = stack.pop() {
231 hops += 1;
232 if hops > 4096 || !visited.insert(c) {
233 continue;
234 }
235 if let Some(s) = super_of(c) {
236 if s == sup {
237 return true;
238 }
239 stack.push(s);
240 }
241 let mut hit = false;
242 for_each_interface(c, |i| {
243 if i == sup {
244 hit = true;
245 } else {
246 stack.push(i);
247 }
248 });
249 if hit {
250 return true;
251 }
252 }
253 false
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn blob_header_is_valid() {
262 assert_eq!(u32_at(0), MAGIC);
263 assert_eq!(u32_at(4), 1);
264 assert!(class_count() > 6000);
265 }
266
267 #[test]
268 fn hierarchy_queries() {
269 assert!(exists("android/view/View"));
270 assert_eq!(super_of("android/view/ViewGroup"), Some("android/view/View"));
271 assert!(is_subtype("android/widget/FrameLayout", "android/view/View"));
272 assert!(is_subtype("java/lang/String", "java/lang/Object"));
273 assert!(is_subtype("java/lang/String", "java/lang/CharSequence"));
274 assert!(!is_subtype("android/view/View", "java/lang/String"));
275 assert!(class_flags("android/view/ViewGroup") & CF_ABSTRACT != 0);
276 }
277
278 #[test]
279 fn removed_api_families_resolve_as_unknown() {
280 assert!(exists("org/apache/http/HttpResponse"));
281 assert!(class_flags("org/apache/http/HttpResponse") & CF_UNKNOWN != 0);
282 }
283
284 #[test]
285 fn member_queries() {
286 let mut has_x = false;
287 for_each_field("android/view/View", |n, f| {
288 if n == "X" && f & MF_STATIC != 0 {
289 has_x = true;
290 }
291 });
292 assert!(has_x, "View.X static field");
293 let mut ctors = 0;
294 for_each_method("java/lang/Throwable", |d, _| {
295 if d.starts_with("<init>") {
296 ctors += 1;
297 }
298 });
299 assert!(ctors >= 4);
300 let mut has_run = false;
301 for_each_method("java/lang/Runnable", |d, f| {
302 if d == "run()V" && f & MF_ABSTRACT != 0 && f & MF_STATIC == 0 {
303 has_run = true;
304 }
305 });
306 assert!(has_run, "Runnable.run abstract instance method");
307 assert!(class_flags("java/lang/Runnable") & CF_INTERFACE != 0);
308 }
309}