Skip to main content

sqry_classpath/bytecode/
constants.rs

1//! JVM constant pool helper utilities.
2//!
3//! Provides extraction functions for working with parsed `cafebabe` class file
4//! data, converting constant pool items and descriptors into our stub model types.
5
6use cafebabe::attributes::{AttributeData, AttributeInfo};
7use cafebabe::constant_pool::LiteralConstant;
8use cafebabe::descriptors::{FieldDescriptor, FieldType, MethodDescriptor, ReturnDescriptor};
9
10use crate::stub::model::{BaseType, ConstantValue, OrderedFloat, TypeSignature};
11
12// ---------------------------------------------------------------------------
13// Descriptor → TypeSignature conversion
14// ---------------------------------------------------------------------------
15
16/// Convert a `cafebabe` [`FieldDescriptor`] into our [`TypeSignature`].
17///
18/// Handles primitive types, object types (converting `/` to `.` in FQNs),
19/// and array types (recursively wrapping in `TypeSignature::Array`).
20pub(crate) fn field_descriptor_to_type(desc: &FieldDescriptor<'_>) -> TypeSignature {
21    let base = field_type_to_signature(&desc.field_type);
22    wrap_in_arrays(base, desc.dimensions)
23}
24
25/// Convert a `cafebabe` [`ReturnDescriptor`] into our [`TypeSignature`].
26///
27/// Maps `void` to `TypeSignature::Base(BaseType::Void)` and delegates field
28/// descriptors to [`field_descriptor_to_type`].
29pub(crate) fn return_descriptor_to_type(desc: &ReturnDescriptor<'_>) -> TypeSignature {
30    match desc {
31        ReturnDescriptor::Void => TypeSignature::Base(BaseType::Void),
32        ReturnDescriptor::Return(fd) => field_descriptor_to_type(fd),
33    }
34}
35
36/// Convert a `cafebabe` [`MethodDescriptor`] into parameter types and return type.
37pub(crate) fn method_descriptor_to_types(
38    desc: &MethodDescriptor<'_>,
39) -> (Vec<TypeSignature>, TypeSignature) {
40    let param_types = desc
41        .parameters
42        .iter()
43        .map(field_descriptor_to_type)
44        .collect();
45    let return_type = return_descriptor_to_type(&desc.return_type);
46    (param_types, return_type)
47}
48
49/// Convert a single `cafebabe` [`FieldType`] into a [`TypeSignature`]
50/// (without array wrapping).
51fn field_type_to_signature(ft: &FieldType<'_>) -> TypeSignature {
52    match ft {
53        FieldType::Byte => TypeSignature::Base(BaseType::Byte),
54        FieldType::Char => TypeSignature::Base(BaseType::Char),
55        FieldType::Double => TypeSignature::Base(BaseType::Double),
56        FieldType::Float => TypeSignature::Base(BaseType::Float),
57        FieldType::Integer => TypeSignature::Base(BaseType::Int),
58        FieldType::Long => TypeSignature::Base(BaseType::Long),
59        FieldType::Short => TypeSignature::Base(BaseType::Short),
60        FieldType::Boolean => TypeSignature::Base(BaseType::Boolean),
61        FieldType::Object(class_name) => TypeSignature::Class {
62            fqn: class_name_to_fqn(class_name),
63            type_arguments: vec![],
64        },
65    }
66}
67
68/// Wrap a [`TypeSignature`] in `n` layers of `TypeSignature::Array`.
69fn wrap_in_arrays(inner: TypeSignature, dimensions: u8) -> TypeSignature {
70    let mut sig = inner;
71    for _ in 0..dimensions {
72        sig = TypeSignature::Array(Box::new(sig));
73    }
74    sig
75}
76
77// ---------------------------------------------------------------------------
78// Class name helpers
79// ---------------------------------------------------------------------------
80
81/// Convert a JVM internal class name (with `/` separators) to a fully qualified
82/// name (with `.` separators).
83///
84/// For example, `"java/util/HashMap"` becomes `"java.util.HashMap"`.
85pub(crate) fn class_name_to_fqn(name: &str) -> String {
86    name.replace('/', ".")
87}
88
89// ---------------------------------------------------------------------------
90// Constant value extraction
91// ---------------------------------------------------------------------------
92
93/// Convert a `cafebabe` [`LiteralConstant`] to our [`ConstantValue`].
94pub(crate) fn literal_to_constant_value(lit: &LiteralConstant<'_>) -> ConstantValue {
95    match lit {
96        LiteralConstant::Integer(v) => ConstantValue::Int(*v),
97        LiteralConstant::Long(v) => ConstantValue::Long(*v),
98        LiteralConstant::Float(v) => ConstantValue::Float(OrderedFloat(*v)),
99        LiteralConstant::Double(v) => ConstantValue::Double(OrderedFloat(*v)),
100        LiteralConstant::String(s) => ConstantValue::String(s.to_string()),
101        LiteralConstant::StringBytes(bytes) => {
102            // Best-effort: try UTF-8, fall back to lossy conversion.
103            ConstantValue::String(String::from_utf8_lossy(bytes).into_owned())
104        }
105    }
106}
107
108// ---------------------------------------------------------------------------
109// Attribute search helpers
110// ---------------------------------------------------------------------------
111
112/// Extract the `SourceFile` attribute value from a list of attributes.
113pub(crate) fn extract_source_file(attrs: &[AttributeInfo<'_>]) -> Option<String> {
114    attrs.iter().find_map(|a| match &a.data {
115        AttributeData::SourceFile(s) => Some(s.to_string()),
116        _ => None,
117    })
118}
119
120/// Extract method parameter names from the `MethodParameters` attribute.
121pub(crate) fn extract_method_parameter_names(attrs: &[AttributeInfo<'_>]) -> Vec<String> {
122    for attr in attrs {
123        if let AttributeData::MethodParameters(params) = &attr.data {
124            return params
125                .iter()
126                .filter_map(|p| p.name.as_ref().map(std::string::ToString::to_string))
127                .collect();
128        }
129    }
130    vec![]
131}
132
133/// Extract the constant value from a field's `ConstantValue` attribute.
134pub(crate) fn extract_constant_value(attrs: &[AttributeInfo<'_>]) -> Option<ConstantValue> {
135    attrs.iter().find_map(|a| match &a.data {
136        AttributeData::ConstantValue(lit) => Some(literal_to_constant_value(lit)),
137        _ => None,
138    })
139}
140
141// ---------------------------------------------------------------------------
142// Tests
143// ---------------------------------------------------------------------------
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_class_name_to_fqn() {
151        assert_eq!(class_name_to_fqn("java/lang/String"), "java.lang.String");
152        assert_eq!(class_name_to_fqn("com/example/Foo"), "com.example.Foo");
153        assert_eq!(class_name_to_fqn("SimpleClass"), "SimpleClass");
154    }
155
156    #[test]
157    fn test_literal_to_constant_value() {
158        assert_eq!(
159            literal_to_constant_value(&LiteralConstant::Integer(42)),
160            ConstantValue::Int(42)
161        );
162        assert_eq!(
163            literal_to_constant_value(&LiteralConstant::Long(123_456_789)),
164            ConstantValue::Long(123_456_789)
165        );
166        assert_eq!(
167            literal_to_constant_value(&LiteralConstant::Float(std::f32::consts::PI)),
168            ConstantValue::Float(OrderedFloat(std::f32::consts::PI))
169        );
170        assert_eq!(
171            literal_to_constant_value(&LiteralConstant::Double(std::f64::consts::E)),
172            ConstantValue::Double(OrderedFloat(std::f64::consts::E))
173        );
174        assert_eq!(
175            literal_to_constant_value(&LiteralConstant::String("hello".into())),
176            ConstantValue::String("hello".to_owned())
177        );
178    }
179}