Skip to main content

rusty_javac/call_resolver/
catalog.rs

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(&current) {
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}