1use cafebabe::attributes::AttributeData;
18use cafebabe::constant_pool::{
19 BootstrapArgument, MethodHandle, ReferenceKind as CafeReferenceKind,
20};
21
22use crate::stub::model::{LambdaTargetStub, ReferenceKind};
23
24use super::constants::class_name_to_fqn;
25
26const LAMBDA_METAFACTORY_CLASS: &str = "java/lang/invoke/LambdaMetafactory";
32
33const METAFACTORY_METHOD: &str = "metafactory";
35
36const ALT_METAFACTORY_METHOD: &str = "altMetafactory";
39
40const IMPL_METHOD_ARG_INDEX: usize = 2;
49
50pub fn extract_lambda_targets(class: &cafebabe::ClassFile<'_>) -> Vec<LambdaTargetStub> {
71 let mut targets = Vec::new();
72
73 for attr in &class.attributes {
74 if let AttributeData::BootstrapMethods(entries) = &attr.data {
75 for (idx, entry) in entries.iter().enumerate() {
76 if !is_lambda_metafactory(&entry.method) {
78 continue;
79 }
80
81 match extract_impl_handle(idx, &entry.arguments) {
83 Some(stub) => targets.push(stub),
84 None => continue,
85 }
86 }
87 }
88 }
89
90 targets
91}
92
93fn is_lambda_metafactory(handle: &MethodHandle<'_>) -> bool {
102 handle.class_name.as_ref() == LAMBDA_METAFACTORY_CLASS
103 && (handle.member_ref.name.as_ref() == METAFACTORY_METHOD
104 || handle.member_ref.name.as_ref() == ALT_METAFACTORY_METHOD)
105}
106
107fn extract_impl_handle(
113 bootstrap_idx: usize,
114 arguments: &[BootstrapArgument<'_>],
115) -> Option<LambdaTargetStub> {
116 if arguments.len() <= IMPL_METHOD_ARG_INDEX {
117 log::warn!(
118 "BootstrapMethods entry {bootstrap_idx}: expected at least {} arguments, \
119 found {}; skipping",
120 IMPL_METHOD_ARG_INDEX + 1,
121 arguments.len(),
122 );
123 return None;
124 }
125
126 match &arguments[IMPL_METHOD_ARG_INDEX] {
127 BootstrapArgument::MethodHandle(handle) => {
128 let reference_kind = match convert_reference_kind(handle.kind) {
129 Some(kind) => kind,
130 None => {
131 log::warn!(
132 "BootstrapMethods entry {bootstrap_idx}: \
133 unsupported reference kind {:?}; skipping",
134 handle.kind,
135 );
136 return None;
137 }
138 };
139
140 Some(LambdaTargetStub {
141 owner_fqn: class_name_to_fqn(handle.class_name.as_ref()),
142 method_name: handle.member_ref.name.to_string(),
143 method_descriptor: handle.member_ref.descriptor.to_string(),
144 reference_kind,
145 })
146 }
147 other => {
148 log::warn!(
149 "BootstrapMethods entry {bootstrap_idx}: expected MethodHandle at \
150 argument index {IMPL_METHOD_ARG_INDEX}, found {kind}; skipping",
151 kind = bootstrap_arg_kind_name(other),
152 );
153 None
154 }
155 }
156}
157
158fn convert_reference_kind(kind: CafeReferenceKind) -> Option<ReferenceKind> {
160 Some(match kind {
161 CafeReferenceKind::GetField => ReferenceKind::GetField,
162 CafeReferenceKind::GetStatic => ReferenceKind::GetStatic,
163 CafeReferenceKind::PutField => ReferenceKind::PutField,
164 CafeReferenceKind::PutStatic => ReferenceKind::PutStatic,
165 CafeReferenceKind::InvokeVirtual => ReferenceKind::InvokeVirtual,
166 CafeReferenceKind::InvokeStatic => ReferenceKind::InvokeStatic,
167 CafeReferenceKind::InvokeSpecial => ReferenceKind::InvokeSpecial,
168 CafeReferenceKind::NewInvokeSpecial => ReferenceKind::NewInvokeSpecial,
169 CafeReferenceKind::InvokeInterface => ReferenceKind::InvokeInterface,
170 })
171}
172
173fn bootstrap_arg_kind_name(arg: &BootstrapArgument<'_>) -> &'static str {
176 match arg {
177 BootstrapArgument::LiteralConstant(_) => "LiteralConstant",
178 BootstrapArgument::ClassInfo(_) => "ClassInfo",
179 BootstrapArgument::MethodHandle(_) => "MethodHandle",
180 BootstrapArgument::MethodType(_) => "MethodType",
181 }
182}
183
184#[cfg(test)]
189mod tests {
190 use super::*;
191 use cafebabe::attributes::{AttributeData, AttributeInfo, BootstrapMethodEntry};
192 use cafebabe::constant_pool::{
193 BootstrapArgument, MemberKind, MethodHandle, NameAndType,
194 ReferenceKind as CafeReferenceKind,
195 };
196 use std::borrow::Cow;
197
198 fn metafactory_handle<'a>() -> MethodHandle<'a> {
202 MethodHandle {
203 kind: CafeReferenceKind::InvokeStatic,
204 class_name: Cow::Borrowed(LAMBDA_METAFACTORY_CLASS),
205 member_kind: MemberKind::Method,
206 member_ref: NameAndType {
207 name: Cow::Borrowed(METAFACTORY_METHOD),
208 descriptor: Cow::Borrowed(
209 "(Ljava/lang/invoke/MethodHandles$Lookup;\
210 Ljava/lang/String;\
211 Ljava/lang/invoke/MethodType;\
212 Ljava/lang/invoke/MethodType;\
213 Ljava/lang/invoke/MethodHandle;\
214 Ljava/lang/invoke/MethodType;\
215 )Ljava/lang/invoke/CallSite;",
216 ),
217 },
218 }
219 }
220
221 fn alt_metafactory_handle<'a>() -> MethodHandle<'a> {
223 MethodHandle {
224 kind: CafeReferenceKind::InvokeStatic,
225 class_name: Cow::Borrowed(LAMBDA_METAFACTORY_CLASS),
226 member_kind: MemberKind::Method,
227 member_ref: NameAndType {
228 name: Cow::Borrowed(ALT_METAFACTORY_METHOD),
229 descriptor: Cow::Borrowed(
230 "(Ljava/lang/invoke/MethodHandles$Lookup;\
231 Ljava/lang/String;\
232 Ljava/lang/invoke/MethodType;\
233 [Ljava/lang/Object;\
234 )Ljava/lang/invoke/CallSite;",
235 ),
236 },
237 }
238 }
239
240 fn string_concat_handle<'a>() -> MethodHandle<'a> {
242 MethodHandle {
243 kind: CafeReferenceKind::InvokeStatic,
244 class_name: Cow::Borrowed("java/lang/invoke/StringConcatFactory"),
245 member_kind: MemberKind::Method,
246 member_ref: NameAndType {
247 name: Cow::Borrowed("makeConcatWithConstants"),
248 descriptor: Cow::Borrowed(
249 "(Ljava/lang/invoke/MethodHandles$Lookup;\
250 Ljava/lang/String;\
251 Ljava/lang/invoke/MethodType;\
252 Ljava/lang/String;\
253 [Ljava/lang/Object;\
254 )Ljava/lang/invoke/CallSite;",
255 ),
256 },
257 }
258 }
259
260 fn lambda_bootstrap_args<'a>(
265 impl_kind: CafeReferenceKind,
266 impl_class: &'a str,
267 impl_name: &'a str,
268 impl_descriptor: &'a str,
269 ) -> Vec<BootstrapArgument<'a>> {
270 vec![
271 BootstrapArgument::MethodType(Cow::Borrowed("(Ljava/lang/Object;)Ljava/lang/Object;")),
273 BootstrapArgument::MethodType(Cow::Borrowed("(Ljava/lang/String;)Ljava/lang/String;")),
275 BootstrapArgument::MethodHandle(MethodHandle {
277 kind: impl_kind,
278 class_name: Cow::Borrowed(impl_class),
279 member_kind: MemberKind::Method,
280 member_ref: NameAndType {
281 name: Cow::Borrowed(impl_name),
282 descriptor: Cow::Borrowed(impl_descriptor),
283 },
284 }),
285 ]
286 }
287
288 #[test]
296 fn no_bootstrap_methods_returns_empty() {
297 let attrs: Vec<AttributeInfo<'_>> = vec![];
300 let targets = extract_lambda_targets_from_attrs(&attrs);
301 assert!(targets.is_empty(), "Expected empty targets");
302 }
303
304 #[test]
307 fn lambda_target_from_method_reference() {
308 let entries = vec![BootstrapMethodEntry {
309 method: metafactory_handle(),
310 arguments: lambda_bootstrap_args(
311 CafeReferenceKind::InvokeVirtual,
312 "java/lang/String",
313 "toUpperCase",
314 "()Ljava/lang/String;",
315 ),
316 }];
317
318 let attrs = vec![AttributeInfo {
319 name: Cow::Borrowed("BootstrapMethods"),
320 data: AttributeData::BootstrapMethods(entries),
321 }];
322
323 let targets = extract_lambda_targets_from_attrs(&attrs);
324
325 assert_eq!(targets.len(), 1);
326 assert_eq!(targets[0].owner_fqn, "java.lang.String");
327 assert_eq!(targets[0].method_name, "toUpperCase");
328 assert_eq!(targets[0].method_descriptor, "()Ljava/lang/String;");
329 assert_eq!(targets[0].reference_kind, ReferenceKind::InvokeVirtual);
330 }
331
332 #[test]
335 fn method_reference_with_invoke_static() {
336 let entries = vec![BootstrapMethodEntry {
337 method: metafactory_handle(),
338 arguments: lambda_bootstrap_args(
339 CafeReferenceKind::InvokeStatic,
340 "java/lang/Integer",
341 "parseInt",
342 "(Ljava/lang/String;)I",
343 ),
344 }];
345
346 let attrs = vec![AttributeInfo {
347 name: Cow::Borrowed("BootstrapMethods"),
348 data: AttributeData::BootstrapMethods(entries),
349 }];
350
351 let targets = extract_lambda_targets_from_attrs(&attrs);
352
353 assert_eq!(targets.len(), 1);
354 assert_eq!(targets[0].owner_fqn, "java.lang.Integer");
355 assert_eq!(targets[0].method_name, "parseInt");
356 assert_eq!(targets[0].method_descriptor, "(Ljava/lang/String;)I");
357 assert_eq!(targets[0].reference_kind, ReferenceKind::InvokeStatic);
358 }
359
360 #[test]
363 fn non_lambda_metafactory_skipped() {
364 let entries = vec![BootstrapMethodEntry {
365 method: string_concat_handle(),
366 arguments: vec![BootstrapArgument::LiteralConstant(
367 cafebabe::constant_pool::LiteralConstant::String(Cow::Borrowed("\u{1}Hello \u{1}")),
368 )],
369 }];
370
371 let attrs = vec![AttributeInfo {
372 name: Cow::Borrowed("BootstrapMethods"),
373 data: AttributeData::BootstrapMethods(entries),
374 }];
375
376 let targets = extract_lambda_targets_from_attrs(&attrs);
377 assert!(
378 targets.is_empty(),
379 "Non-LambdaMetafactory should be skipped"
380 );
381 }
382
383 #[test]
386 fn multiple_lambda_targets() {
387 let entries = vec![
388 BootstrapMethodEntry {
390 method: metafactory_handle(),
391 arguments: lambda_bootstrap_args(
392 CafeReferenceKind::InvokeVirtual,
393 "java/lang/String",
394 "toUpperCase",
395 "()Ljava/lang/String;",
396 ),
397 },
398 BootstrapMethodEntry {
400 method: string_concat_handle(),
401 arguments: vec![],
402 },
403 BootstrapMethodEntry {
405 method: metafactory_handle(),
406 arguments: lambda_bootstrap_args(
407 CafeReferenceKind::NewInvokeSpecial,
408 "java/util/ArrayList",
409 "<init>",
410 "()V",
411 ),
412 },
413 BootstrapMethodEntry {
415 method: alt_metafactory_handle(),
416 arguments: lambda_bootstrap_args(
417 CafeReferenceKind::InvokeStatic,
418 "com/example/Service",
419 "lambda$process$0",
420 "(Ljava/lang/Object;)V",
421 ),
422 },
423 ];
424
425 let attrs = vec![AttributeInfo {
426 name: Cow::Borrowed("BootstrapMethods"),
427 data: AttributeData::BootstrapMethods(entries),
428 }];
429
430 let targets = extract_lambda_targets_from_attrs(&attrs);
431
432 assert_eq!(
434 targets.len(),
435 3,
436 "Expected 3 lambda targets, got {}",
437 targets.len()
438 );
439
440 assert_eq!(targets[0].owner_fqn, "java.lang.String");
441 assert_eq!(targets[0].method_name, "toUpperCase");
442 assert_eq!(targets[0].reference_kind, ReferenceKind::InvokeVirtual);
443
444 assert_eq!(targets[1].owner_fqn, "java.util.ArrayList");
445 assert_eq!(targets[1].method_name, "<init>");
446 assert_eq!(targets[1].reference_kind, ReferenceKind::NewInvokeSpecial);
447
448 assert_eq!(targets[2].owner_fqn, "com.example.Service");
449 assert_eq!(targets[2].method_name, "lambda$process$0");
450 assert_eq!(targets[2].reference_kind, ReferenceKind::InvokeStatic);
451 }
452
453 #[test]
456 fn reference_kind_mapping_exhaustive() {
457 let cafe_to_model = [
458 (CafeReferenceKind::GetField, ReferenceKind::GetField),
459 (CafeReferenceKind::GetStatic, ReferenceKind::GetStatic),
460 (CafeReferenceKind::PutField, ReferenceKind::PutField),
461 (CafeReferenceKind::PutStatic, ReferenceKind::PutStatic),
462 (
463 CafeReferenceKind::InvokeVirtual,
464 ReferenceKind::InvokeVirtual,
465 ),
466 (CafeReferenceKind::InvokeStatic, ReferenceKind::InvokeStatic),
467 (
468 CafeReferenceKind::InvokeSpecial,
469 ReferenceKind::InvokeSpecial,
470 ),
471 (
472 CafeReferenceKind::NewInvokeSpecial,
473 ReferenceKind::NewInvokeSpecial,
474 ),
475 (
476 CafeReferenceKind::InvokeInterface,
477 ReferenceKind::InvokeInterface,
478 ),
479 ];
480
481 for (cafe_kind, expected_model_kind) in &cafe_to_model {
482 let result = convert_reference_kind(*cafe_kind);
483 assert_eq!(
484 result,
485 Some(*expected_model_kind),
486 "Mapping failed for {cafe_kind:?}"
487 );
488 }
489 }
490
491 #[test]
494 fn too_few_arguments_skipped_with_warning() {
495 let entries = vec![BootstrapMethodEntry {
497 method: metafactory_handle(),
498 arguments: vec![
499 BootstrapArgument::MethodType(Cow::Borrowed(
500 "(Ljava/lang/Object;)Ljava/lang/Object;",
501 )),
502 BootstrapArgument::MethodType(Cow::Borrowed(
503 "(Ljava/lang/String;)Ljava/lang/String;",
504 )),
505 ],
506 }];
507
508 let attrs = vec![AttributeInfo {
509 name: Cow::Borrowed("BootstrapMethods"),
510 data: AttributeData::BootstrapMethods(entries),
511 }];
512
513 let targets = extract_lambda_targets_from_attrs(&attrs);
514 assert!(targets.is_empty(), "Malformed entry should be skipped");
515 }
516
517 #[test]
520 fn wrong_argument_type_at_index_2_skipped() {
521 let entries = vec![BootstrapMethodEntry {
523 method: metafactory_handle(),
524 arguments: vec![
525 BootstrapArgument::MethodType(Cow::Borrowed(
526 "(Ljava/lang/Object;)Ljava/lang/Object;",
527 )),
528 BootstrapArgument::MethodType(Cow::Borrowed(
529 "(Ljava/lang/String;)Ljava/lang/String;",
530 )),
531 BootstrapArgument::MethodType(Cow::Borrowed("()V")),
532 ],
533 }];
534
535 let attrs = vec![AttributeInfo {
536 name: Cow::Borrowed("BootstrapMethods"),
537 data: AttributeData::BootstrapMethods(entries),
538 }];
539
540 let targets = extract_lambda_targets_from_attrs(&attrs);
541 assert!(
542 targets.is_empty(),
543 "Wrong type at index 2 should be skipped"
544 );
545 }
546
547 #[test]
550 fn interface_method_reference() {
551 let entries = vec![BootstrapMethodEntry {
552 method: metafactory_handle(),
553 arguments: lambda_bootstrap_args(
554 CafeReferenceKind::InvokeInterface,
555 "java/util/List",
556 "size",
557 "()I",
558 ),
559 }];
560
561 let attrs = vec![AttributeInfo {
562 name: Cow::Borrowed("BootstrapMethods"),
563 data: AttributeData::BootstrapMethods(entries),
564 }];
565
566 let targets = extract_lambda_targets_from_attrs(&attrs);
567
568 assert_eq!(targets.len(), 1);
569 assert_eq!(targets[0].owner_fqn, "java.util.List");
570 assert_eq!(targets[0].method_name, "size");
571 assert_eq!(targets[0].reference_kind, ReferenceKind::InvokeInterface);
572 }
573
574 #[test]
577 fn fqn_conversion_internal_to_dotted() {
578 let entries = vec![BootstrapMethodEntry {
579 method: metafactory_handle(),
580 arguments: lambda_bootstrap_args(
581 CafeReferenceKind::InvokeStatic,
582 "com/example/deeply/nested/ServiceImpl",
583 "handle",
584 "(Ljava/lang/Object;)V",
585 ),
586 }];
587
588 let attrs = vec![AttributeInfo {
589 name: Cow::Borrowed("BootstrapMethods"),
590 data: AttributeData::BootstrapMethods(entries),
591 }];
592
593 let targets = extract_lambda_targets_from_attrs(&attrs);
594
595 assert_eq!(targets.len(), 1);
596 assert_eq!(
597 targets[0].owner_fqn,
598 "com.example.deeply.nested.ServiceImpl"
599 );
600 }
601
602 fn extract_lambda_targets_from_attrs(attrs: &[AttributeInfo<'_>]) -> Vec<LambdaTargetStub> {
607 let mut targets = Vec::new();
608 for attr in attrs {
609 if let AttributeData::BootstrapMethods(entries) = &attr.data {
610 for (idx, entry) in entries.iter().enumerate() {
611 if !is_lambda_metafactory(&entry.method) {
612 continue;
613 }
614 if let Some(stub) = extract_impl_handle(idx, &entry.arguments) {
615 targets.push(stub);
616 }
617 }
618 }
619 }
620 targets
621 }
622}