1use core::fmt;
4use std::collections::BTreeMap;
5
6use crate::{Constant, ConstantPool, Opcode};
7
8#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
10pub struct InstructionId(pub u32);
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum InstructionOperand {
15 Immediate(i32),
17 Local(u16),
19 Constant(u16),
21 Branch(i32),
23 TableLow(i32),
25 TableHigh(i32),
27 LookupKey(i32),
29 Count(u8),
31 Dimensions(u8),
33 ArrayType(u8),
35}
36
37#[derive(Clone, Debug, Eq, PartialEq)]
39pub struct Instruction {
40 pub opcode: Opcode,
42 pub wide: bool,
44 pub operands: Vec<InstructionOperand>,
46}
47
48#[derive(Clone, Debug, Eq, PartialEq)]
50pub struct LocatedInstruction {
51 pub id: InstructionId,
53 pub offset: u32,
55 pub instruction: Instruction,
57}
58
59#[derive(Clone, Debug, Eq, PartialEq)]
61pub struct DecodedCode {
62 pub instructions: Vec<LocatedInstruction>,
64 pub offsets: BTreeMap<u32, InstructionId>,
66}
67
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub struct ExceptionHandlerRange {
71 pub start: u16,
73 pub end: u16,
75 pub handler: u16,
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq)]
81pub enum InstructionErrorKind {
82 Truncated,
84 InvalidOpcode,
86 Version,
88 ReservedByte,
90 ConstantPool,
92 IllegalWide,
94 VariableLayout,
96 InvalidTarget,
98 InvalidHandler,
100 WidthOverflow,
102 InvalidOperands,
104 Manifest,
106}
107
108#[derive(Clone, Debug, Eq, PartialEq)]
110pub struct InstructionError {
111 pub kind: InstructionErrorKind,
113 pub offset: u32,
115 pub message: String,
117}
118
119impl fmt::Display for InstructionError {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 write!(f, "{} at code offset {}", self.message, self.offset)
122 }
123}
124
125impl std::error::Error for InstructionError {}
126
127pub fn decode_instructions(
129 code: &[u8],
130 major_version: u16,
131 pool: &ConstantPool,
132) -> Result<DecodedCode, InstructionError> {
133 let mut cursor = 0usize;
134 let mut instructions = Vec::new();
135 let mut offsets = BTreeMap::new();
136 while cursor < code.len() {
137 let start = cursor;
138 let first = read_u1(code, &mut cursor, start)?;
139 let mut opcode = Opcode::from_byte(first);
140 let mut metadata = opcode.metadata();
141 check_metadata(metadata, major_version, start)?;
142 let wide = metadata.operands.starts_with("modified_opcode:");
143 if wide {
144 opcode = Opcode::from_byte(read_u1(code, &mut cursor, start)?);
145 metadata = opcode.metadata();
146 check_metadata(metadata, major_version, start)?;
147 if metadata.operands != "local:u1" && metadata.operands != "local:u1,increment:s1" {
148 return Err(error(
149 InstructionErrorKind::IllegalWide,
150 start,
151 format!("wide cannot modify {}", metadata.mnemonic),
152 ));
153 }
154 }
155 let operands = if opcode == Opcode::Tableswitch {
156 decode_table_switch(code, &mut cursor, start)?
157 } else if opcode == Opcode::Lookupswitch {
158 decode_lookup_switch(code, &mut cursor, start)?
159 } else {
160 if metadata.width == "variable" && !wide {
161 return Err(error(
162 InstructionErrorKind::VariableLayout,
163 start,
164 format!("unsupported variable layout for {}", metadata.mnemonic),
165 ));
166 }
167 let mut operands = Vec::new();
168 for field in metadata.operands.split(',') {
169 match field {
170 "none" => {}
171 "value:s1" | "increment:s1" if !wide => operands.push(
172 InstructionOperand::Immediate(i32::from(
173 read_u1(code, &mut cursor, start)? as i8,
174 )),
175 ),
176 "increment:s1" => operands.push(InstructionOperand::Immediate(i32::from(
177 read_u2(code, &mut cursor, start)? as i16,
178 ))),
179 "value:s2" | "increment:s2" => operands.push(InstructionOperand::Immediate(
180 i32::from(read_u2(code, &mut cursor, start)? as i16),
181 )),
182 "local:u1" if !wide => operands.push(InstructionOperand::Local(u16::from(
183 read_u1(code, &mut cursor, start)?,
184 ))),
185 "local:u1" => operands.push(InstructionOperand::Local(read_u2(
186 code,
187 &mut cursor,
188 start,
189 )?)),
190 "constant_pool:u1" => operands.push(InstructionOperand::Constant(u16::from(
191 read_u1(code, &mut cursor, start)?,
192 ))),
193 "constant_pool:u2" => operands.push(InstructionOperand::Constant(read_u2(
194 code,
195 &mut cursor,
196 start,
197 )?)),
198 "branch:s2" => operands.push(InstructionOperand::Branch(i32::from(read_u2(
199 code,
200 &mut cursor,
201 start,
202 )?
203 as i16))),
204 "branch:s4" => {
205 operands.push(InstructionOperand::Branch(
206 read_u4(code, &mut cursor, start)? as i32,
207 ))
208 }
209 "count:u1" => operands.push(InstructionOperand::Count(read_u1(
210 code,
211 &mut cursor,
212 start,
213 )?)),
214 "dimensions:u1" => operands.push(InstructionOperand::Dimensions(read_u1(
215 code,
216 &mut cursor,
217 start,
218 )?)),
219 "atype:u1" => operands.push(InstructionOperand::ArrayType(read_u1(
220 code,
221 &mut cursor,
222 start,
223 )?)),
224 "zero:u1" => check_zero(read_u1(code, &mut cursor, start)?, start)?,
225 "zero:u2" => check_zero(read_u2(code, &mut cursor, start)?, start)?,
226 other => {
227 return Err(error(
228 InstructionErrorKind::Manifest,
229 start,
230 format!("unsupported manifest operand {other}"),
231 ));
232 }
233 }
234 }
235 operands
236 };
237 if let Some(InstructionOperand::Constant(index)) = operands
238 .iter()
239 .find(|v| matches!(v, InstructionOperand::Constant(_)))
240 {
241 validate_constant(pool, *index, metadata.constant_pool, start)?;
242 }
243 let id = InstructionId(u32::try_from(instructions.len()).map_err(|_| {
244 error(
245 InstructionErrorKind::Manifest,
246 start,
247 "too many instructions",
248 )
249 })?);
250 let offset = u32::try_from(start).map_err(|_| {
251 error(
252 InstructionErrorKind::Manifest,
253 start,
254 "code offset exceeds u32",
255 )
256 })?;
257 offsets.insert(offset, id);
258 instructions.push(LocatedInstruction {
259 id,
260 offset,
261 instruction: Instruction {
262 opcode,
263 wide,
264 operands,
265 },
266 });
267 }
268 let decoded = DecodedCode {
269 instructions,
270 offsets,
271 };
272 validate_branch_targets(&decoded, code.len())?;
273 Ok(decoded)
274}
275
276pub fn validate_exception_handlers(
278 decoded: &DecodedCode,
279 code_length: usize,
280 handlers: &[ExceptionHandlerRange],
281) -> Result<(), InstructionError> {
282 for range in handlers {
283 let start = usize::from(range.start);
284 let end = usize::from(range.end);
285 let handler = usize::from(range.handler);
286 if start >= end {
287 return Err(error(
288 InstructionErrorKind::InvalidHandler,
289 start,
290 format!("exception handler range {start}..{end} is empty or reversed"),
291 ));
292 }
293 require_boundary(decoded, start, code_length, false, "exception range start")?;
294 require_boundary(decoded, end, code_length, true, "exception range end")?;
295 require_boundary(decoded, handler, code_length, false, "exception handler")?;
296 }
297 Ok(())
298}
299
300fn decode_table_switch(
301 code: &[u8],
302 cursor: &mut usize,
303 start: usize,
304) -> Result<Vec<InstructionOperand>, InstructionError> {
305 read_padding(code, cursor, start)?;
306 let default = read_i4(code, cursor, start)?;
307 let low = read_i4(code, cursor, start)?;
308 let high = read_i4(code, cursor, start)?;
309 if high < low {
310 return Err(error(
311 InstructionErrorKind::VariableLayout,
312 start,
313 format!("tableswitch high key {high} precedes low key {low}"),
314 ));
315 }
316 let count = i64::from(high) - i64::from(low) + 1;
317 let count = usize::try_from(count).map_err(|_| {
318 error(
319 InstructionErrorKind::VariableLayout,
320 start,
321 "tableswitch key range overflows addressable input",
322 )
323 })?;
324 ensure_entries_fit(code, *cursor, count, 4, start, "tableswitch")?;
325 let mut operands = Vec::with_capacity(count.saturating_add(3));
326 operands.push(InstructionOperand::Branch(default));
327 operands.push(InstructionOperand::TableLow(low));
328 operands.push(InstructionOperand::TableHigh(high));
329 for _ in 0..count {
330 operands.push(InstructionOperand::Branch(read_i4(code, cursor, start)?));
331 }
332 Ok(operands)
333}
334
335fn decode_lookup_switch(
336 code: &[u8],
337 cursor: &mut usize,
338 start: usize,
339) -> Result<Vec<InstructionOperand>, InstructionError> {
340 read_padding(code, cursor, start)?;
341 let default = read_i4(code, cursor, start)?;
342 let pairs_origin = *cursor;
343 let pair_count = read_i4(code, cursor, start)?;
344 let pair_count = usize::try_from(pair_count).map_err(|_| {
345 error(
346 InstructionErrorKind::VariableLayout,
347 pairs_origin,
348 format!("lookupswitch pair count {pair_count} is negative"),
349 )
350 })?;
351 ensure_entries_fit(code, *cursor, pair_count, 8, start, "lookupswitch")?;
352 let mut operands = Vec::with_capacity(pair_count.saturating_mul(2).saturating_add(1));
353 operands.push(InstructionOperand::Branch(default));
354 let mut previous = None;
355 for _ in 0..pair_count {
356 let key_origin = *cursor;
357 let key = read_i4(code, cursor, start)?;
358 if let Some(prior) = previous
359 && key <= prior
360 {
361 let relation = if key == prior {
362 "duplicate"
363 } else {
364 "out-of-order"
365 };
366 return Err(error(
367 InstructionErrorKind::VariableLayout,
368 key_origin,
369 format!("lookupswitch {relation} key {key} follows {prior}"),
370 ));
371 }
372 let displacement = read_i4(code, cursor, start)?;
373 operands.push(InstructionOperand::LookupKey(key));
374 operands.push(InstructionOperand::Branch(displacement));
375 previous = Some(key);
376 }
377 Ok(operands)
378}
379
380fn read_padding(code: &[u8], cursor: &mut usize, start: usize) -> Result<(), InstructionError> {
381 let padding = (4 - (*cursor % 4)) % 4;
382 for _ in 0..padding {
383 let origin = *cursor;
384 if read_u1(code, cursor, start)? != 0 {
385 return Err(error(
386 InstructionErrorKind::ReservedByte,
387 origin,
388 "switch alignment padding must be zero",
389 ));
390 }
391 }
392 Ok(())
393}
394
395fn ensure_entries_fit(
396 code: &[u8],
397 cursor: usize,
398 count: usize,
399 width: usize,
400 start: usize,
401 layout: &str,
402) -> Result<(), InstructionError> {
403 let bytes = count.checked_mul(width).ok_or_else(|| {
404 error(
405 InstructionErrorKind::VariableLayout,
406 start,
407 format!("{layout} entry byte count overflows"),
408 )
409 })?;
410 let end = cursor.checked_add(bytes).ok_or_else(|| {
411 error(
412 InstructionErrorKind::VariableLayout,
413 start,
414 format!("{layout} end offset overflows"),
415 )
416 })?;
417 if end > code.len() {
418 return Err(error(
419 InstructionErrorKind::Truncated,
420 start,
421 format!("truncated {layout}"),
422 ));
423 }
424 Ok(())
425}
426
427fn validate_branch_targets(
428 decoded: &DecodedCode,
429 code_length: usize,
430) -> Result<(), InstructionError> {
431 for located in &decoded.instructions {
432 for operand in &located.instruction.operands {
433 if let InstructionOperand::Branch(displacement) = operand {
434 let target = i64::from(located.offset) + i64::from(*displacement);
435 let target = usize::try_from(target).map_err(|_| {
436 error(
437 InstructionErrorKind::InvalidTarget,
438 located.offset as usize,
439 format!("branch target {target} is outside the code array"),
440 )
441 })?;
442 require_boundary(decoded, target, code_length, false, "branch target")?;
443 }
444 }
445 }
446 Ok(())
447}
448
449fn require_boundary(
450 decoded: &DecodedCode,
451 offset: usize,
452 code_length: usize,
453 allow_end: bool,
454 subject: &str,
455) -> Result<(), InstructionError> {
456 let offset_u32 = u32::try_from(offset).map_err(|_| {
457 error(
458 InstructionErrorKind::InvalidTarget,
459 offset,
460 format!("{subject} {offset} exceeds the classfile offset range"),
461 )
462 })?;
463 if (allow_end && offset == code_length) || decoded.offsets.contains_key(&offset_u32) {
464 return Ok(());
465 }
466 if offset >= code_length {
467 return Err(error(
468 if subject.starts_with("exception") {
469 InstructionErrorKind::InvalidHandler
470 } else {
471 InstructionErrorKind::InvalidTarget
472 },
473 offset,
474 format!("{subject} {offset} is outside the code array"),
475 ));
476 }
477 Err(error(
478 if subject.starts_with("exception") {
479 InstructionErrorKind::InvalidHandler
480 } else {
481 InstructionErrorKind::InvalidTarget
482 },
483 offset,
484 format!("{subject} {offset} is not an instruction boundary"),
485 ))
486}
487
488pub(crate) fn check_metadata(
489 metadata: &crate::OpcodeMetadata,
490 major: u16,
491 offset: usize,
492) -> Result<(), InstructionError> {
493 if metadata.width == "invalid" || metadata.operands == "invalid" {
494 return Err(error(
495 InstructionErrorKind::InvalidOpcode,
496 offset,
497 format!("opcode {} is reserved", metadata.mnemonic),
498 ));
499 }
500 let since = metadata
501 .since
502 .split('.')
503 .next()
504 .and_then(|v| v.parse::<u16>().ok())
505 .ok_or_else(|| {
506 error(
507 InstructionErrorKind::Manifest,
508 offset,
509 "invalid since version",
510 )
511 })?;
512 if major < since {
513 return Err(error(
514 InstructionErrorKind::Version,
515 offset,
516 format!(
517 "opcode {} requires classfile version {}, found {}",
518 metadata.mnemonic, metadata.since, major
519 ),
520 ));
521 }
522 if metadata.until != "unbounded" {
523 let until = metadata
524 .until
525 .split('.')
526 .next()
527 .and_then(|v| v.parse::<u16>().ok())
528 .ok_or_else(|| {
529 error(
530 InstructionErrorKind::Manifest,
531 offset,
532 "invalid until version",
533 )
534 })?;
535 if major > until {
536 return Err(error(
537 InstructionErrorKind::Version,
538 offset,
539 format!(
540 "opcode {} ended with classfile version {}, found {}",
541 metadata.mnemonic, metadata.until, major
542 ),
543 ));
544 }
545 }
546 Ok(())
547}
548
549fn validate_constant(
550 pool: &ConstantPool,
551 index: u16,
552 category: &str,
553 offset: usize,
554) -> Result<(), InstructionError> {
555 let value = pool.entry(index, index).map_err(|cause| {
556 error(
557 InstructionErrorKind::ConstantPool,
558 offset,
559 cause.to_string(),
560 )
561 })?;
562 let valid = match category {
563 "Fieldref" => matches!(value, Constant::Fieldref { .. }),
564 "Methodref" => matches!(value, Constant::Methodref { .. }),
565 "InterfaceMethodref" => matches!(value, Constant::InterfaceMethodref { .. }),
566 "Methodref|InterfaceMethodref" => matches!(
567 value,
568 Constant::Methodref { .. } | Constant::InterfaceMethodref { .. }
569 ),
570 "InvokeDynamic" => matches!(value, Constant::InvokeDynamic { .. }),
571 "Class" => matches!(value, Constant::Class { .. }),
572 "loadable-category-1" => !matches!(value, Constant::Long(_) | Constant::Double(_)),
573 "loadable-category-2" => matches!(value, Constant::Long(_) | Constant::Double(_)),
574 "none" => false,
575 _ => false,
576 };
577 if valid {
578 Ok(())
579 } else {
580 Err(error(
581 InstructionErrorKind::ConstantPool,
582 offset,
583 format!("constant pool index {index} is not {category}"),
584 ))
585 }
586}
587
588fn read_u1(code: &[u8], cursor: &mut usize, start: usize) -> Result<u8, InstructionError> {
589 let value = code.get(*cursor).copied().ok_or_else(|| {
590 error(
591 InstructionErrorKind::Truncated,
592 start,
593 "truncated instruction",
594 )
595 })?;
596 *cursor += 1;
597 Ok(value)
598}
599fn read_u2(code: &[u8], cursor: &mut usize, start: usize) -> Result<u16, InstructionError> {
600 Ok(u16::from_be_bytes([
601 read_u1(code, cursor, start)?,
602 read_u1(code, cursor, start)?,
603 ]))
604}
605fn read_u4(code: &[u8], cursor: &mut usize, start: usize) -> Result<u32, InstructionError> {
606 Ok(u32::from_be_bytes([
607 read_u1(code, cursor, start)?,
608 read_u1(code, cursor, start)?,
609 read_u1(code, cursor, start)?,
610 read_u1(code, cursor, start)?,
611 ]))
612}
613fn read_i4(code: &[u8], cursor: &mut usize, start: usize) -> Result<i32, InstructionError> {
614 Ok(read_u4(code, cursor, start)? as i32)
615}
616fn check_zero<T: Default + PartialEq>(value: T, start: usize) -> Result<(), InstructionError> {
617 if value == T::default() {
618 Ok(())
619 } else {
620 Err(error(
621 InstructionErrorKind::ReservedByte,
622 start,
623 "reserved operand bytes must be zero",
624 ))
625 }
626}
627pub(crate) fn error(
628 kind: InstructionErrorKind,
629 offset: usize,
630 message: impl Into<String>,
631) -> InstructionError {
632 InstructionError {
633 kind,
634 offset: u32::try_from(offset).unwrap_or(u32::MAX),
635 message: message.into(),
636 }
637}