use crate::verifiers::bytecode::type_system::VerificationType;
use crate::verifiers::context::VerificationContext;
use crate::verifiers::error::{Result, VerifyError};
#[derive(Clone, Debug, PartialEq)]
pub struct Frame {
pub locals: Vec<VerificationType>,
pub stack: Vec<VerificationType>,
max_stack: usize,
}
impl Frame {
#[must_use]
pub fn new(max_locals: usize, max_stack: usize) -> Self {
Self {
locals: vec![VerificationType::Top; max_locals],
stack: Vec::with_capacity(max_stack),
max_stack,
}
}
#[must_use]
pub fn with_locals(locals: Vec<VerificationType>, max_stack: usize) -> Self {
Self {
locals,
stack: Vec::with_capacity(max_stack),
max_stack,
}
}
#[inline]
#[must_use]
pub fn stack_depth(&self) -> usize {
self.stack.len()
}
#[inline]
#[must_use]
pub fn locals_count(&self) -> usize {
self.locals.len()
}
#[inline]
#[must_use]
pub fn max_stack(&self) -> usize {
self.max_stack
}
#[inline]
#[must_use]
pub fn is_stack_empty(&self) -> bool {
self.stack.is_empty()
}
pub fn push(&mut self, ty: VerificationType) -> Result<()> {
if self.stack.len() >= self.max_stack {
return Err(VerifyError::VerifyError(
"Operand stack overflow".to_string(),
));
}
self.stack.push(ty);
Ok(())
}
pub fn push_category2(&mut self, ty: VerificationType) -> Result<()> {
debug_assert!(ty.is_category2());
if self.stack.len() + 2 > self.max_stack {
return Err(VerifyError::VerifyError(
"Operand stack overflow".to_string(),
));
}
self.stack.push(ty);
self.stack.push(VerificationType::Top);
Ok(())
}
pub fn pop(&mut self) -> Result<VerificationType> {
self.stack
.pop()
.ok_or_else(|| VerifyError::VerifyError("Operand stack underflow".to_string()))
}
pub fn pop_category2(&mut self) -> Result<VerificationType> {
let top = self.pop()?;
if top != VerificationType::Top {
return Err(VerifyError::VerifyError(format!(
"Expected Top for category 2 second slot, got {top}"
)));
}
let value = self.pop()?;
if !value.is_category2() {
return Err(VerifyError::VerifyError(format!(
"Expected category 2 type, got {value}"
)));
}
Ok(value)
}
pub fn peek(&self) -> Result<&VerificationType> {
self.stack
.last()
.ok_or_else(|| VerifyError::VerifyError("Operand stack underflow".to_string()))
}
pub fn peek_at(&self, depth: usize) -> Result<&VerificationType> {
let len = self.stack.len();
if depth >= len {
return Err(VerifyError::VerifyError(format!(
"Stack depth {depth} exceeds stack size {len}"
)));
}
Ok(&self.stack[len - 1 - depth])
}
pub fn clear_stack(&mut self) {
self.stack.clear();
}
pub fn get_local(&self, index: u16) -> Result<&VerificationType> {
let idx = index as usize;
if idx >= self.locals.len() {
return Err(VerifyError::VerifyError(format!(
"Local variable index {index} out of bounds (max {})",
self.locals.len()
)));
}
Ok(&self.locals[idx])
}
pub fn set_local(&mut self, index: u16, ty: VerificationType) -> Result<()> {
let idx = index as usize;
if idx >= self.locals.len() {
return Err(VerifyError::VerifyError(format!(
"Local variable index {index} out of bounds (max {})",
self.locals.len()
)));
}
self.locals[idx] = ty;
Ok(())
}
pub fn set_local_category2(&mut self, index: u16, ty: VerificationType) -> Result<()> {
debug_assert!(ty.is_category2());
let idx = index as usize;
if idx + 1 >= self.locals.len() {
return Err(VerifyError::VerifyError(format!(
"Local variable index {index} + 1 out of bounds for category 2 type"
)));
}
self.locals[idx] = ty;
self.locals[idx + 1] = VerificationType::Top;
Ok(())
}
pub fn get_local_category2(&self, index: u16) -> Result<&VerificationType> {
let idx = index as usize;
if idx + 1 >= self.locals.len() {
return Err(VerifyError::VerifyError(format!(
"Local variable index {index} + 1 out of bounds for category 2 type"
)));
}
let ty = &self.locals[idx];
if !ty.is_category2() {
return Err(VerifyError::VerifyError(format!(
"Expected category 2 type at local {index}, got {ty}"
)));
}
let second = &self.locals[idx + 1];
if *second != VerificationType::Top {
return Err(VerifyError::VerifyError(format!(
"Expected Top at local {} for category 2, got {second}",
index + 1
)));
}
Ok(ty)
}
pub fn merge<C: VerificationContext>(&mut self, other: &Frame, context: &C) -> Result<bool> {
if self.stack.len() != other.stack.len() {
return Err(VerifyError::VerifyError(format!(
"Stack depth mismatch at merge point: {} vs {}",
self.stack.len(),
other.stack.len()
)));
}
if self.locals.len() < other.locals.len() {
return Err(VerifyError::VerifyError(format!(
"Locals count mismatch: {} vs {}",
self.locals.len(),
other.locals.len()
)));
}
let mut changed = false;
for (target, source) in self.locals.iter_mut().zip(&other.locals) {
if target != source {
let merged = target.merge(source, context)?;
if *target != merged {
*target = merged;
changed = true;
}
}
}
for (target, source) in self.stack.iter_mut().zip(&other.stack) {
if target != source {
let merged = target.merge(source, context)?;
if *target != merged {
*target = merged;
changed = true;
}
}
}
Ok(changed)
}
pub fn initialize_object(
&mut self,
uninitialized: &VerificationType,
initialized: &VerificationType,
) {
for local in &mut self.locals {
if local == uninitialized {
*local = initialized.clone();
}
}
for stack_entry in &mut self.stack {
if stack_entry == uninitialized {
*stack_entry = initialized.clone();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::JavaString;
struct MockContext;
impl VerificationContext for MockContext {
fn is_subclass(&self, subclass: &str, superclass: &str) -> Result<bool> {
Ok(subclass == superclass)
}
fn is_assignable(&self, target: &str, source: &str) -> Result<bool> {
Ok(target == source || target == "java/lang/Object")
}
fn common_superclass(&self, _class1: &str, _class2: &str) -> Result<String> {
Ok("java/lang/Object".to_string())
}
}
#[test]
fn test_new_frame() {
let frame = Frame::new(5, 10);
assert_eq!(frame.locals_count(), 5);
assert_eq!(frame.max_stack(), 10);
assert!(frame.is_stack_empty());
assert_eq!(frame.stack_depth(), 0);
}
#[test]
fn test_push_pop() {
let mut frame = Frame::new(5, 10);
frame.push(VerificationType::Integer).unwrap();
assert_eq!(frame.stack_depth(), 1);
let ty = frame.pop().unwrap();
assert_eq!(ty, VerificationType::Integer);
assert!(frame.is_stack_empty());
}
#[test]
fn test_stack_overflow() {
let mut frame = Frame::new(5, 2);
frame.push(VerificationType::Integer).unwrap();
frame.push(VerificationType::Integer).unwrap();
let result = frame.push(VerificationType::Integer);
assert!(result.is_err());
}
#[test]
fn test_stack_underflow() {
let mut frame = Frame::new(5, 10);
let result = frame.pop();
assert!(result.is_err());
}
#[test]
fn test_category2_operations() {
let mut frame = Frame::new(5, 10);
frame.push_category2(VerificationType::Long).unwrap();
assert_eq!(frame.stack_depth(), 2);
let ty = frame.pop_category2().unwrap();
assert_eq!(ty, VerificationType::Long);
assert!(frame.is_stack_empty());
}
#[test]
fn test_local_operations() {
let mut frame = Frame::new(5, 10);
frame.set_local(2, VerificationType::Integer).unwrap();
let ty = frame.get_local(2).unwrap();
assert_eq!(*ty, VerificationType::Integer);
}
#[test]
fn test_local_category2() {
let mut frame = Frame::new(5, 10);
frame
.set_local_category2(1, VerificationType::Double)
.unwrap();
let ty = frame.get_local_category2(1).unwrap();
assert_eq!(*ty, VerificationType::Double);
assert_eq!(*frame.get_local(2).unwrap(), VerificationType::Top);
}
#[test]
fn test_local_out_of_bounds() {
let frame = Frame::new(5, 10);
let result = frame.get_local(10);
assert!(result.is_err());
}
#[test]
fn test_merge_same_frames() {
let ctx = MockContext;
let mut frame1 = Frame::new(5, 10);
frame1.set_local(0, VerificationType::Integer).unwrap();
frame1.push(VerificationType::Float).unwrap();
let frame2 = frame1.clone();
let changed = frame1.merge(&frame2, &ctx).unwrap();
assert!(!changed);
}
#[test]
fn test_merge_different_types() {
let ctx = MockContext;
let mut frame1 = Frame::new(5, 10);
frame1
.set_local(
0,
VerificationType::Object(JavaString::from("java/lang/String")),
)
.unwrap();
let mut frame2 = Frame::new(5, 10);
frame2
.set_local(
0,
VerificationType::Object(JavaString::from("java/lang/Integer")),
)
.unwrap();
let changed = frame1.merge(&frame2, &ctx).unwrap();
assert!(changed);
assert_eq!(
*frame1.get_local(0).unwrap(),
VerificationType::Object(JavaString::from("java/lang/Object"))
);
}
#[test]
fn test_merge_stack_depth_mismatch() {
let ctx = MockContext;
let mut frame1 = Frame::new(5, 10);
frame1.push(VerificationType::Integer).unwrap();
let frame2 = Frame::new(5, 10);
let result = frame1.merge(&frame2, &ctx);
assert!(result.is_err());
}
#[test]
fn test_initialize_object() {
let mut frame = Frame::new(5, 10);
let uninit = VerificationType::Uninitialized(0);
let init = VerificationType::Object(JavaString::from("java/lang/Object"));
frame.set_local(0, uninit.clone()).unwrap();
frame.push(uninit.clone()).unwrap();
frame.initialize_object(&uninit, &init);
assert_eq!(*frame.get_local(0).unwrap(), init);
assert_eq!(*frame.peek().unwrap(), init);
}
#[test]
fn test_peek_at() {
let mut frame = Frame::new(5, 10);
frame.push(VerificationType::Integer).unwrap();
frame.push(VerificationType::Float).unwrap();
frame.push(VerificationType::Long).unwrap();
assert_eq!(*frame.peek_at(0).unwrap(), VerificationType::Long);
assert_eq!(*frame.peek_at(1).unwrap(), VerificationType::Float);
assert_eq!(*frame.peek_at(2).unwrap(), VerificationType::Integer);
assert!(frame.peek_at(3).is_err());
}
}