sim-codec-classfile 0.1.0

Bounded, lossless Java Virtual Machine classfile codec for SIM
Documentation
fn decode_constant(
    reader: &mut ByteReader<'_>,
    index: u16,
    tag: u8,
    major: u16,
) -> Result<Constant, ConstantPoolError> {
    let read_u1 = |reader: &mut ByteReader<'_>| {
        reader
            .read_u1()
            .map_err(|e| ConstantPoolError::bytes(index, e))
    };
    let read_u2 = |reader: &mut ByteReader<'_>| {
        reader
            .read_u2()
            .map_err(|e| ConstantPoolError::bytes(index, e))
    };
    let read_u4 = |reader: &mut ByteReader<'_>| {
        reader
            .read_u4()
            .map_err(|e| ConstantPoolError::bytes(index, e))
    };
    let version = |minimum| {
        if major < minimum {
            Err(ConstantPoolError::new(
                ConstantPoolErrorKind::Version,
                index,
                None,
                format!("constant tag {tag} requires classfile major version {minimum}"),
            ))
        } else {
            Ok(())
        }
    };
    Ok(match tag {
        1 => {
            let length = read_u2(reader)?;
            let bytes = reader
                .take(usize::from(length))
                .map_err(|e| ConstantPoolError::bytes(index, e))?;
            Constant::Utf8(
                decode_modified_utf8(bytes, reader.allocation_budget())
                    .map_err(|e| ConstantPoolError::bytes(index, e))?,
            )
        }
        3 => Constant::Integer(read_u4(reader)?),
        4 => Constant::Float(read_u4(reader)?),
        5 => Constant::Long((u64::from(read_u4(reader)?) << 32) | u64::from(read_u4(reader)?)),
        6 => Constant::Double((u64::from(read_u4(reader)?) << 32) | u64::from(read_u4(reader)?)),
        7 => Constant::Class {
            name_index: read_u2(reader)?,
        },
        8 => Constant::String {
            string_index: read_u2(reader)?,
        },
        9 => Constant::Fieldref {
            class_index: read_u2(reader)?,
            name_and_type_index: read_u2(reader)?,
        },
        10 => Constant::Methodref {
            class_index: read_u2(reader)?,
            name_and_type_index: read_u2(reader)?,
        },
        11 => Constant::InterfaceMethodref {
            class_index: read_u2(reader)?,
            name_and_type_index: read_u2(reader)?,
        },
        12 => Constant::NameAndType {
            name_index: read_u2(reader)?,
            descriptor_index: read_u2(reader)?,
        },
        15 => {
            version(51)?;
            Constant::MethodHandle {
                reference_kind: read_u1(reader)?,
                reference_index: read_u2(reader)?,
            }
        }
        16 => {
            version(51)?;
            Constant::MethodType {
                descriptor_index: read_u2(reader)?,
            }
        }
        17 => {
            version(55)?;
            Constant::Dynamic {
                bootstrap_method_attr_index: read_u2(reader)?,
                name_and_type_index: read_u2(reader)?,
            }
        }
        18 => {
            version(51)?;
            Constant::InvokeDynamic {
                bootstrap_method_attr_index: read_u2(reader)?,
                name_and_type_index: read_u2(reader)?,
            }
        }
        19 => {
            version(53)?;
            Constant::Module {
                name_index: read_u2(reader)?,
            }
        }
        20 => {
            version(53)?;
            Constant::Package {
                name_index: read_u2(reader)?,
            }
        }
        _ => {
            return Err(ConstantPoolError::new(
                ConstantPoolErrorKind::UnknownTag,
                index,
                None,
                format!("unknown constant tag {tag}"),
            ));
        }
    })
}

fn expect(
    pool: &ConstantPool,
    source: u16,
    target: u16,
    category: &'static str,
    predicate: impl FnOnce(&Constant) -> bool,
) -> Result<(), ConstantPoolError> {
    let value = pool.entry(source, target)?;
    if predicate(value) {
        Ok(())
    } else {
        Err(ConstantPoolError::new(
            ConstantPoolErrorKind::WrongCategory,
            source,
            Some(target),
            format!("index {source} requires {category} at index {target}"),
        ))
    }
}

