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