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,187 @@
//! Метки тела метода: где обязаны стоять и как резолвятся.
//!
//! Backend нумерует инструкции по порядку — метки ставятся прямо на номера,
//! без байтовых смещений.
use std::collections::{BTreeMap, BTreeSet};
use ristretto_classfile::attributes::Attribute as RtAttr;
use ristretto_classfile::attributes::{ExceptionTableEntry, Instruction};
use crate::error::ClassfileError;
use crate::insn::Label;
/// Номера инструкций, на которых обязана стоять метка.
///
/// `extra` — позиции отладочных таблиц (уже номерами): вставки пассов
/// не должны оставлять их без меток.
pub(super) fn label_indexes(
code: &[Instruction],
exception_table: &[ExceptionTableEntry],
extra: &BTreeSet<u32>,
) -> Result<BTreeSet<u32>, ClassfileError> {
let mut indexes = BTreeSet::from([0u32]);
for (position, instruction) in code.iter().enumerate() {
let index = to_index(position)?;
for target in branch_indexes(instruction, index) {
indexes.insert(to_index_checked(target)?);
}
}
for entry in exception_table {
indexes.insert(u32::from(entry.range_pc.start));
indexes.insert(u32::from(entry.range_pc.end));
indexes.insert(u32::from(entry.handler_pc));
}
indexes.extend(extra.iter().copied());
Ok(indexes)
}
/// Цели ветвлений backend'а: для `if*`/`goto` — абсолютный номер, для `switch` — дельта.
fn branch_indexes(instruction: &Instruction, index: u16) -> Vec<i32> {
let base = i32::from(index);
match instruction {
Instruction::Tableswitch(table) => once(base + table.default)
.chain(table.offsets.iter().map(|target| base + target))
.collect(),
Instruction::Lookupswitch(lookup) => once(base + lookup.default)
.chain(lookup.pairs.values().map(|target| base + target))
.collect(),
Instruction::Ifeq(target)
| Instruction::Ifne(target)
| Instruction::Iflt(target)
| Instruction::Ifge(target)
| Instruction::Ifgt(target)
| Instruction::Ifle(target)
| Instruction::If_icmpeq(target)
| Instruction::If_icmpne(target)
| Instruction::If_icmplt(target)
| Instruction::If_icmpge(target)
| Instruction::If_icmpgt(target)
| Instruction::If_icmple(target)
| Instruction::If_acmpeq(target)
| Instruction::If_acmpne(target)
| Instruction::Ifnull(target)
| Instruction::Ifnonnull(target)
| Instruction::Goto(target)
| Instruction::Jsr(target) => vec![i32::from(*target)],
Instruction::Goto_w(target) | Instruction::Jsr_w(target) => vec![*target],
_ => Vec::new(),
}
}
fn once(value: i32) -> std::iter::Once<i32> {
std::iter::once(value)
}
/// Превратить отсортированный набор номеров в последовательные метки.
pub(super) fn assign(indexes: &BTreeSet<u32>) -> BTreeMap<u32, Label> {
indexes
.iter()
.enumerate()
.map(|(position, index)| (*index, position as u32))
.collect()
}
/// Метка для номера инструкции.
pub(super) fn lookup(labels: &BTreeMap<u32, Label>, index: u16) -> Result<Label, ClassfileError> {
labels
.get(&u32::from(index))
.copied()
.ok_or_else(|| ClassfileError::Parse(format!("нет метки для инструкции {index}")))
}
/// Позиции отладочных таблиц, которым нужны метки.
pub(super) fn collect_debug_labels(
attributes: &[RtAttr],
starts: &[u16],
code_length: u16,
extra: &mut BTreeSet<u32>,
) -> Result<(), ClassfileError> {
for attr in attributes {
match attr {
RtAttr::LineNumberTable { line_numbers, .. } => {
for line in line_numbers {
extra.insert(u32::from(line.start_pc));
}
}
RtAttr::LocalVariableTable { variables, .. } => {
for var in variables {
extra.insert(byte_to_index(starts, var.start_pc)?);
extra.insert(end_index(starts, var.start_pc, var.length, code_length)?);
}
}
RtAttr::LocalVariableTypeTable { variable_types, .. } => {
for var in variable_types {
extra.insert(byte_to_index(starts, var.start_pc)?);
extra.insert(end_index(starts, var.start_pc, var.length, code_length)?);
}
}
_ => {}
}
}
Ok(())
}
/// Байт → номер инструкции (только границы инструкций).
pub(super) fn byte_to_index(starts: &[u16], byte: u16) -> Result<u32, ClassfileError> {
starts
.iter()
.position(|&start| start == byte)
.and_then(|position| u32::try_from(position).ok())
.ok_or_else(|| ClassfileError::Parse(format!("смещение {byte} мимо инструкции")))
}
/// Конец диапазона `[start, start+length)`: индекс за концом.
fn end_index(
starts: &[u16],
start: u16,
length: u16,
code_length: u16,
) -> Result<u32, ClassfileError> {
let end = start
.checked_add(length)
.ok_or_else(|| ClassfileError::Parse("диапазон вне метода".into()))?;
if end == code_length {
return u32::try_from(starts.len())
.map_err(|_| ClassfileError::Parse("в теле метода больше 65535 инструкций".into()));
}
byte_to_index(starts, end)
}
/// Метка для байтового смещения отладочной таблицы.
pub(super) fn resolve_byte(
labels: &BTreeMap<u32, Label>,
starts: &[u16],
byte: u16,
) -> Result<Label, ClassfileError> {
let index = byte_to_index(starts, byte)?;
labels
.get(&index)
.copied()
.ok_or_else(|| ClassfileError::Parse(format!("нет метки для смещения {byte}")))
}
/// Метка конца диапазона отладочной таблицы.
pub(super) fn resolve_end(
labels: &BTreeMap<u32, Label>,
starts: &[u16],
start: u16,
length: u16,
code_length: u16,
) -> Result<Label, ClassfileError> {
let index = end_index(starts, start, length, code_length)?;
labels
.get(&index)
.copied()
.ok_or_else(|| ClassfileError::Parse(format!("нет метки конца {index}")))
}
fn to_index(position: usize) -> Result<u16, ClassfileError> {
u16::try_from(position)
.map_err(|_| ClassfileError::Parse("в теле метода больше 65535 инструкций".into()))
}
fn to_index_checked(index: i32) -> Result<u32, ClassfileError> {
u32::try_from(index)
.map_err(|_| ClassfileError::Parse(format!("номер инструкции {index} вне тела метода")))
}