use crate::class_file::{ClassFile, ConstantPoolEntry};
use crate::error::{to_runtime_error_enum, RuntimeError};
use crate::memory::{StackFrame, Value};
use byteorder::{BigEndian, ReadBytesExt};
use std::io::Cursor;
use super::Interpreter;
use super::InterpreterResult;
fn get_bootstrap_method(class: &ClassFile, index: u16) -> Option<(u16, Vec<u16>)> {
for attr in &class.attributes {
let name = class.get_string(attr.attribute_name_index)?;
if name == "BootstrapMethods" {
let mut cursor = Cursor::new(&attr.info);
let num_bootstrap_methods = cursor.read_u16::<BigEndian>().ok()?;
if index >= num_bootstrap_methods {
return None;
}
for i in 0..num_bootstrap_methods {
let method_ref = cursor.read_u16::<BigEndian>().ok()?;
let num_args = cursor.read_u16::<BigEndian>().ok()?;
let mut args = Vec::with_capacity(num_args as usize);
for _ in 0..num_args {
args.push(cursor.read_u16::<BigEndian>().ok()?);
}
if i == index {
return Some((method_ref, args));
}
}
}
}
None
}
fn resolve_method_handle(class: &ClassFile, handle_index: u16) -> Option<(String, String)> {
let entry = class.constant_pool.get(handle_index as usize)?;
match entry {
ConstantPoolEntry::ConstantMethodHandle {
reference_index, ..
} => {
let ref_entry = class.constant_pool.get(*reference_index as usize)?;
match ref_entry {
ConstantPoolEntry::ConstantMethodref {
class_index,
name_and_type_index,
}
| ConstantPoolEntry::ConstantInterfaceMethodref {
class_index,
name_and_type_index,
} => {
let class_name = class.get_class_name_from_index(*class_index)?;
let nt_entry = class.constant_pool.get(*name_and_type_index as usize)?;
match nt_entry {
ConstantPoolEntry::ConstantNameAndType { name_index, .. } => {
let method_name = class.get_string(*name_index)?;
Some((class_name, method_name))
}
_ => None,
}
}
_ => None,
}
}
_ => None,
}
}
fn get_constant_value_as_string(class: &ClassFile, index: u16) -> String {
if let Some(entry) = class.constant_pool.get(index as usize) {
match entry {
ConstantPoolEntry::ConstantString { string_index } => {
class.get_string(*string_index).unwrap_or_default()
}
ConstantPoolEntry::ConstantInteger { bytes } => bytes.to_string(),
ConstantPoolEntry::ConstantFloat { bytes } => bytes.to_string(),
ConstantPoolEntry::ConstantLong { bytes } => bytes.to_string(),
ConstantPoolEntry::ConstantDouble { bytes } => bytes.to_string(),
ConstantPoolEntry::ConstantClass { name_index } => {
class.get_string(*name_index).unwrap_or_default()
}
ConstantPoolEntry::ConstantUtf8 { bytes } => String::from_utf8_lossy(bytes).to_string(),
_ => format!("Constant#{}", index),
}
} else {
String::new()
}
}
impl Interpreter {
pub(crate) fn native_println(&mut self, value: Value) -> InterpreterResult {
match value {
Value::Int(i) => println!("{}", i),
Value::Float(f) => println!("{}", f),
Value::Long(l) => println!("{}", l),
Value::Double(d) => println!("{}", d),
Value::Reference(addr) => {
if let Some(obj) = self.memory.heap.get_object(addr) {
if obj.class_name == "java/lang/String" {
if let Some(string_data) = &obj.string_data {
println!("{}", string_data);
} else {
println!("[String]");
}
} else {
println!("[{}]", obj.class_name);
}
} else {
println!("null");
}
}
Value::ArrayRef(addr) => println!("[Array@{}]", addr),
Value::Null => println!("null"),
_ => println!("{:?}", value),
}
Ok(())
}
pub(crate) fn handle_invokedynamic(
&mut self,
class: &ClassFile,
frame: &mut StackFrame,
index: usize,
) -> InterpreterResult {
let cp_entry = class.constant_pool.get(index).ok_or_else(|| {
to_runtime_error_enum(RuntimeError::IllegalArgument(format!(
"Constant pool index {} out of bounds",
index
)))
})?;
let (bootstrap_index, name_and_type_index) = match cp_entry {
ConstantPoolEntry::ConstantInvokeDynamic {
bootstrap_method_attr_index,
name_and_type_index,
} => (bootstrap_method_attr_index, name_and_type_index),
_ => {
return Err(to_runtime_error_enum(RuntimeError::IllegalArgument(
"Expected InvokeDynamic constant".to_string(),
)));
}
};
let name_and_type_entry = class.constant_pool.get(*name_and_type_index as usize);
let (_method_name, descriptor) = match name_and_type_entry {
Some(ConstantPoolEntry::ConstantNameAndType {
name_index,
descriptor_index,
}) => (
class.get_string(*name_index).unwrap_or_default(),
class.get_string(*descriptor_index).unwrap_or_default(),
),
_ => {
return Err(to_runtime_error_enum(RuntimeError::IllegalArgument(
"Invalid NameAndType in invokedynamic".to_string(),
)));
}
};
let (bsm_ref, bsm_args) =
get_bootstrap_method(class, *bootstrap_index).ok_or_else(|| {
to_runtime_error_enum(RuntimeError::IllegalArgument(format!(
"Invalid bootstrap method index {}",
bootstrap_index
)))
})?;
let (bsm_class, bsm_name) = resolve_method_handle(class, bsm_ref).ok_or_else(|| {
to_runtime_error_enum(RuntimeError::IllegalArgument(
"Could not resolve bootstrap method handle".to_string(),
))
})?;
if bsm_class == "java/lang/invoke/StringConcatFactory"
&& bsm_name == "makeConcatWithConstants"
{
let param_count = crate::interpreter::descriptor::count_parameters(&descriptor);
let mut stack_args = Vec::with_capacity(param_count);
for _ in 0..param_count {
stack_args.push(frame.pop()?);
}
stack_args.reverse();
let mut result = String::new();
if bsm_args.is_empty() {
for arg in stack_args {
let s = self.value_to_string(&arg);
result.push_str(&s);
}
} else {
let recipe_index = bsm_args[0];
let recipe = get_constant_value_as_string(class, recipe_index);
let mut arg_idx = 0;
let mut const_idx = 0;
for c in recipe.chars() {
if c == '\u{1}' {
if arg_idx < stack_args.len() {
let s = self.value_to_string(&stack_args[arg_idx]);
result.push_str(&s);
arg_idx += 1;
}
} else if c == '\u{2}' {
if const_idx < bsm_args.len() - 1 {
let const_val_idx = bsm_args[1 + const_idx];
let s = get_constant_value_as_string(class, const_val_idx);
result.push_str(&s);
const_idx += 1;
}
} else {
result.push(c);
}
}
}
let addr = self.memory.heap.allocate_string(result);
frame.push(Value::Reference(addr))?;
} else {
return Err(to_runtime_error_enum(RuntimeError::Unimplemented(format!(
"Unsupported invokedynamic bootstrap method: {}.{}",
bsm_class, bsm_name
))));
}
Ok(())
}
}