@@ -620,13 +620,19 @@ impl<'s> ProguardMapper<'s> {
620620 self . prepare_frame_for_mapping ( & frame, & mut carried_outline_pos) ;
621621
622622 let mut collected = self . collect_remapped_frames ( & effective_frame) ;
623+ let had_frames = !collected. frames . is_empty ( ) ;
623624 if next_frame_can_rewrite {
624625 apply_rewrite_rules ( & mut collected, current_exception_descriptor. as_deref ( ) ) ;
625626 }
626627
627628 next_frame_can_rewrite = false ;
628629 current_exception_descriptor = None ;
629630
631+ // If rewrite rules cleared all frames, skip entirely
632+ if had_frames && collected. frames . is_empty ( ) {
633+ continue ;
634+ }
635+
630636 format_frames ( & mut stacktrace, line, collected. frames . into_iter ( ) ) ?;
631637 continue ;
632638 }
@@ -678,11 +684,17 @@ impl<'s> ProguardMapper<'s> {
678684
679685 let effective = self . prepare_frame_for_mapping ( f, & mut carried_outline_pos) ;
680686 let mut collected = self . collect_remapped_frames ( & effective) ;
687+ let had_frames = !collected. frames . is_empty ( ) ;
681688 if next_frame_can_rewrite {
682689 apply_rewrite_rules ( & mut collected, exception_descriptor. as_deref ( ) ) ;
683690 }
684691 next_frame_can_rewrite = false ;
685692
693+ // If rewrite rules cleared all frames, skip entirely
694+ if had_frames && collected. frames . is_empty ( ) {
695+ continue ;
696+ }
697+
686698 if collected. frames . is_empty ( ) {
687699 frames_out. push ( f. clone ( ) ) ;
688700 } else {
@@ -977,4 +989,85 @@ java.lang.RuntimeException: boom
977989
978990 assert_eq ! ( mapper. remap_stacktrace( input) . unwrap( ) , expected) ;
979991 }
992+
993+ #[ test]
994+ fn rewrite_frame_removes_all_frames_skips_line ( ) {
995+ // When rewrite rules remove ALL frames, the line should be skipped entirely
996+ // (not fall back to original obfuscated frame)
997+ let mapping = "\
998+ some.Class -> a:
999+ 4:4:void inlined():10:10 -> call
1000+ 4:4:void outer():20 -> call
1001+ # {\" id\" :\" com.android.tools.r8.rewriteFrame\" ,\" conditions\" :[\" throws(Ljava/lang/NullPointerException;)\" ],\" actions\" :[\" removeInnerFrames(2)\" ]}
1002+ some.Other -> b:
1003+ 5:5:void method():30 -> run
1004+ " ;
1005+ let mapper = ProguardMapper :: from ( mapping) ;
1006+
1007+ let input = "\
1008+ java.lang.NullPointerException: Boom
1009+ at a.call(SourceFile:4)
1010+ at b.run(SourceFile:5)
1011+ " ;
1012+
1013+ // The first frame (a.call) should be completely removed by rewrite rules,
1014+ // not replaced with the original "at a.call(SourceFile:4)"
1015+ let expected = "\
1016+ java.lang.NullPointerException: Boom
1017+ at some.Other.method(SourceFile:30)
1018+ " ;
1019+
1020+ let actual = mapper. remap_stacktrace ( input) . unwrap ( ) ;
1021+ assert_eq ! ( actual, expected) ;
1022+ }
1023+
1024+ #[ test]
1025+ fn rewrite_frame_removes_all_frames_skips_line_typed ( ) {
1026+ // When rewrite rules remove ALL frames, the frame should be skipped entirely
1027+ // (not fall back to original obfuscated frame)
1028+ let mapping = "\
1029+ some.Class -> a:
1030+ 4:4:void inlined():10:10 -> call
1031+ 4:4:void outer():20 -> call
1032+ # {\" id\" :\" com.android.tools.r8.rewriteFrame\" ,\" conditions\" :[\" throws(Ljava/lang/NullPointerException;)\" ],\" actions\" :[\" removeInnerFrames(2)\" ]}
1033+ some.Other -> b:
1034+ 5:5:void method():30 -> run
1035+ " ;
1036+ let mapper = ProguardMapper :: from ( mapping) ;
1037+
1038+ let trace = StackTrace {
1039+ exception : Some ( Throwable {
1040+ class : "java.lang.NullPointerException" ,
1041+ message : Some ( "Boom" ) ,
1042+ } ) ,
1043+ frames : vec ! [
1044+ StackFrame {
1045+ class: "a" ,
1046+ method: "call" ,
1047+ line: 4 ,
1048+ file: Some ( "SourceFile" ) ,
1049+ parameters: None ,
1050+ method_synthesized: false ,
1051+ } ,
1052+ StackFrame {
1053+ class: "b" ,
1054+ method: "run" ,
1055+ line: 5 ,
1056+ file: Some ( "SourceFile" ) ,
1057+ parameters: None ,
1058+ method_synthesized: false ,
1059+ } ,
1060+ ] ,
1061+ cause : None ,
1062+ } ;
1063+
1064+ let remapped = mapper. remap_stacktrace_typed ( & trace) ;
1065+
1066+ // The first frame should be completely removed by rewrite rules,
1067+ // leaving only the second frame
1068+ assert_eq ! ( remapped. frames. len( ) , 1 ) ;
1069+ assert_eq ! ( remapped. frames[ 0 ] . class, "some.Other" ) ;
1070+ assert_eq ! ( remapped. frames[ 0 ] . method, "method" ) ;
1071+ assert_eq ! ( remapped. frames[ 0 ] . line, 30 ) ;
1072+ }
9801073}
0 commit comments