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/// Find the first attribute with the given name from a slice of attributes.
113#[allow(dead_code)]
114pub(crate) fn find_attribute<'a, 'b>(
115    attrs: &'a [AttributeInfo<'b>],
116    name: &str,
117) -> Option<&'a AttributeInfo<'b>> {
118    attrs.iter().find(|a| a.name == name)
119}
120
121/// Extract the `SourceFile` attribute value from a list of attributes.
122pub(crate) fn extract_source_file(attrs: &[AttributeInfo<'_>]) -> Option<String> {
123    attrs.iter().find_map(|a| match &a.data {
124        AttributeData::SourceFile(s) => Some(s.to_string()),
125        _ => None,
126    })
127}
128
129/// Extract method parameter names from the `MethodParameters` attribute.
130pub(crate) fn extract_method_parameter_names(attrs: &[AttributeInfo<'_>]) -> Vec<String> {
131    for attr in attrs {
132        if let AttributeData::MethodParameters(params) = &attr.data {
133            return params
134                .iter()
135                .filter_map(|p| p.name.as_ref().map(std::string::ToString::to_string))
136                .collect();
137        }
138    }
139    vec![]
140}
141
142/// Extract the constant value from a field's `ConstantValue` attribute.
143pub(crate) fn extract_constant_value(attrs: &[AttributeInfo<'_>]) -> Option<ConstantValue> {
144    attrs.iter().find_map(|a| match &a.data {
145        AttributeData::ConstantValue(lit) => Some(literal_to_constant_value(lit)),
146        _ => None,
147    })
148}
149
150// ---------------------------------------------------------------------------
151// Descriptor string parsing (from raw strings, independent of cafebabe)
152// ---------------------------------------------------------------------------
153
154/// Parse a raw JVM field descriptor string into a [`TypeSignature`].
155///
156/// This provides descriptor parsing from raw strings (e.g., when we have a
157/// descriptor as a `&str` rather than a parsed `cafebabe` type).
158///
159/// # Examples
160/// - `"I"` → `Base(Int)`
161/// - `"Ljava/lang/String;"` → `Class { fqn: "java.lang.String", .. }`
162/// - `"[I"` → `Array(Base(Int))`
163/// - `"[[Ljava/lang/String;"` → `Array(Array(Class { .. }))`
164#[allow(dead_code)]
165pub(crate) fn parse_field_descriptor_str(desc: &str) -> Option<TypeSignature> {
166    let (sig, rest) = parse_field_type_from_str(desc)?;
167    if rest.is_empty() { Some(sig) } else { None }
168}
169
170/// Parse a raw JVM method descriptor string, returning parameter types and
171/// return type.
172///
173/// # Examples
174/// - `"()V"` → `(vec![], Base(Void))`
175/// - `"(ILjava/lang/String;)V"` → `(vec![Base(Int), Class{..}], Base(Void))`
176#[allow(dead_code)]
177pub(crate) fn parse_method_descriptor_str(
178    desc: &str,
179) -> Option<(Vec<TypeSignature>, TypeSignature)> {
180    let bytes = desc.as_bytes();
181    if bytes.is_empty() || bytes[0] != b'(' {
182        return None;
183    }
184    let mut pos = 1;
185    let mut params = Vec::new();
186    while pos < bytes.len() && bytes[pos] != b')' {
187        let (sig, rest) = parse_field_type_from_str(&desc[pos..])?;
188        let consumed = desc[pos..].len() - rest.len();
189        pos += consumed;
190        params.push(sig);
191    }
192    if pos >= bytes.len() || bytes[pos] != b')' {
193        return None;
194    }
195    pos += 1;
196    let ret_str = &desc[pos..];
197    let return_type = if ret_str == "V" {
198        TypeSignature::Base(BaseType::Void)
199    } else {
200        let (sig, rest) = parse_field_type_from_str(ret_str)?;
201        if !rest.is_empty() {
202            return None;
203        }
204        sig
205    };
206    Some((params, return_type))
207}
208
209/// Parse one field type from the start of a descriptor string, returning the
210/// parsed type and the remaining unparsed portion.
211fn parse_field_type_from_str(desc: &str) -> Option<(TypeSignature, &str)> {
212    let bytes = desc.as_bytes();
213    if bytes.is_empty() {
214        return None;
215    }
216    match bytes[0] {
217        b'B' => Some((TypeSignature::Base(BaseType::Byte), &desc[1..])),
218        b'C' => Some((TypeSignature::Base(BaseType::Char), &desc[1..])),
219        b'D' => Some((TypeSignature::Base(BaseType::Double), &desc[1..])),
220        b'F' => Some((TypeSignature::Base(BaseType::Float), &desc[1..])),
221        b'I' => Some((TypeSignature::Base(BaseType::Int), &desc[1..])),
222        b'J' => Some((TypeSignature::Base(BaseType::Long), &desc[1..])),
223        b'S' => Some((TypeSignature::Base(BaseType::Short), &desc[1..])),
224        b'Z' => Some((TypeSignature::Base(BaseType::Boolean), &desc[1..])),
225        b'V' => Some((TypeSignature::Base(BaseType::Void), &desc[1..])),
226        b'L' => {
227            // Find the terminating `;`
228            let semi_pos = desc[1..].find(';')?;
229            let class_name = &desc[1..=semi_pos];
230            let fqn = class_name.replace('/', ".");
231            let sig = TypeSignature::Class {
232                fqn,
233                type_arguments: vec![],
234            };
235            Some((sig, &desc[2 + semi_pos..]))
236        }
237        b'[' => {
238            let (inner, rest) = parse_field_type_from_str(&desc[1..])?;
239            Some((TypeSignature::Array(Box::new(inner)), rest))
240        }
241        _ => None,
242    }
243}
244
245// ---------------------------------------------------------------------------
246// Tests
247// ---------------------------------------------------------------------------
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn test_class_name_to_fqn() {
255        assert_eq!(class_name_to_fqn("java/lang/String"), "java.lang.String");
256        assert_eq!(class_name_to_fqn("com/example/Foo"), "com.example.Foo");
257        assert_eq!(class_name_to_fqn("SimpleClass"), "SimpleClass");
258    }
259
260    #[test]
261    fn test_parse_primitive_descriptors() {
262        assert!(matches!(
263            parse_field_descriptor_str("B"),
264            Some(TypeSignature::Base(BaseType::Byte))
265        ));
266        assert!(matches!(
267            parse_field_descriptor_str("C"),
268            Some(TypeSignature::Base(BaseType::Char))
269        ));
270        assert!(matches!(
271            parse_field_descriptor_str("D"),
272            Some(TypeSignature::Base(BaseType::Double))
273        ));
274        assert!(matches!(
275            parse_field_descriptor_str("F"),
276            Some(TypeSignature::Base(BaseType::Float))
277        ));
278        assert!(matches!(
279            parse_field_descriptor_str("I"),
280            Some(TypeSignature::Base(BaseType::Int))
281        ));
282        assert!(matches!(
283            parse_field_descriptor_str("J"),
284            Some(TypeSignature::Base(BaseType::Long))
285        ));
286        assert!(matches!(
287            parse_field_descriptor_str("S"),
288            Some(TypeSignature::Base(BaseType::Short))
289        ));
290        assert!(matches!(
291            parse_field_descriptor_str("Z"),
292            Some(TypeSignature::Base(BaseType::Boolean))
293        ));
294        assert!(matches!(
295            parse_field_descriptor_str("V"),
296            Some(TypeSignature::Base(BaseType::Void))
297        ));
298    }
299
300    #[test]
301    fn test_parse_class_descriptor() {
302        let sig = parse_field_descriptor_str("Ljava/lang/String;").unwrap();
303        match sig {
304            TypeSignature::Class {
305                fqn,
306                type_arguments,
307            } => {
308                assert_eq!(fqn, "java.lang.String");
309                assert!(type_arguments.is_empty());
310            }
311            other => panic!("Expected Class, got {other:?}"),
312        }
313    }
314
315    #[test]
316    fn test_parse_array_descriptor() {
317        let sig = parse_field_descriptor_str("[I").unwrap();
318        match sig {
319            TypeSignature::Array(inner) => {
320                assert!(matches!(*inner, TypeSignature::Base(BaseType::Int)));
321            }
322            other => panic!("Expected Array, got {other:?}"),
323        }
324    }
325
326    #[test]
327    fn test_parse_nested_array_descriptor() {
328        let sig = parse_field_descriptor_str("[[Ljava/lang/String;").unwrap();
329        match sig {
330            TypeSignature::Array(outer) => match *outer {
331                TypeSignature::Array(inner) => match *inner {
332                    TypeSignature::Class { ref fqn, .. } => {
333                        assert_eq!(fqn, "java.lang.String");
334                    }
335                    other => panic!("Expected Class, got {other:?}"),
336                },
337                other => panic!("Expected Array, got {other:?}"),
338            },
339            other => panic!("Expected Array, got {other:?}"),
340        }
341    }
342
343    #[test]
344    fn test_parse_method_descriptor_void_void() {
345        let (params, ret) = parse_method_descriptor_str("()V").unwrap();
346        assert!(params.is_empty());
347        assert!(matches!(ret, TypeSignature::Base(BaseType::Void)));
348    }
349
350    #[test]
351    fn test_parse_method_descriptor_with_params() {
352        let (params, ret) = parse_method_descriptor_str("(ILjava/lang/String;)V").unwrap();
353        assert_eq!(params.len(), 2);
354        assert!(matches!(params[0], TypeSignature::Base(BaseType::Int)));
355        match &params[1] {
356            TypeSignature::Class { fqn, .. } => assert_eq!(fqn, "java.lang.String"),
357            other => panic!("Expected Class, got {other:?}"),
358        }
359        assert!(matches!(ret, TypeSignature::Base(BaseType::Void)));
360    }
361
362    #[test]
363    fn test_parse_method_descriptor_with_return() {
364        let (params, ret) = parse_method_descriptor_str("()Ljava/lang/String;").unwrap();
365        assert!(params.is_empty());
366        match &ret {
367            TypeSignature::Class { fqn, .. } => assert_eq!(fqn, "java.lang.String"),
368            other => panic!("Expected Class, got {other:?}"),
369        }
370    }
371
372    #[test]
373    fn test_parse_method_descriptor_complex() {
374        let (params, ret) =
375            parse_method_descriptor_str("(BJ[Ljava/lang/Object;D)Ljava/util/List;").unwrap();
376        assert_eq!(params.len(), 4);
377        assert!(matches!(params[0], TypeSignature::Base(BaseType::Byte)));
378        assert!(matches!(params[1], TypeSignature::Base(BaseType::Long)));
379        match &params[2] {
380            TypeSignature::Array(inner) => match inner.as_ref() {
381                TypeSignature::Class { fqn, .. } => assert_eq!(fqn, "java.lang.Object"),
382                other => panic!("Expected Class, got {other:?}"),
383            },
384            other => panic!("Expected Array, got {other:?}"),
385        }
386        assert!(matches!(params[3], TypeSignature::Base(BaseType::Double)));
387        match &ret {
388            TypeSignature::Class { fqn, .. } => assert_eq!(fqn, "java.util.List"),
389            other => panic!("Expected Class, got {other:?}"),
390        }
391    }
392
393    #[test]
394    fn test_parse_invalid_descriptor() {
395        assert!(parse_field_descriptor_str("").is_none());
396        assert!(parse_field_descriptor_str("X").is_none());
397        assert!(parse_field_descriptor_str("Ljava/lang/String").is_none()); // missing semicolon
398        assert!(parse_field_descriptor_str("II").is_none()); // trailing chars
399    }
400
401    #[test]
402    fn test_parse_invalid_method_descriptor() {
403        assert!(parse_method_descriptor_str("").is_none());
404        assert!(parse_method_descriptor_str("V").is_none()); // no parens
405        assert!(parse_method_descriptor_str("(").is_none()); // unclosed
406        assert!(parse_method_descriptor_str("()").is_none()); // no return type
407    }
408
409    #[test]
410    fn test_literal_to_constant_value() {
411        assert_eq!(
412            literal_to_constant_value(&LiteralConstant::Integer(42)),
413            ConstantValue::Int(42)
414        );
415        assert_eq!(
416            literal_to_constant_value(&LiteralConstant::Long(123_456_789)),
417            ConstantValue::Long(123_456_789)
418        );
419        assert_eq!(
420            literal_to_constant_value(&LiteralConstant::Float(std::f32::consts::PI)),
421            ConstantValue::Float(OrderedFloat(std::f32::consts::PI))
422        );
423        assert_eq!(
424            literal_to_constant_value(&LiteralConstant::Double(std::f64::consts::E)),
425            ConstantValue::Double(OrderedFloat(std::f64::consts::E))
426        );
427        assert_eq!(
428            literal_to_constant_value(&LiteralConstant::String("hello".into())),
429            ConstantValue::String("hello".to_owned())
430        );
431    }
432}