Skip to content

Commit ea0879b

Browse files
romtsnclaude
andcommitted
fix: Cap span expansion at u16::MAX (65535) line range
Prevents excessive iteration from malformed mapping files where the original line range could be extremely large (up to u32::MAX). The JVM bytecode maximum line number is 65535, so any span beyond that is invalid and falls through to single-line handling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b4acde2 commit ea0879b

File tree

2 files changed

+21
-1
lines changed

2 files changed

+21
-1
lines changed

src/cache/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ use crate::mapper::{format_cause, format_frames, format_throwable};
8383
use crate::utils::{class_name_to_descriptor, extract_class_name, synthesize_source_file};
8484
use crate::{java, stacktrace, DeobfuscatedSignature, StackFrame, StackTrace, Throwable};
8585

86+
/// Maximum number of frames emitted by span expansion for a single mapping entry.
87+
///
88+
/// R8 uses `0:65535` as the catch-all range for methods with a single unique position:
89+
/// <https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md#catch-all-range-for-methods-with-a-single-unique-position>
90+
///
91+
/// No real method would span more lines than this, so ranges exceeding this cap
92+
/// are treated as malformed and fall through to single-line handling.
93+
const MAX_SPAN_EXPANSION: u32 = 65_535;
94+
8695
pub use raw::{ProguardCache, PRGCACHE_VERSION};
8796

8897
/// Result of looking up member mappings for a frame.
@@ -1005,6 +1014,8 @@ fn iterate_with_lines<'a>(
10051014
// emit one frame per original line.
10061015
if member.original_endline != u32::MAX
10071016
&& member.original_endline > member.original_startline().unwrap_or(0)
1017+
&& (member.original_endline - member.original_startline().unwrap_or(0))
1018+
<= MAX_SPAN_EXPANSION
10081019
{
10091020
let first_line = member.original_startline().unwrap_or(0) as usize;
10101021
let last_line = member.original_endline as usize;

src/mapper.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ use std::fmt;
44
use std::fmt::{Error as FmtError, Write};
55
use std::iter::FusedIterator;
66

7+
/// Maximum number of frames emitted by span expansion for a single mapping entry.
8+
///
9+
/// R8 uses `0:65535` as the catch-all range for methods with a single unique position:
10+
/// <https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md#catch-all-range-for-methods-with-a-single-unique-position>
11+
///
12+
/// No real method would span more lines than this, so ranges exceeding this cap
13+
/// are treated as malformed and fall through to single-line handling.
14+
const MAX_SPAN_EXPANSION: usize = 65_535;
15+
716
use crate::builder::{
817
Member, MethodReceiver, ParsedProguardMapping, RewriteAction, RewriteCondition, RewriteRule,
918
};
@@ -701,7 +710,7 @@ impl<'s> ProguardMapper<'s> {
701710
// emit one frame per original line.
702711
if let Some(oe) = member.original_endline {
703712
let os = member.original_startline.unwrap_or(0);
704-
if oe > os {
713+
if oe > os && (oe - os) <= MAX_SPAN_EXPANSION {
705714
for line in os..=oe {
706715
collected.frames.push(map_member_without_lines(
707716
&frame,

0 commit comments

Comments
 (0)