use cafebabe::attributes::AttributeData;
use cafebabe::constant_pool::{
BootstrapArgument, MethodHandle, ReferenceKind as CafeReferenceKind,
};
use crate::stub::model::{LambdaTargetStub, ReferenceKind};
use super::constants::class_name_to_fqn;
const LAMBDA_METAFACTORY_CLASS: &str = "java/lang/invoke/LambdaMetafactory";
const METAFACTORY_METHOD: &str = "metafactory";
const ALT_METAFACTORY_METHOD: &str = "altMetafactory";
const IMPL_METHOD_ARG_INDEX: usize = 2;
#[must_use]
#[allow(clippy::needless_continue)] pub fn extract_lambda_targets(class: &cafebabe::ClassFile<'_>) -> Vec<LambdaTargetStub> {
let mut targets = Vec::new();
for attr in &class.attributes {
if let AttributeData::BootstrapMethods(entries) = &attr.data {
for (idx, entry) in entries.iter().enumerate() {
if !is_lambda_metafactory(&entry.method) {
continue;
}
match extract_impl_handle(idx, &entry.arguments) {
Some(stub) => targets.push(stub),
#[allow(clippy::needless_continue)] None => continue,
}
}
}
}
targets
}
fn is_lambda_metafactory(handle: &MethodHandle<'_>) -> bool {
handle.class_name.as_ref() == LAMBDA_METAFACTORY_CLASS
&& (handle.member_ref.name.as_ref() == METAFACTORY_METHOD
|| handle.member_ref.name.as_ref() == ALT_METAFACTORY_METHOD)
}
fn extract_impl_handle(
bootstrap_idx: usize,
arguments: &[BootstrapArgument<'_>],
) -> Option<LambdaTargetStub> {
if arguments.len() <= IMPL_METHOD_ARG_INDEX {
log::warn!(
"BootstrapMethods entry {bootstrap_idx}: expected at least {} arguments, \
found {}; skipping",
IMPL_METHOD_ARG_INDEX + 1,
arguments.len(),
);
return None;
}
match &arguments[IMPL_METHOD_ARG_INDEX] {
BootstrapArgument::MethodHandle(handle) => {
#[allow(clippy::manual_let_else)] let reference_kind = if let Some(kind) = convert_reference_kind(handle.kind) {
kind
} else {
log::warn!(
"BootstrapMethods entry {bootstrap_idx}: \
unsupported reference kind {:?}; skipping",
handle.kind,
);
return None;
};
Some(LambdaTargetStub {
owner_fqn: class_name_to_fqn(handle.class_name.as_ref()),
method_name: handle.member_ref.name.to_string(),
method_descriptor: handle.member_ref.descriptor.to_string(),
reference_kind,
})
}
other => {
log::warn!(
"BootstrapMethods entry {bootstrap_idx}: expected MethodHandle at \
argument index {IMPL_METHOD_ARG_INDEX}, found {kind}; skipping",
kind = bootstrap_arg_kind_name(other),
);
None
}
}
}
#[allow(clippy::unnecessary_wraps)] fn convert_reference_kind(kind: CafeReferenceKind) -> Option<ReferenceKind> {
Some(match kind {
CafeReferenceKind::GetField => ReferenceKind::GetField,
CafeReferenceKind::GetStatic => ReferenceKind::GetStatic,
CafeReferenceKind::PutField => ReferenceKind::PutField,
CafeReferenceKind::PutStatic => ReferenceKind::PutStatic,
CafeReferenceKind::InvokeVirtual => ReferenceKind::InvokeVirtual,
CafeReferenceKind::InvokeStatic => ReferenceKind::InvokeStatic,
CafeReferenceKind::InvokeSpecial => ReferenceKind::InvokeSpecial,
CafeReferenceKind::NewInvokeSpecial => ReferenceKind::NewInvokeSpecial,
CafeReferenceKind::InvokeInterface => ReferenceKind::InvokeInterface,
})
}
fn bootstrap_arg_kind_name(arg: &BootstrapArgument<'_>) -> &'static str {
match arg {
BootstrapArgument::LiteralConstant(_) => "LiteralConstant",
BootstrapArgument::ClassInfo(_) => "ClassInfo",
BootstrapArgument::MethodHandle(_) => "MethodHandle",
BootstrapArgument::MethodType(_) => "MethodType",
}
}
#[cfg(test)]
mod tests {
use super::*;
use cafebabe::attributes::{AttributeData, AttributeInfo, BootstrapMethodEntry};
use cafebabe::constant_pool::{
BootstrapArgument, MemberKind, MethodHandle, NameAndType,
ReferenceKind as CafeReferenceKind,
};
use std::borrow::Cow;
fn metafactory_handle<'a>() -> MethodHandle<'a> {
MethodHandle {
kind: CafeReferenceKind::InvokeStatic,
class_name: Cow::Borrowed(LAMBDA_METAFACTORY_CLASS),
member_kind: MemberKind::Method,
member_ref: NameAndType {
name: Cow::Borrowed(METAFACTORY_METHOD),
descriptor: Cow::Borrowed(
"(Ljava/lang/invoke/MethodHandles$Lookup;\
Ljava/lang/String;\
Ljava/lang/invoke/MethodType;\
Ljava/lang/invoke/MethodType;\
Ljava/lang/invoke/MethodHandle;\
Ljava/lang/invoke/MethodType;\
)Ljava/lang/invoke/CallSite;",
),
},
}
}
fn alt_metafactory_handle<'a>() -> MethodHandle<'a> {
MethodHandle {
kind: CafeReferenceKind::InvokeStatic,
class_name: Cow::Borrowed(LAMBDA_METAFACTORY_CLASS),
member_kind: MemberKind::Method,
member_ref: NameAndType {
name: Cow::Borrowed(ALT_METAFACTORY_METHOD),
descriptor: Cow::Borrowed(
"(Ljava/lang/invoke/MethodHandles$Lookup;\
Ljava/lang/String;\
Ljava/lang/invoke/MethodType;\
[Ljava/lang/Object;\
)Ljava/lang/invoke/CallSite;",
),
},
}
}
fn string_concat_handle<'a>() -> MethodHandle<'a> {
MethodHandle {
kind: CafeReferenceKind::InvokeStatic,
class_name: Cow::Borrowed("java/lang/invoke/StringConcatFactory"),
member_kind: MemberKind::Method,
member_ref: NameAndType {
name: Cow::Borrowed("makeConcatWithConstants"),
descriptor: Cow::Borrowed(
"(Ljava/lang/invoke/MethodHandles$Lookup;\
Ljava/lang/String;\
Ljava/lang/invoke/MethodType;\
Ljava/lang/String;\
[Ljava/lang/Object;\
)Ljava/lang/invoke/CallSite;",
),
},
}
}
fn lambda_bootstrap_args<'a>(
impl_kind: CafeReferenceKind,
impl_class: &'a str,
impl_name: &'a str,
impl_descriptor: &'a str,
) -> Vec<BootstrapArgument<'a>> {
vec![
BootstrapArgument::MethodType(Cow::Borrowed("(Ljava/lang/Object;)Ljava/lang/Object;")),
BootstrapArgument::MethodType(Cow::Borrowed("(Ljava/lang/String;)Ljava/lang/String;")),
BootstrapArgument::MethodHandle(MethodHandle {
kind: impl_kind,
class_name: Cow::Borrowed(impl_class),
member_kind: MemberKind::Method,
member_ref: NameAndType {
name: Cow::Borrowed(impl_name),
descriptor: Cow::Borrowed(impl_descriptor),
},
}),
]
}
#[test]
fn no_bootstrap_methods_returns_empty() {
let attrs: Vec<AttributeInfo<'_>> = vec![];
let targets = extract_lambda_targets_from_attrs(&attrs);
assert!(targets.is_empty(), "Expected empty targets");
}
#[test]
fn lambda_target_from_method_reference() {
let entries = vec![BootstrapMethodEntry {
method: metafactory_handle(),
arguments: lambda_bootstrap_args(
CafeReferenceKind::InvokeVirtual,
"java/lang/String",
"toUpperCase",
"()Ljava/lang/String;",
),
}];
let attrs = vec![AttributeInfo {
name: Cow::Borrowed("BootstrapMethods"),
data: AttributeData::BootstrapMethods(entries),
}];
let targets = extract_lambda_targets_from_attrs(&attrs);
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].owner_fqn, "java.lang.String");
assert_eq!(targets[0].method_name, "toUpperCase");
assert_eq!(targets[0].method_descriptor, "()Ljava/lang/String;");
assert_eq!(targets[0].reference_kind, ReferenceKind::InvokeVirtual);
}
#[test]
fn method_reference_with_invoke_static() {
let entries = vec![BootstrapMethodEntry {
method: metafactory_handle(),
arguments: lambda_bootstrap_args(
CafeReferenceKind::InvokeStatic,
"java/lang/Integer",
"parseInt",
"(Ljava/lang/String;)I",
),
}];
let attrs = vec![AttributeInfo {
name: Cow::Borrowed("BootstrapMethods"),
data: AttributeData::BootstrapMethods(entries),
}];
let targets = extract_lambda_targets_from_attrs(&attrs);
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].owner_fqn, "java.lang.Integer");
assert_eq!(targets[0].method_name, "parseInt");
assert_eq!(targets[0].method_descriptor, "(Ljava/lang/String;)I");
assert_eq!(targets[0].reference_kind, ReferenceKind::InvokeStatic);
}
#[test]
fn non_lambda_metafactory_skipped() {
let entries = vec![BootstrapMethodEntry {
method: string_concat_handle(),
arguments: vec![BootstrapArgument::LiteralConstant(
cafebabe::constant_pool::LiteralConstant::String(Cow::Borrowed("\u{1}Hello \u{1}")),
)],
}];
let attrs = vec![AttributeInfo {
name: Cow::Borrowed("BootstrapMethods"),
data: AttributeData::BootstrapMethods(entries),
}];
let targets = extract_lambda_targets_from_attrs(&attrs);
assert!(
targets.is_empty(),
"Non-LambdaMetafactory should be skipped"
);
}
#[test]
fn multiple_lambda_targets() {
let entries = vec![
BootstrapMethodEntry {
method: metafactory_handle(),
arguments: lambda_bootstrap_args(
CafeReferenceKind::InvokeVirtual,
"java/lang/String",
"toUpperCase",
"()Ljava/lang/String;",
),
},
BootstrapMethodEntry {
method: string_concat_handle(),
arguments: vec![],
},
BootstrapMethodEntry {
method: metafactory_handle(),
arguments: lambda_bootstrap_args(
CafeReferenceKind::NewInvokeSpecial,
"java/util/ArrayList",
"<init>",
"()V",
),
},
BootstrapMethodEntry {
method: alt_metafactory_handle(),
arguments: lambda_bootstrap_args(
CafeReferenceKind::InvokeStatic,
"com/example/Service",
"lambda$process$0",
"(Ljava/lang/Object;)V",
),
},
];
let attrs = vec![AttributeInfo {
name: Cow::Borrowed("BootstrapMethods"),
data: AttributeData::BootstrapMethods(entries),
}];
let targets = extract_lambda_targets_from_attrs(&attrs);
assert_eq!(
targets.len(),
3,
"Expected 3 lambda targets, got {}",
targets.len()
);
assert_eq!(targets[0].owner_fqn, "java.lang.String");
assert_eq!(targets[0].method_name, "toUpperCase");
assert_eq!(targets[0].reference_kind, ReferenceKind::InvokeVirtual);
assert_eq!(targets[1].owner_fqn, "java.util.ArrayList");
assert_eq!(targets[1].method_name, "<init>");
assert_eq!(targets[1].reference_kind, ReferenceKind::NewInvokeSpecial);
assert_eq!(targets[2].owner_fqn, "com.example.Service");
assert_eq!(targets[2].method_name, "lambda$process$0");
assert_eq!(targets[2].reference_kind, ReferenceKind::InvokeStatic);
}
#[test]
fn reference_kind_mapping_exhaustive() {
let cafe_to_model = [
(CafeReferenceKind::GetField, ReferenceKind::GetField),
(CafeReferenceKind::GetStatic, ReferenceKind::GetStatic),
(CafeReferenceKind::PutField, ReferenceKind::PutField),
(CafeReferenceKind::PutStatic, ReferenceKind::PutStatic),
(
CafeReferenceKind::InvokeVirtual,
ReferenceKind::InvokeVirtual,
),
(CafeReferenceKind::InvokeStatic, ReferenceKind::InvokeStatic),
(
CafeReferenceKind::InvokeSpecial,
ReferenceKind::InvokeSpecial,
),
(
CafeReferenceKind::NewInvokeSpecial,
ReferenceKind::NewInvokeSpecial,
),
(
CafeReferenceKind::InvokeInterface,
ReferenceKind::InvokeInterface,
),
];
for (cafe_kind, expected_model_kind) in &cafe_to_model {
let result = convert_reference_kind(*cafe_kind);
assert_eq!(
result,
Some(*expected_model_kind),
"Mapping failed for {cafe_kind:?}"
);
}
}
#[test]
fn too_few_arguments_skipped_with_warning() {
let entries = vec![BootstrapMethodEntry {
method: metafactory_handle(),
arguments: vec![
BootstrapArgument::MethodType(Cow::Borrowed(
"(Ljava/lang/Object;)Ljava/lang/Object;",
)),
BootstrapArgument::MethodType(Cow::Borrowed(
"(Ljava/lang/String;)Ljava/lang/String;",
)),
],
}];
let attrs = vec![AttributeInfo {
name: Cow::Borrowed("BootstrapMethods"),
data: AttributeData::BootstrapMethods(entries),
}];
let targets = extract_lambda_targets_from_attrs(&attrs);
assert!(targets.is_empty(), "Malformed entry should be skipped");
}
#[test]
fn wrong_argument_type_at_index_2_skipped() {
let entries = vec![BootstrapMethodEntry {
method: metafactory_handle(),
arguments: vec![
BootstrapArgument::MethodType(Cow::Borrowed(
"(Ljava/lang/Object;)Ljava/lang/Object;",
)),
BootstrapArgument::MethodType(Cow::Borrowed(
"(Ljava/lang/String;)Ljava/lang/String;",
)),
BootstrapArgument::MethodType(Cow::Borrowed("()V")),
],
}];
let attrs = vec![AttributeInfo {
name: Cow::Borrowed("BootstrapMethods"),
data: AttributeData::BootstrapMethods(entries),
}];
let targets = extract_lambda_targets_from_attrs(&attrs);
assert!(
targets.is_empty(),
"Wrong type at index 2 should be skipped"
);
}
#[test]
fn interface_method_reference() {
let entries = vec![BootstrapMethodEntry {
method: metafactory_handle(),
arguments: lambda_bootstrap_args(
CafeReferenceKind::InvokeInterface,
"java/util/List",
"size",
"()I",
),
}];
let attrs = vec![AttributeInfo {
name: Cow::Borrowed("BootstrapMethods"),
data: AttributeData::BootstrapMethods(entries),
}];
let targets = extract_lambda_targets_from_attrs(&attrs);
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].owner_fqn, "java.util.List");
assert_eq!(targets[0].method_name, "size");
assert_eq!(targets[0].reference_kind, ReferenceKind::InvokeInterface);
}
#[test]
fn fqn_conversion_internal_to_dotted() {
let entries = vec![BootstrapMethodEntry {
method: metafactory_handle(),
arguments: lambda_bootstrap_args(
CafeReferenceKind::InvokeStatic,
"com/example/deeply/nested/ServiceImpl",
"handle",
"(Ljava/lang/Object;)V",
),
}];
let attrs = vec![AttributeInfo {
name: Cow::Borrowed("BootstrapMethods"),
data: AttributeData::BootstrapMethods(entries),
}];
let targets = extract_lambda_targets_from_attrs(&attrs);
assert_eq!(targets.len(), 1);
assert_eq!(
targets[0].owner_fqn,
"com.example.deeply.nested.ServiceImpl"
);
}
fn extract_lambda_targets_from_attrs(attrs: &[AttributeInfo<'_>]) -> Vec<LambdaTargetStub> {
let mut targets = Vec::new();
for attr in attrs {
if let AttributeData::BootstrapMethods(entries) = &attr.data {
for (idx, entry) in entries.iter().enumerate() {
if !is_lambda_metafactory(&entry.method) {
continue;
}
if let Some(stub) = extract_impl_handle(idx, &entry.arguments) {
targets.push(stub);
}
}
}
}
targets
}
}