Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6f00903
WIP
romtsn Nov 11, 2025
1dd7d25
Merge branch 'master' into rz/feat/rewrite-frames-support
romtsn Dec 1, 2025
c7dab35
Add more complex test
romtsn Dec 1, 2025
085678a
add test fixture file
romtsn Dec 1, 2025
79335e1
Support multiple rewrite rules (OR)
romtsn Dec 3, 2025
96bcffa
Do not destructure COllectedFrames
romtsn Dec 3, 2025
9870077
Improve COllectedFrames handling
romtsn Dec 4, 2025
292f1f5
Remove unnecessary import
romtsn Dec 4, 2025
217bd9a
feat(r8): Support annotation in ProguardCache
romtsn Dec 4, 2025
ad156c8
Simplify conditions
romtsn Dec 9, 2025
bb565b7
Skip line if rewrite rules cleared all frames
romtsn Dec 9, 2025
de58387
Merge branch 'master' into rz/feat/rewrite-frames-support
romtsn Dec 10, 2025
9fabece
resolve merge conflicts
romtsn Dec 10, 2025
5d83211
Merge branch 'rz/feat/rewrite-frames-support' into rz/feat/rewrite-fr…
romtsn Dec 10, 2025
d8979d6
use let-else
romtsn Dec 10, 2025
649dcef
use u32::MAX for unknown variants
romtsn Dec 10, 2025
872af98
Drop unnecessary into_iter
romtsn Dec 10, 2025
bec6bce
Skip line if rewrite rules cleared all frames
romtsn Dec 10, 2025
1892067
Do not add RewritRules with both empty conditions and actions
romtsn Dec 10, 2025
c204f0c
Return early in apply_rewrite_rules
romtsn Dec 10, 2025
34101f6
Add doc for rewrite rules
romtsn Dec 10, 2025
9ab3a72
Commonize 'remap_frame' and use it in remap_stacktrace(typed)
romtsn Dec 12, 2025
00dd48c
Merge branch 'master' into rz/feat/rewrite-frames-support-cache
romtsn Dec 18, 2025
410035e
Apply rewrite rules after outline frames are skipped in Cache
romtsn Dec 18, 2025
fe4b5b6
Keep remap_frame method, introduce new remap_frame_with_context
romtsn Dec 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 79 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,29 @@ pub(crate) struct MethodInfo {
pub(crate) is_outline: bool,
}

/// Supported rewrite frame actions.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum RewriteAction<'s> {
RemoveInnerFrames(usize),
/// Placeholder to retain unsupported action strings for future handling.
Unknown(&'s str),
}

/// Supported rewrite frame conditions.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum RewriteCondition<'s> {
Throws(&'s str),
/// Placeholder to retain unsupported condition strings for future handling.
Unknown(&'s str),
}

/// A rewrite frame rule attached to a method mapping.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct RewriteRule<'s> {
pub(crate) conditions: Vec<RewriteCondition<'s>>,
pub(crate) actions: Vec<RewriteAction<'s>>,
}

/// A member record in a Proguard file.
#[derive(Clone, Debug)]
pub(crate) struct Member<'s> {
Expand All @@ -136,6 +159,51 @@ pub(crate) struct Member<'s> {
pub(crate) original_endline: Option<usize>,
/// Optional outline callsite positions map attached to this member.
pub(crate) outline_callsite_positions: Option<HashMap<usize, usize>>,
/// Optional rewrite rules attached to this member.
pub(crate) rewrite_rules: Vec<RewriteRule<'s>>,
}

fn parse_rewrite_rule<'s>(conditions: &[&'s str], actions: &[&'s str]) -> Option<RewriteRule<'s>> {
if conditions.is_empty() || actions.is_empty() {
return None;
}

let mut parsed_conditions = Vec::with_capacity(conditions.len());
for condition in conditions {
let condition = condition.trim();
if condition.is_empty() {
return None;
}
if let Some(rest) = condition.strip_prefix("throws(") {
let descriptor = rest.strip_suffix(')')?;
if descriptor.is_empty() {
return None;
}
parsed_conditions.push(RewriteCondition::Throws(descriptor));
} else {
parsed_conditions.push(RewriteCondition::Unknown(condition));
}
}

let mut parsed_actions = Vec::with_capacity(actions.len());
for action in actions {
let action = action.trim();
if action.is_empty() {
return None;
}
if let Some(rest) = action.strip_prefix("removeInnerFrames(") {
let count_str = rest.strip_suffix(')')?;
let count = count_str.parse().ok()?;
parsed_actions.push(RewriteAction::RemoveInnerFrames(count));
} else {
parsed_actions.push(RewriteAction::Unknown(action));
}
}

Some(RewriteRule {
conditions: parsed_conditions,
actions: parsed_actions,
})
}

/// A collection of member records for a particular class
Expand Down Expand Up @@ -196,6 +264,7 @@ impl<'s> ParsedProguardMapping<'s> {
// Consume R8 headers attached to this class.
while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() {
match r8_header {
R8Header::RewriteFrame { .. } => {}
R8Header::SourceFile { file_name } => {
current_class.source_file = Some(file_name)
}
Expand Down Expand Up @@ -251,6 +320,7 @@ impl<'s> ParsedProguardMapping<'s> {
.entry((current_class_obfuscated, ObfuscatedName(obfuscated)))
.or_default();

let mut rewrite_rules: Vec<RewriteRule<'s>> = Vec::new();
let method = MethodKey {
// Save the receiver name, keeping track of whether it's the current class
// (i.e. the one to which this member record belongs) or another class.
Expand All @@ -276,6 +346,14 @@ impl<'s> ParsedProguardMapping<'s> {
R8Header::Outline => {
method_info.is_outline = true;
}
R8Header::RewriteFrame {
conditions,
actions,
} => {
if let Some(rule) = parse_rewrite_rule(conditions, actions) {
rewrite_rules.push(rule);
}
}
R8Header::OutlineCallsite {
positions,
outline: _,
Expand All @@ -302,6 +380,7 @@ impl<'s> ParsedProguardMapping<'s> {
original_startline,
original_endline,
outline_callsite_positions,
rewrite_rules,
};

members.all.push(member.clone());
Expand Down
Loading