Initial commit: noxis JVM bytecode obfuscator

Workspace of 9 crates (noxis, noxis-core, noxis-classfile, noxis-analysis,
noxis-passes, noxis-runtime, noxis-jar, noxis-verify, noxis-structure-check).
AGPL-3.0-only license, Rust 2024 edition, unsafe_code denied.
This commit is contained in:
Loki 2026-10-08 22:01:03 +02:00
commit a641e69455
140 changed files with 17197 additions and 0 deletions

View file

@ -0,0 +1,169 @@
//! `Code` backend'а → тело метода нашего IR: метки, исключения, кадры.
//!
//! Backend уже перевёл байтовые смещения в номера инструкций — конвертеры
//! работают прямо с ними: метки ставятся на целях переходов, границах
//! try-блоков и позициях отладочных таблиц; кадры переносятся как есть
//! (индексные дельты). Позиции `LineNumberTable`/`LocalVariableTable` —
//! тоже метки, поэтому вставки пассов их не ломают (байты досчитает writer).
mod frames;
mod labels;
use std::collections::BTreeSet;
use std::io::Cursor;
use ristretto_classfile::ConstantPool;
use ristretto_classfile::attributes::Attribute as RtAttr;
use ristretto_classfile::attributes::{ExceptionTableEntry, Instruction};
use super::attrs;
use super::insns::convert;
use crate::error::ClassfileError;
use crate::insn::Insn;
use crate::model::{
Attribute, Code, ExceptionHandler, LineNumber, LocalVariable, LocalVariableType,
};
use frames::{catch_type, to_frames};
use labels::{assign, collect_debug_labels, label_indexes, lookup, resolve_byte, resolve_end};
/// Тело метода целиком: backend `Code` → наш [`Code`].
pub(super) fn convert_code(
max_stack: u16,
max_locals: u16,
code: &[Instruction],
exception_table: &[ExceptionTableEntry],
attributes: &[RtAttr],
pool: &ConstantPool<'_>,
) -> Result<Code, ClassfileError> {
let (starts, code_length) = layout(code)?;
let mut extra = BTreeSet::new();
collect_debug_labels(attributes, &starts, code_length, &mut extra)?;
let labels = assign(&label_indexes(code, exception_table, &extra)?);
let resolve = |index: u16| lookup(&labels, index);
let mut insns = Vec::with_capacity(code.len() + labels.len());
for (position, instruction) in code.iter().enumerate() {
let index = u16::try_from(position)
.map_err(|_| ClassfileError::Parse("в теле метода больше 65535 инструкций".into()))?;
if let Some(label) = labels.get(&u32::from(index)) {
insns.push(Insn::Mark(*label));
}
insns.push(convert(instruction, index, pool, &resolve)?);
}
let end = u32::try_from(code.len())
.map_err(|_| ClassfileError::Parse("в теле метода больше 65535 инструкций".into()))?;
if let Some(label) = labels.get(&end) {
insns.push(Insn::Mark(*label));
}
let exception_table = exception_table
.iter()
.map(|entry| {
Ok(ExceptionHandler {
start: lookup(&labels, entry.range_pc.start)?,
end: lookup(&labels, entry.range_pc.end)?,
handler: lookup(&labels, entry.handler_pc)?,
catch_type: catch_type(pool, entry.catch_type)?,
})
})
.collect::<Result<_, ClassfileError>>()?;
let mut frames = Vec::new();
let mut rest = Vec::new();
for attr in attributes {
match attr {
RtAttr::StackMapTable { frames: found, .. } => {
if !frames.is_empty() {
return Err(ClassfileError::Parse(
"два StackMapTable в одном Code".into(),
));
}
frames = to_frames(found, pool)?;
}
RtAttr::LineNumberTable { line_numbers, .. } => rest.push(Attribute::LineNumberTable(
line_numbers
.iter()
.map(|line| {
Ok(LineNumber {
start: lookup(&labels, line.start_pc)?,
line: line.line_number,
})
})
.collect::<Result<_, ClassfileError>>()?,
)),
RtAttr::LocalVariableTable { variables, .. } => {
rest.push(Attribute::LocalVariableTable(
variables
.iter()
.map(|var| {
Ok(LocalVariable {
start: resolve_byte(&labels, &starts, var.start_pc)?,
end: resolve_end(
&labels,
&starts,
var.start_pc,
var.length,
code_length,
)?,
name: super::pool::utf8(pool, var.name_index)?,
descriptor: super::pool::utf8(pool, var.descriptor_index)?,
index: var.index,
})
})
.collect::<Result<_, ClassfileError>>()?,
))
}
RtAttr::LocalVariableTypeTable { variable_types, .. } => {
rest.push(Attribute::LocalVariableTypeTable(
variable_types
.iter()
.map(|var| {
Ok(LocalVariableType {
start: resolve_byte(&labels, &starts, var.start_pc)?,
end: resolve_end(
&labels,
&starts,
var.start_pc,
var.length,
code_length,
)?,
name: super::pool::utf8(pool, var.name_index)?,
signature: super::pool::utf8(pool, var.signature_index)?,
index: var.index,
})
})
.collect::<Result<_, ClassfileError>>()?,
));
}
other => rest.push(attrs::convert(other, pool)?),
}
}
Ok(Code {
max_stack,
max_locals,
insns,
exception_table,
frames,
attributes: rest,
})
}
/// Байтовые смещения инструкций и длина тела метода.
fn layout(code: &[Instruction]) -> Result<(Vec<u16>, u16), ClassfileError> {
let mut cursor = Cursor::new(Vec::new());
let mut offsets = Vec::with_capacity(code.len());
for instruction in code {
offsets.push(tell(&cursor)?);
instruction
.to_bytes(&mut cursor)
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
}
Ok((offsets, tell(&cursor)?))
}
fn tell(cursor: &Cursor<Vec<u8>>) -> Result<u16, ClassfileError> {
u16::try_from(cursor.position())
.map_err(|_| ClassfileError::Parse("тело метода длиннее 64 КБ".into()))
}