use crate::JavaString;
use crate::attributes::Instruction;
use crate::class_file::ClassFile;
use crate::constant::Constant;
use crate::verifiers::bytecode::frame::Frame;
use crate::verifiers::bytecode::type_system::VerificationType;
use crate::verifiers::error::{Result, VerifyError};
#[expect(clippy::unnecessary_wraps)]
#[inline]
pub fn handle_nop() -> Result<()> {
Ok(())
}
pub fn handle_aconst_null(frame: &mut Frame) -> Result<()> {
frame.push(VerificationType::Null)
}
pub fn handle_iconst(frame: &mut Frame) -> Result<()> {
frame.push(VerificationType::Integer)
}
pub fn handle_lconst(frame: &mut Frame) -> Result<()> {
frame.push_category2(VerificationType::Long)
}
pub fn handle_fconst(frame: &mut Frame) -> Result<()> {
frame.push(VerificationType::Float)
}
pub fn handle_dconst(frame: &mut Frame) -> Result<()> {
frame.push_category2(VerificationType::Double)
}
pub fn handle_push_int(frame: &mut Frame) -> Result<()> {
frame.push(VerificationType::Integer)
}
pub fn handle_ldc(frame: &mut Frame, class_file: &ClassFile<'_>, index: u16) -> Result<()> {
let constant = class_file
.constant_pool
.get(index)
.ok_or(VerifyError::InvalidConstantPoolIndex(index))?;
match constant {
Constant::Integer(_) => frame.push(VerificationType::Integer),
Constant::Float(_) => frame.push(VerificationType::Float),
Constant::String(_) => frame.push(VerificationType::java_lang_string()),
Constant::Class(_) => frame.push(VerificationType::java_lang_class()),
Constant::MethodHandle { .. } => frame.push(VerificationType::Object(JavaString::from(
"java/lang/invoke/MethodHandle",
))),
Constant::MethodType { .. } => frame.push(VerificationType::Object(JavaString::from(
"java/lang/invoke/MethodType",
))),
Constant::Dynamic { .. } => {
frame.push(VerificationType::java_lang_object())
}
_ => Err(VerifyError::VerifyError(format!(
"ldc: unsupported constant type at index {index}"
))),
}
}
pub fn handle_ldc2_w(frame: &mut Frame, class_file: &ClassFile<'_>, index: u16) -> Result<()> {
let constant = class_file
.constant_pool
.get(index)
.ok_or(VerifyError::InvalidConstantPoolIndex(index))?;
match constant {
Constant::Long(_) => frame.push_category2(VerificationType::Long),
Constant::Double(_) => frame.push_category2(VerificationType::Double),
_ => Err(VerifyError::VerifyError(format!(
"ldc2_w: expected long or double constant at index {index}"
))),
}
}
pub fn handle_monitorenter(frame: &mut Frame) -> Result<()> {
let objectref = frame.pop()?;
if !objectref.is_reference() {
return Err(VerifyError::VerifyError(format!(
"monitorenter: expected reference, got {objectref}"
)));
}
Ok(())
}
pub fn handle_monitorexit(frame: &mut Frame) -> Result<()> {
let objectref = frame.pop()?;
if !objectref.is_reference() {
return Err(VerifyError::VerifyError(format!(
"monitorexit: expected reference, got {objectref}"
)));
}
Ok(())
}
#[expect(clippy::unnecessary_wraps)]
pub fn handle_wide() -> Result<()> {
Ok(())
}
pub fn handle_reserved(instruction: &Instruction) -> Result<()> {
match instruction {
Instruction::Breakpoint => {
Ok(())
}
Instruction::Impdep1 | Instruction::Impdep2 => {
Err(VerifyError::VerifyError(
"Implementation-dependent instructions are not allowed".to_string(),
))
}
_ => Ok(()),
}
}
pub fn dispatch_misc(
instruction: &Instruction,
frame: &mut Frame,
class_file: &ClassFile<'_>,
) -> Result<bool> {
match instruction {
Instruction::Nop => handle_nop()?,
Instruction::Aconst_null => handle_aconst_null(frame)?,
Instruction::Iconst_m1
| Instruction::Iconst_0
| Instruction::Iconst_1
| Instruction::Iconst_2
| Instruction::Iconst_3
| Instruction::Iconst_4
| Instruction::Iconst_5 => handle_iconst(frame)?,
Instruction::Lconst_0 | Instruction::Lconst_1 => handle_lconst(frame)?,
Instruction::Fconst_0 | Instruction::Fconst_1 | Instruction::Fconst_2 => {
handle_fconst(frame)?;
}
Instruction::Dconst_0 | Instruction::Dconst_1 => handle_dconst(frame)?,
Instruction::Bipush(_) | Instruction::Sipush(_) => handle_push_int(frame)?,
Instruction::Ldc(index) => handle_ldc(frame, class_file, u16::from(*index))?,
Instruction::Ldc_w(index) => handle_ldc(frame, class_file, *index)?,
Instruction::Ldc2_w(index) => handle_ldc2_w(frame, class_file, *index)?,
Instruction::Monitorenter => handle_monitorenter(frame)?,
Instruction::Monitorexit => handle_monitorexit(frame)?,
Instruction::Wide => handle_wide()?,
Instruction::Breakpoint | Instruction::Impdep1 | Instruction::Impdep2 => {
handle_reserved(instruction)?;
}
_ => return Ok(false),
}
Ok(true)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Version;
use crate::constant_pool::ConstantPool;
fn create_test_class_file() -> ClassFile<'static> {
let mut constant_pool = ConstantPool::default();
constant_pool.add(Constant::utf8("Test")).unwrap();
constant_pool.add(Constant::Class(1)).unwrap();
constant_pool.add(Constant::Integer(42)).unwrap();
constant_pool
.add(Constant::Float(std::f32::consts::PI))
.unwrap();
constant_pool.add(Constant::Long(100)).unwrap();
constant_pool
.add(Constant::Double(std::f64::consts::E))
.unwrap();
constant_pool.add(Constant::utf8("Hello")).unwrap();
constant_pool.add(Constant::String(9)).unwrap();
ClassFile {
version: Version::Java8 { minor: 0 },
constant_pool,
access_flags: crate::ClassAccessFlags::PUBLIC,
this_class: 2,
super_class: 0,
interfaces: vec![],
fields: vec![],
methods: vec![],
attributes: vec![],
code_source_url: None,
}
}
#[test]
fn test_nop_success() {
assert!(handle_nop().is_ok());
}
#[test]
fn test_aconst_null_success() {
let mut frame = Frame::new(5, 10);
handle_aconst_null(&mut frame).unwrap();
assert_eq!(*frame.peek().unwrap(), VerificationType::Null);
}
#[test]
fn test_iconst_success() {
let mut frame = Frame::new(5, 10);
handle_iconst(&mut frame).unwrap();
assert_eq!(*frame.peek().unwrap(), VerificationType::Integer);
}
#[test]
fn test_lconst_success() {
let mut frame = Frame::new(5, 10);
handle_lconst(&mut frame).unwrap();
assert_eq!(frame.stack_depth(), 2);
}
#[test]
fn test_fconst_success() {
let mut frame = Frame::new(5, 10);
handle_fconst(&mut frame).unwrap();
assert_eq!(*frame.peek().unwrap(), VerificationType::Float);
}
#[test]
fn test_dconst_success() {
let mut frame = Frame::new(5, 10);
handle_dconst(&mut frame).unwrap();
assert_eq!(frame.stack_depth(), 2);
}
#[test]
fn test_push_int_success() {
let mut frame = Frame::new(5, 10);
handle_push_int(&mut frame).unwrap();
assert_eq!(*frame.peek().unwrap(), VerificationType::Integer);
}
#[test]
fn test_ldc_integer_success() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
handle_ldc(&mut frame, &class_file, 3).unwrap();
assert_eq!(*frame.peek().unwrap(), VerificationType::Integer);
}
#[test]
fn test_ldc_float_success() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
handle_ldc(&mut frame, &class_file, 4).unwrap();
assert_eq!(*frame.peek().unwrap(), VerificationType::Float);
}
#[test]
fn test_ldc_string_success() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
handle_ldc(&mut frame, &class_file, 10).unwrap();
assert_eq!(*frame.peek().unwrap(), VerificationType::java_lang_string());
}
#[test]
fn test_ldc_class_success() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
handle_ldc(&mut frame, &class_file, 2).unwrap();
assert_eq!(*frame.peek().unwrap(), VerificationType::java_lang_class());
}
#[test]
fn test_ldc_invalid_index_fails() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let result = handle_ldc(&mut frame, &class_file, 999);
assert!(result.is_err());
}
#[test]
fn test_ldc_long_not_allowed_fails() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let result = handle_ldc(&mut frame, &class_file, 5);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("unsupported constant type")
);
}
#[test]
fn test_ldc2_w_long_success() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
handle_ldc2_w(&mut frame, &class_file, 5).unwrap();
assert_eq!(frame.stack_depth(), 2);
}
#[test]
fn test_ldc2_w_double_success() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
handle_ldc2_w(&mut frame, &class_file, 7).unwrap();
assert_eq!(frame.stack_depth(), 2);
}
#[test]
fn test_ldc2_w_invalid_index_fails() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let result = handle_ldc2_w(&mut frame, &class_file, 999);
assert!(result.is_err());
}
#[test]
fn test_ldc2_w_integer_not_allowed_fails() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let result = handle_ldc2_w(&mut frame, &class_file, 3);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("expected long or double")
);
}
#[test]
fn test_monitorenter_success() {
let mut frame = Frame::new(5, 10);
frame.push(VerificationType::java_lang_object()).unwrap();
handle_monitorenter(&mut frame).unwrap();
assert!(frame.is_stack_empty());
}
#[test]
fn test_monitorenter_null_success() {
let mut frame = Frame::new(5, 10);
frame.push(VerificationType::Null).unwrap();
handle_monitorenter(&mut frame).unwrap();
assert!(frame.is_stack_empty());
}
#[test]
fn test_monitorenter_non_reference_fails() {
let mut frame = Frame::new(5, 10);
frame.push(VerificationType::Integer).unwrap();
let result = handle_monitorenter(&mut frame);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("expected reference")
);
}
#[test]
fn test_monitorexit_success() {
let mut frame = Frame::new(5, 10);
frame.push(VerificationType::java_lang_object()).unwrap();
handle_monitorexit(&mut frame).unwrap();
assert!(frame.is_stack_empty());
}
#[test]
fn test_monitorexit_non_reference_fails() {
let mut frame = Frame::new(5, 10);
frame.push(VerificationType::Integer).unwrap();
let result = handle_monitorexit(&mut frame);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("expected reference")
);
}
#[test]
fn test_wide_success() {
assert!(handle_wide().is_ok());
}
#[test]
fn test_reserved_breakpoint_success() {
assert!(handle_reserved(&Instruction::Breakpoint).is_ok());
}
#[test]
fn test_reserved_impdep1_fails() {
let result = handle_reserved(&Instruction::Impdep1);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Implementation-dependent")
);
}
#[test]
fn test_reserved_impdep2_fails() {
let result = handle_reserved(&Instruction::Impdep2);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Implementation-dependent")
);
}
#[test]
fn test_dispatch_nop() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Nop, &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_aconst_null() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Aconst_null, &mut frame, &class_file).unwrap();
assert!(handled);
assert_eq!(*frame.peek().unwrap(), VerificationType::Null);
}
#[test]
fn test_dispatch_iconst() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Iconst_0, &mut frame, &class_file).unwrap();
assert!(handled);
assert_eq!(*frame.peek().unwrap(), VerificationType::Integer);
}
#[test]
fn test_dispatch_lconst() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Lconst_0, &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_fconst() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Fconst_0, &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_dconst() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Dconst_0, &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_bipush() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Bipush(42), &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_sipush() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Sipush(1000), &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_ldc() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Ldc(3), &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_ldc_w() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Ldc_w(3), &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_ldc2_w() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Ldc2_w(5), &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_monitorenter() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
frame.push(VerificationType::java_lang_object()).unwrap();
let handled = dispatch_misc(&Instruction::Monitorenter, &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_monitorexit() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
frame.push(VerificationType::java_lang_object()).unwrap();
let handled = dispatch_misc(&Instruction::Monitorexit, &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_wide() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Wide, &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_breakpoint() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Breakpoint, &mut frame, &class_file).unwrap();
assert!(handled);
}
#[test]
fn test_dispatch_impdep1_fails() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let result = dispatch_misc(&Instruction::Impdep1, &mut frame, &class_file);
assert!(result.is_err());
}
#[test]
fn test_dispatch_non_misc() {
let class_file = create_test_class_file();
let mut frame = Frame::new(5, 10);
let handled = dispatch_misc(&Instruction::Iadd, &mut frame, &class_file).unwrap();
assert!(!handled);
}
}