fn validate_constant(
    pool: &ConstantPool,
    index: u16,
    value: &Constant,
    major: u16,
) -> Result<(), ConstantPoolError> {
    let utf8 = |value: &Constant| matches!(value, Constant::Utf8(_));
    let class = |value: &Constant| matches!(value, Constant::Class { .. });
    let nat = |value: &Constant| matches!(value, Constant::NameAndType { .. });
    match value {
        Constant::Class { name_index }
        | Constant::String {
            string_index: name_index,
        }
        | Constant::MethodType {
            descriptor_index: name_index,
        }
        | Constant::Module { name_index }
        | Constant::Package { name_index } => expect(pool, index, *name_index, "Utf8", utf8),
        Constant::NameAndType {
            name_index,
            descriptor_index,
        } => {
            expect(pool, index, *name_index, "Utf8", utf8)?;
            expect(pool, index, *descriptor_index, "Utf8", utf8)
        }
        Constant::Fieldref {
            class_index,
            name_and_type_index,
        }
        | Constant::Methodref {
            class_index,
            name_and_type_index,
        }
        | Constant::InterfaceMethodref {
            class_index,
            name_and_type_index,
        } => {
            expect(pool, index, *class_index, "Class", class)?;
            expect(pool, index, *name_and_type_index, "NameAndType", nat)
        }
        Constant::Dynamic {
            name_and_type_index,
            ..
        }
        | Constant::InvokeDynamic {
            name_and_type_index,
            ..
        } => expect(pool, index, *name_and_type_index, "NameAndType", nat),
        Constant::MethodHandle {
            reference_kind,
            reference_index,
        } => {
            let legal = match *reference_kind {
                1..=4 => matches!(
                    pool.entry(index, *reference_index)?,
                    Constant::Fieldref { .. }
                ),
                5 | 8 => matches!(
                    pool.entry(index, *reference_index)?,
                    Constant::Methodref { .. }
                ),
                6 | 7 => {
                    matches!(
                        pool.entry(index, *reference_index)?,
                        Constant::Methodref { .. }
                    ) || (major >= 52
                        && matches!(
                            pool.entry(index, *reference_index)?,
                            Constant::InterfaceMethodref { .. }
                        ))
                }
                9 => matches!(
                    pool.entry(index, *reference_index)?,
                    Constant::InterfaceMethodref { .. }
                ),
                _ => {
                    return Err(ConstantPoolError::new(
                        ConstantPoolErrorKind::ReferenceKind,
                        index,
                        Some(*reference_index),
                        format!("invalid method-handle reference kind {reference_kind}"),
                    ));
                }
            };
            if legal {
                Ok(())
            } else {
                Err(ConstantPoolError::new(
                    ConstantPoolErrorKind::WrongCategory,
                    index,
                    Some(*reference_index),
                    format!(
                        "method-handle kind {reference_kind} has illegal target index {reference_index}"
                    ),
                ))
            }
        }
        _ => Ok(()),
    }
}

fn encode_constant(
    writer: &mut ByteWriter,
    index: u16,
    value: &Constant,
) -> Result<(), ConstantPoolError> {
    macro_rules! write {
        ($method:ident($value:expr)) => {
            writer
                .$method($value)
                .map_err(|e| ConstantPoolError::bytes(index, e))?
        };
    }
    let (tag, fields): (u8, &[u16]) = match value {
        Constant::Class { name_index } => (7, core::slice::from_ref(name_index)),
        Constant::String { string_index } => (8, core::slice::from_ref(string_index)),
        Constant::MethodType { descriptor_index } => (16, core::slice::from_ref(descriptor_index)),
        Constant::Module { name_index } => (19, core::slice::from_ref(name_index)),
        Constant::Package { name_index } => (20, core::slice::from_ref(name_index)),
        _ => (0, &[]),
    };
    if tag != 0 {
        write!(write_u1(tag));
        for field in fields {
            write!(write_u2(*field));
        }
        return Ok(());
    }
    match value {
        Constant::Utf8(text) => {
            write!(write_u1(1));
            let bytes = encode_modified_utf8(text, usize::from(u16::MAX))
                .map_err(|e| ConstantPoolError::bytes(index, e))?;
            let length = u16::try_from(bytes.len()).map_err(|_| {
                ConstantPoolError::new(
                    ConstantPoolErrorKind::InvalidLayout,
                    index,
                    None,
                    "Utf8 constant exceeds u16 length",
                )
            })?;
            write!(write_u2(length));
            writer
                .write_bytes(&bytes)
                .map_err(|e| ConstantPoolError::bytes(index, e))?;
        }
        Constant::Integer(bits) => {
            write!(write_u1(3));
            write!(write_u4(*bits));
        }
        Constant::Float(bits) => {
            write!(write_u1(4));
            write!(write_u4(*bits));
        }
        Constant::Long(bits) => {
            write!(write_u1(5));
            write!(write_u4((*bits >> 32) as u32));
            write!(write_u4(*bits as u32));
        }
        Constant::Double(bits) => {
            write!(write_u1(6));
            write!(write_u4((*bits >> 32) as u32));
            write!(write_u4(*bits as u32));
        }
        Constant::Fieldref {
            class_index,
            name_and_type_index,
        } => {
            write!(write_u1(9));
            write!(write_u2(*class_index));
            write!(write_u2(*name_and_type_index));
        }
        Constant::Methodref {
            class_index,
            name_and_type_index,
        } => {
            write!(write_u1(10));
            write!(write_u2(*class_index));
            write!(write_u2(*name_and_type_index));
        }
        Constant::InterfaceMethodref {
            class_index,
            name_and_type_index,
        } => {
            write!(write_u1(11));
            write!(write_u2(*class_index));
            write!(write_u2(*name_and_type_index));
        }
        Constant::NameAndType {
            name_index,
            descriptor_index,
        } => {
            write!(write_u1(12));
            write!(write_u2(*name_index));
            write!(write_u2(*descriptor_index));
        }
        Constant::MethodHandle {
            reference_kind,
            reference_index,
        } => {
            write!(write_u1(15));
            write!(write_u1(*reference_kind));
            write!(write_u2(*reference_index));
        }
        Constant::Dynamic {
            bootstrap_method_attr_index,
            name_and_type_index,
        } => {
            write!(write_u1(17));
            write!(write_u2(*bootstrap_method_attr_index));
            write!(write_u2(*name_and_type_index));
        }
        Constant::InvokeDynamic {
            bootstrap_method_attr_index,
            name_and_type_index,
        } => {
            write!(write_u1(18));
            write!(write_u2(*bootstrap_method_attr_index));
            write!(write_u2(*name_and_type_index));
        }
        Constant::Class { .. }
        | Constant::String { .. }
        | Constant::MethodType { .. }
        | Constant::Module { .. }
        | Constant::Package { .. } => unreachable!(),
    }
    Ok(())
}