9
9
import org .objectweb .asm .tree .ClassNode ;
10
10
import org .objectweb .asm .tree .InsnList ;
11
11
import org .objectweb .asm .tree .InsnNode ;
12
+ import org .objectweb .asm .tree .JumpInsnNode ;
12
13
import org .objectweb .asm .tree .LabelNode ;
13
14
import org .objectweb .asm .tree .MethodNode ;
14
15
import org .objectweb .asm .tree .MultiANewArrayInsnNode ;
15
16
import org .objectweb .asm .tree .TryCatchBlockNode ;
16
17
import org .objectweb .asm .tree .TypeInsnNode ;
17
18
import org .objectweb .asm .tree .analysis .Frame ;
18
19
import software .coley .recaf .info .JvmClassInfo ;
19
- import software .coley .recaf .services .assembler .ExpressionCompileException ;
20
20
import software .coley .recaf .services .inheritance .InheritanceGraph ;
21
21
import software .coley .recaf .services .inheritance .InheritanceGraphService ;
22
22
import software .coley .recaf .services .inheritance .InheritanceVertex ;
43
43
import java .util .HashMap ;
44
44
import java .util .HashSet ;
45
45
import java .util .IdentityHashMap ;
46
+ import java .util .Iterator ;
46
47
import java .util .List ;
47
48
import java .util .Map ;
49
+ import java .util .Objects ;
48
50
import java .util .OptionalInt ;
49
51
import java .util .Set ;
52
+ import java .util .stream .Collectors ;
50
53
51
54
import static org .objectweb .asm .Opcodes .*;
52
55
@@ -93,14 +96,69 @@ public void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace
93
96
if (method .instructions == null || method .tryCatchBlocks == null || method .tryCatchBlocks .isEmpty ())
94
97
continue ;
95
98
96
- dirty |= pass0PruneNeverThrown (context , workspace , node , method );
97
- dirty |= pass1PruneNeverThrowingOrDuplicate (context , node , method );
98
- dirty |= pass2ConvertOpaqueThrowToDirectFlow ( );
99
+ dirty |= pass0PruneIgnoredHandlers (context , node , method );
100
+ dirty |= pass1PruneNeverThrown (context , workspace , node , method );
101
+ dirty |= pass2PruneNeverThrowingOrDuplicate ( context , node , method );
99
102
}
100
103
if (dirty )
101
104
context .setNode (bundle , initialClassState , node );
102
105
}
103
106
107
+ /**
108
+ * Remove try-catch blocks that cannot possibly be utilized at runtime.
109
+ *
110
+ * @param context
111
+ * Transformer context.
112
+ * @param node
113
+ * Defining class.
114
+ * @param method
115
+ * Method to transform.
116
+ *
117
+ * @return {@code true} when one or more try-catch blocks have been removed.
118
+ *
119
+ * @throws TransformationException
120
+ * Thrown when dead code after transformation could not be pruned.
121
+ */
122
+ private boolean pass0PruneIgnoredHandlers (@ Nonnull JvmTransformerContext context , @ Nonnull ClassNode node , @ Nonnull MethodNode method ) throws TransformationException {
123
+ // Given the following {start, end, handler, ex-type} blocks:
124
+ // { R, S, Q, * },
125
+ // { R, S, C, * },
126
+ // { R, S, S, Ljava/lang/ArrayIndexOutOfBoundsException; }
127
+ // Only the first is going to be used.
128
+ // - It appears first, so it will be checked first by the JVM
129
+ // - Its range covers all possible instructions of the other two try blocks
130
+ // - Its handled type is more generic ("*" is catch-all)
131
+ // See: https://github.com/openjdk/jdk21u/blob/master/src/hotspot/share/oops/method.cpp#L227
132
+ //
133
+ // Process:
134
+ // 1. Collect try-catch handlers keyed by their range
135
+ // 2. Prune handlers of narrower types in the collection
136
+ // 3. Retain only remaining handlers in the collection
137
+ List <TryCatchBlockNode > blocks = new ArrayList <>(method .tryCatchBlocks );
138
+ Map <ThrowingRange , Handlers > handlersMap = new HashMap <>();
139
+ for (TryCatchBlockNode block : blocks ) {
140
+ int start = AsmInsnUtil .indexOf (block .start );
141
+ int end = AsmInsnUtil .indexOf (block .end );
142
+ if (start < end ) {
143
+ ThrowingRange range = new ThrowingRange (start , end );
144
+ handlersMap .computeIfAbsent (range , r -> new Handlers ()).addBlock (block );
145
+ }
146
+ }
147
+ for (Handlers handlers : handlersMap .values ())
148
+ handlers .prune (inheritanceGraph );
149
+ Set <TryCatchBlockNode > allHandlers = handlersMap .values ()
150
+ .stream ()
151
+ .flatMap (handlers -> handlers .blocks .stream ())
152
+ .collect (Collectors .toSet ());
153
+ if (method .tryCatchBlocks .retainAll (allHandlers )) {
154
+ // Removing handlers can mean blocks starting with an expected 'Throwable' on the stack are now invalid.
155
+ // These should be dead code though, so if we prune code that isn't visitable these should go away.
156
+ context .pruneDeadCode (node , method );
157
+ return true ;
158
+ }
159
+ return false ;
160
+ }
161
+
104
162
/**
105
163
* Remove try-catch blocks that have handle exception types that are defined in the workspace
106
164
* but never actually constructed and thrown.
@@ -117,9 +175,10 @@ public void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace
117
175
* @return {@code true} when one or more try-catch blocks have been removed.
118
176
*
119
177
* @throws TransformationException
120
- * Thrown when the {@link ExpressionCompileException} cannot be found in the transformer context.
178
+ * Thrown when the {@link ExceptionCollectionTransformer} cannot be found in the transformer context,
179
+ * or when dead code couldn't be pruned.
121
180
*/
122
- private boolean pass0PruneNeverThrown (@ Nonnull JvmTransformerContext context , @ Nonnull Workspace workspace , @ Nonnull ClassNode node , @ Nonnull MethodNode method ) throws TransformationException {
181
+ private boolean pass1PruneNeverThrown (@ Nonnull JvmTransformerContext context , @ Nonnull Workspace workspace , @ Nonnull ClassNode node , @ Nonnull MethodNode method ) throws TransformationException {
123
182
ExceptionCollectionTransformer exceptions = context .getJvmTransformer (ExceptionCollectionTransformer .class );
124
183
125
184
// Collect which blocks are candidates for removal.
@@ -228,7 +287,7 @@ private boolean pass0PruneNeverThrown(@Nonnull JvmTransformerContext context, @N
228
287
* @throws TransformationException
229
288
* Thrown when code cannot be analyzed <i>(Needed for certain checks)</i>.
230
289
*/
231
- private boolean pass1PruneNeverThrowingOrDuplicate (@ Nonnull JvmTransformerContext context , @ Nonnull ClassNode node , @ Nonnull MethodNode method ) throws TransformationException {
290
+ private boolean pass2PruneNeverThrowingOrDuplicate (@ Nonnull JvmTransformerContext context , @ Nonnull ClassNode node , @ Nonnull MethodNode method ) throws TransformationException {
232
291
InsnList instructions = method .instructions ;
233
292
List <TryCatchBlockNode > tryCatchBlocks = method .tryCatchBlocks ;
234
293
Frame <ReValue >[] frames = context .analyze (inheritanceGraph , node , method );
@@ -635,7 +694,10 @@ && doesHandleException(tryCatch, EX_CCE)) {
635
694
return false ;
636
695
}
637
696
638
- private boolean pass2ConvertOpaqueThrowToDirectFlow () {
697
+ private boolean pass3ConvertOpaqueThrowToDirectFlow (@ Nonnull JvmTransformerContext context , @ Nonnull ClassNode node , @ Nonnull MethodNode method ) {
698
+ // TODO: Rather than make this a separate pass, I think we can work the intended behavior outlined here into the prior pass
699
+ if (true ) return false ;
700
+
639
701
// TODO: Look for try blocks that end in 'throw T' with a 'catch T' handler
640
702
// - Must always take the path
641
703
// - Not always direct, can be '1 / 0' with a 'catch MathError' handler
@@ -644,6 +706,30 @@ private boolean pass2ConvertOpaqueThrowToDirectFlow() {
644
706
// - Worst case, it relies on stack info from the thrown exception
645
707
// (we can keep the 'new T' or replace the throwing '1 / 0' with 'new T')
646
708
// - If found, replace the throwing code with a 'goto handler'
709
+ InsnList instructions = method .instructions ;
710
+ for (TryCatchBlockNode tryCatch : new ArrayList <>(method .tryCatchBlocks )) {
711
+ int start = instructions .indexOf (tryCatch .start );
712
+ int end = instructions .indexOf (tryCatch .end );
713
+
714
+ // TODO: Validate that the block WILL flow into a 'throw' case
715
+ // - Same code as prior pass?
716
+ boolean willThrow = true ;
717
+ Set <AbstractInsnNode > throwingInstructions = Collections .newSetFromMap (new IdentityHashMap <>());
718
+ for (int i = start ; i < end ; i ++) {
719
+ // TODO: Mark willThow false if control flow leads to path where no-100% throwing behavior is observed
720
+ }
721
+
722
+ // If the try range of the block WILL throw, we can replace the offending instructions with jumps to the handler block
723
+ if (willThrow && !throwingInstructions .isEmpty ()) {
724
+ for (AbstractInsnNode thrower : throwingInstructions ) {
725
+ // TODO: If exception is not already on stack top, replace with junk exception (null is not a good replacement)
726
+ instructions .insertBefore (thrower , new InsnNode (ACONST_NULL ));
727
+ instructions .insertBefore (thrower , new JumpInsnNode (GOTO , tryCatch .handler ));
728
+ }
729
+ method .tryCatchBlocks .remove (tryCatch );
730
+ }
731
+ }
732
+
647
733
return false ;
648
734
}
649
735
@@ -739,6 +825,16 @@ boolean canThrow() {
739
825
*/
740
826
private record Block (@ Nullable String type , int start , int end , int handler ) {}
741
827
828
+ /**
829
+ * Range of some code.
830
+ *
831
+ * @param start
832
+ * Start label.
833
+ * @param end
834
+ * End label.
835
+ */
836
+ private record Range (@ Nonnull LabelNode start , @ Nonnull LabelNode end ) {}
837
+
742
838
/**
743
839
* Range of instructions that can possibly throw exceptions.
744
840
*
@@ -753,4 +849,50 @@ public ThrowingRange merge(@Nonnull ThrowingRange other) {
753
849
return new ThrowingRange (Math .min (first , other .first ), Math .max (last , other .last ));
754
850
}
755
851
}
852
+
853
+ /**
854
+ * Collection of try catch blocks.
855
+ *
856
+ * @param blocks
857
+ * Wrapped list of blocks.
858
+ * @param seenTypes
859
+ * Observed types handled by the blocks.
860
+ */
861
+ private record Handlers (@ Nonnull List <TryCatchBlockNode > blocks , @ Nonnull Set <String > seenTypes ) {
862
+ private Handlers () {
863
+ this (new ArrayList <>(), new HashSet <>());
864
+ }
865
+
866
+ /**
867
+ * @param block
868
+ * Block to add.
869
+ */
870
+ public void addBlock (@ Nonnull TryCatchBlockNode block ) {
871
+ blocks .add (block );
872
+ }
873
+
874
+ /**
875
+ * Remove entries from {@link #blocks} that are redundant.
876
+ *
877
+ * @param graph
878
+ * Inheritance graph for classes in the workspace.
879
+ */
880
+ public void prune (@ Nonnull InheritanceGraph graph ) {
881
+ Iterator <TryCatchBlockNode > it = blocks .iterator ();
882
+ while (it .hasNext ()) {
883
+ TryCatchBlockNode block = it .next ();
884
+ String handledType = Objects .requireNonNullElse (block .type , "java/lang/Object" );
885
+ inner :
886
+ {
887
+ for (String seenType : seenTypes ) {
888
+ if (graph .isAssignableFrom (seenType , handledType )) {
889
+ it .remove ();
890
+ break inner ;
891
+ }
892
+ }
893
+ seenTypes .add (handledType );
894
+ }
895
+ }
896
+ }
897
+ }
756
898
}
0 commit comments