1use crate::call_resolver::platform;
2use crate::call_resolver::{FieldRef, MethodRef};
3use crate::ty::check::{boxing_type, is_assignable, unboxing_type};
4use crate::ty::descriptor::{descriptor_to_ty, method_descriptor_to_sig};
5use crate::ty::{MethodSig, Ty};
6use std::cmp::Ordering;
7use std::collections::{HashMap, HashSet, VecDeque};
8
9const ACC_STATIC: u16 = 0x0008;
10const ACC_VARARGS: u16 = 0x0080;
11const OBJECT_CLASS: &str = "java/lang/Object";
12
13#[derive(Debug, Clone, Default)]
14pub struct ClassCatalog {
15 classes: HashSet<String>,
16 packages: HashSet<String>,
17 simple_names: HashMap<String, SimpleName>,
18 fields: HashMap<(String, String), FieldRef>,
19 methods: HashMap<(String, String), Vec<MethodRef>>,
20 interfaces: HashSet<String>,
21 parents: HashMap<String, Vec<String>>,
22}
23
24#[derive(Debug, Clone)]
25enum SimpleName {
26 Unique(String),
27 Ambiguous,
28}
29
30impl ClassCatalog {
31 pub fn platform() -> Self {
32 let mut catalog = Self::default();
33 for class_name in platform::classes() {
34 catalog.insert_internal_class(class_name);
35 }
36 for interface_name in platform::interfaces() {
37 catalog.mark_interface(interface_name);
38 }
39 for parent in platform::parents() {
40 catalog.insert_parent(parent.owner, parent.parent);
41 }
42 for field in platform::fields() {
43 if let Some(ty) = descriptor_to_ty(field.descriptor) {
44 catalog.insert_field(
45 field.owner,
46 field.name,
47 field.descriptor,
48 ty,
49 field.access_flags,
50 );
51 }
52 }
53 for method in platform::methods() {
54 if let Some(sig) = method_descriptor_to_sig(method.name, method.descriptor) {
55 catalog.insert_method(method.owner, sig, method.access_flags, method.is_interface);
56 }
57 }
58 catalog
59 }
60
61 pub fn insert_internal_class(&mut self, internal_name: impl AsRef<str>) {
62 let Some(internal_name) = normalize_internal_name(internal_name.as_ref()) else {
63 return;
64 };
65 if !self.classes.insert(internal_name.clone()) {
66 return;
67 }
68
69 if let Some((package, simple_name)) = internal_name.rsplit_once('/') {
70 self.packages.insert(package.to_string());
71 self.insert_simple_name(simple_name, &internal_name);
72 } else {
73 self.insert_simple_name(&internal_name, &internal_name);
74 }
75 }
76
77 pub fn mark_interface(&mut self, internal_name: impl AsRef<str>) {
78 if let Some(internal_name) = normalize_internal_name(internal_name.as_ref()) {
79 self.interfaces.insert(internal_name);
80 }
81 }
82
83 pub fn insert_parent(&mut self, owner: impl AsRef<str>, parent: impl AsRef<str>) {
84 let Some(owner) = normalize_internal_name(owner.as_ref()) else {
85 return;
86 };
87 let Some(parent) = normalize_internal_name(parent.as_ref()) else {
88 return;
89 };
90 if owner == parent {
91 return;
92 }
93 let parents = self.parents.entry(owner).or_default();
94 if !parents.contains(&parent) {
95 parents.push(parent);
96 }
97 }
98
99 pub fn insert_field(
100 &mut self,
101 owner: impl AsRef<str>,
102 name: impl AsRef<str>,
103 descriptor: impl AsRef<str>,
104 ty: Ty,
105 access_flags: u16,
106 ) {
107 let owner = owner.as_ref().to_string();
108 let name = name.as_ref().to_string();
109 let descriptor = descriptor.as_ref().to_string();
110 self.fields.insert(
111 (owner.clone(), name.clone()),
112 FieldRef {
113 owner,
114 name,
115 descriptor,
116 ty,
117 access_flags,
118 },
119 );
120 }
121
122 pub fn insert_method(
123 &mut self,
124 owner: impl AsRef<str>,
125 sig: MethodSig,
126 access_flags: u16,
127 is_interface: bool,
128 ) {
129 let owner = owner.as_ref().to_string();
130 let descriptor = sig.descriptor();
131 let opcode = if is_interface {
132 rust_asm::opcodes::INVOKEINTERFACE
133 } else {
134 rust_asm::opcodes::INVOKEVIRTUAL
135 };
136 let method = MethodRef {
137 owner: owner.clone(),
138 name: sig.name.to_string(),
139 descriptor,
140 return_ty: sig.return_type,
141 params: sig.params,
142 opcode,
143 is_interface,
144 is_varargs: access_flags & ACC_VARARGS != 0,
145 access_flags,
146 };
147 self.methods
148 .entry((owner, method.name.clone()))
149 .or_default()
150 .push(method);
151 }
152
153 pub fn contains_internal_class(&self, internal_name: &str) -> bool {
154 self.classes.contains(internal_name)
155 }
156
157 pub fn contains_package(&self, package: &str) -> bool {
158 self.packages.contains(package)
159 }
160
161 pub fn resolve_import(&self, path: &str, is_wildcard: bool) -> bool {
162 let internal_name = path.replace('.', "/");
163 if is_wildcard {
164 self.contains_package(&internal_name)
165 } else {
166 self.contains_internal_class(&internal_name)
167 }
168 }
169
170 pub fn resolve_qualified_name(&self, name: &str) -> Option<&str> {
171 let internal_name = name.replace('.', "/");
172 self.classes.get(internal_name.as_str()).map(String::as_str)
173 }
174
175 pub fn resolve_java_lang(&self, simple_name: &str) -> Option<&str> {
176 let internal_name = format!("java/lang/{simple_name}");
177 self.classes.get(internal_name.as_str()).map(String::as_str)
178 }
179
180 pub fn resolve_simple_name(&self, simple_name: &str) -> Option<&str> {
181 match self.simple_names.get(simple_name)? {
182 SimpleName::Unique(internal_name) => Some(internal_name.as_str()),
183 SimpleName::Ambiguous => None,
184 }
185 }
186
187 pub fn is_interface(&self, internal_name: &str) -> bool {
188 self.interfaces.contains(internal_name)
189 }
190
191 pub fn functional_interface_method(&self, internal_name: &str) -> Option<MethodRef> {
192 if !self.interfaces.contains(internal_name) {
193 return None;
194 }
195
196 let mut sam: Option<MethodRef> = None;
197 for ((owner, _), methods) in &self.methods {
198 if owner == internal_name {
199 for m in methods {
200 if m.is_interface {
201 if sam.is_some() {
202 return None;
203 }
204 sam = Some(m.clone());
205 }
206 }
207 }
208 }
209 sam
210 }
211
212 pub fn resolve_static_field(&self, owner: &str, name: &str) -> Option<FieldRef> {
213 self.lookup_order(owner).into_iter().find_map(|owner| {
214 self.fields
215 .get(&(owner, name.to_string()))
216 .filter(|field| field.access_flags & ACC_STATIC != 0)
217 .cloned()
218 })
219 }
220
221 pub fn resolve_instance_method(
222 &self,
223 receiver: &Ty,
224 name: &str,
225 args: &[Ty],
226 ) -> Option<MethodRef> {
227 let owner = match receiver.erasure() {
228 Ty::Class(owner) => owner.to_string(),
229 Ty::Array(_) => Ty::object().internal_name(),
230 _ => return None,
231 };
232 self.resolve_instance_method_in_hierarchy(&owner, name, args)
233 }
234
235 pub fn resolve_static_method(&self, owner: &str, name: &str, args: &[Ty]) -> Option<MethodRef> {
236 self.lookup_order(owner)
237 .into_iter()
238 .filter_map(|owner| self.best_static_method_on_owner(&owner, name, args))
239 .min_by(compare_method_matches)
240 .map(|candidate| candidate.method)
241 }
242
243 pub fn resolve_constructor(&self, owner: &str, args: &[Ty]) -> Option<MethodRef> {
244 self.best_instance_method_on_owner(owner, "<init>", args)
245 .map(|candidate| candidate.method)
246 }
247
248 fn insert_simple_name(&mut self, simple_name: &str, internal_name: &str) {
249 match self.simple_names.get(simple_name) {
250 Some(SimpleName::Unique(existing)) if existing == internal_name => {}
251 Some(_) => {
252 self.simple_names
253 .insert(simple_name.to_string(), SimpleName::Ambiguous);
254 }
255 None => {
256 self.simple_names.insert(
257 simple_name.to_string(),
258 SimpleName::Unique(internal_name.to_string()),
259 );
260 }
261 }
262 }
263
264 fn resolve_instance_method_in_hierarchy(
265 &self,
266 owner: &str,
267 name: &str,
268 args: &[Ty],
269 ) -> Option<MethodRef> {
270 self.lookup_order(owner)
271 .into_iter()
272 .filter_map(|owner| self.best_instance_method_on_owner(&owner, name, args))
273 .min_by(compare_method_matches)
274 .map(|candidate| candidate.method)
275 }
276
277 fn best_instance_method_on_owner(
278 &self,
279 owner: &str,
280 name: &str,
281 args: &[Ty],
282 ) -> Option<MethodCandidate> {
283 self.methods
284 .get(&(owner.to_string(), name.to_string()))?
285 .iter()
286 .filter(|method| method.access_flags & ACC_STATIC == 0)
287 .filter_map(|method| {
288 method_match_score(self, method, args).map(|score| MethodCandidate {
289 method: method.clone(),
290 score,
291 })
292 })
293 .min_by(compare_method_matches)
294 }
295
296 fn best_static_method_on_owner(
297 &self,
298 owner: &str,
299 name: &str,
300 args: &[Ty],
301 ) -> Option<MethodCandidate> {
302 self.methods
303 .get(&(owner.to_string(), name.to_string()))?
304 .iter()
305 .filter(|method| method.access_flags & ACC_STATIC != 0)
306 .filter_map(|method| {
307 method_match_score(self, method, args).map(|score| MethodCandidate {
308 method: method.clone(),
309 score,
310 })
311 })
312 .min_by(compare_method_matches)
313 }
314
315 fn lookup_order(&self, owner: &str) -> Vec<String> {
316 let mut order = Vec::new();
317 let mut seen = HashSet::new();
318 let mut queue = VecDeque::from([owner.to_string()]);
319
320 while let Some(current) = queue.pop_front() {
321 if !seen.insert(current.clone()) {
322 continue;
323 }
324
325 order.push(current.clone());
326 for parent in self.direct_parents(¤t) {
327 queue.push_back(parent);
328 }
329 }
330
331 order
332 }
333
334 fn direct_parents(&self, owner: &str) -> Vec<String> {
335 let mut parents = self.parents.get(owner).cloned().unwrap_or_default();
336 let has_class_parent = parents
337 .iter()
338 .any(|parent| !self.interfaces.contains(parent));
339 if owner != OBJECT_CLASS
340 && self.classes.contains(owner)
341 && !self.interfaces.contains(owner)
342 && !has_class_parent
343 {
344 parents.push(OBJECT_CLASS.to_string());
345 }
346 parents
347 }
348}
349
350#[derive(Clone)]
351struct MethodCandidate {
352 method: MethodRef,
353 score: MethodScore,
354}
355
356#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
357struct MethodScore {
358 varargs: bool,
359 conversion_cost: u16,
360}
361
362fn compare_method_matches(left: &MethodCandidate, right: &MethodCandidate) -> Ordering {
363 left.score.cmp(&right.score)
364}
365
366fn method_match_score(
367 catalog: &ClassCatalog,
368 method: &MethodRef,
369 actual: &[Ty],
370) -> Option<MethodScore> {
371 if method.is_varargs {
372 return varargs_method_score(catalog, method, actual);
373 }
374 if method.params.len() != actual.len() {
375 return None;
376 }
377 Some(MethodScore {
378 varargs: false,
379 conversion_cost: conversion_cost(catalog, &method.params, actual)?,
380 })
381}
382
383fn varargs_method_score(
384 catalog: &ClassCatalog,
385 method: &MethodRef,
386 actual: &[Ty],
387) -> Option<MethodScore> {
388 let fixed_count = method.params.len().checked_sub(1)?;
389 if actual.len() < fixed_count {
390 return None;
391 }
392
393 if actual.len() == method.params.len()
394 && let Some(cost) = conversion_cost(catalog, &method.params, actual)
395 {
396 return Some(MethodScore {
397 varargs: false,
398 conversion_cost: cost,
399 });
400 }
401
402 let Ty::Array(vararg_element) = method.params.last()?.erasure() else {
403 return None;
404 };
405 let fixed_cost = conversion_cost(
406 catalog,
407 &method.params[..fixed_count],
408 &actual[..fixed_count],
409 )?;
410 let vararg_cost = actual[fixed_count..]
411 .iter()
412 .map(|arg| conversion_score(catalog, &vararg_element, arg))
413 .try_fold(0u16, |sum, cost| sum.checked_add(cost?))?;
414
415 Some(MethodScore {
416 varargs: true,
417 conversion_cost: fixed_cost + vararg_cost,
418 })
419}
420
421fn conversion_cost(catalog: &ClassCatalog, expected: &[Ty], actual: &[Ty]) -> Option<u16> {
422 expected
423 .iter()
424 .zip(actual)
425 .map(|(expected, actual)| conversion_score(catalog, expected, actual))
426 .try_fold(0u16, |sum, score| sum.checked_add(score?))
427}
428
429fn conversion_score(catalog: &ClassCatalog, expected: &Ty, actual: &Ty) -> Option<u16> {
430 let expected = expected.erasure();
431 let actual_is_null = matches!(actual, Ty::Wildcard(None));
432 let actual = actual.erasure();
433 if expected == actual {
434 return Some(0);
435 }
436 if actual_is_null && matches!(expected, Ty::Class(_) | Ty::Array(_)) {
437 return Some(0);
438 }
439 if expected.is_primitive() && actual.is_primitive() && is_assignable(&actual, &expected) {
440 return Some(1);
441 }
442 if let Some(boxed) = boxing_type(&actual)
443 && catalog.is_reference_assignable(&boxed, &expected)
444 {
445 return Some(2);
446 }
447 if let Some(unboxed) = unboxing_type(&actual)
448 && is_assignable(&unboxed, &expected)
449 {
450 return Some(2);
451 }
452 if catalog.is_reference_assignable(&actual, &expected) {
453 return Some(3);
454 }
455 None
456}
457
458impl ClassCatalog {
459 fn is_reference_assignable(&self, actual: &Ty, expected: &Ty) -> bool {
460 match (actual.erasure(), expected.erasure()) {
461 (Ty::Class(actual), Ty::Class(expected)) if actual == expected => true,
462 (Ty::Class(actual), Ty::Class(expected)) => self
463 .lookup_order(actual.as_str())
464 .iter()
465 .any(|parent| parent == expected.as_str()),
466 (Ty::Array(_), Ty::Class(expected)) => expected.as_str() == OBJECT_CLASS,
467 (Ty::Array(actual), Ty::Array(expected)) => {
468 self.is_reference_assignable(&actual, &expected)
469 }
470 _ => false,
471 }
472 }
473}
474
475fn normalize_internal_name(name: &str) -> Option<String> {
476 let name = name.trim().trim_end_matches(".class");
477 if name.is_empty() || name.starts_with('[') || name == "module-info" {
478 return None;
479 }
480 Some(name.replace('.', "/"))
481}