• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // ASM: a very small and fast Java bytecode manipulation framework
2 // Copyright (c) 2000-2011 INRIA, France Telecom
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions
7 // are met:
8 // 1. Redistributions of source code must retain the above copyright
9 //    notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 //    notice, this list of conditions and the following disclaimer in the
12 //    documentation and/or other materials provided with the distribution.
13 // 3. Neither the name of the copyright holders nor the names of its
14 //    contributors may be used to endorse or promote products derived from
15 //    this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 // THE POSSIBILITY OF SUCH DAMAGE.
28 package org.objectweb.asm;
29 
30 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
31 import static org.junit.jupiter.api.Assertions.assertEquals;
32 import static org.junit.jupiter.api.Assertions.assertNotNull;
33 
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.StringTokenizer;
39 import java.util.concurrent.atomic.AtomicReference;
40 import org.junit.jupiter.api.Test;
41 import org.junit.jupiter.params.ParameterizedTest;
42 import org.junit.jupiter.params.provider.ValueSource;
43 import org.objectweb.asm.test.ClassFile;
44 
45 /**
46  * ClassWriter unit tests for COMPUTE_MAXS option with JSR instructions.
47  *
48  * @author Eric Bruneton
49  */
50 class ClassWriterComputeMaxsTest {
51 
52   // Some local variable numbers used in tests.
53   private static final int LOCAL1 = 1;
54   private static final int LOCAL2 = 2;
55   private static final int LOCAL3 = 3;
56   private static final int LOCAL4 = 4;
57   private static final int LOCAL5 = 5;
58 
59   // Labels used to generate test cases.
60   private final Label label0 = new Label();
61   private final Label label1 = new Label();
62   private final Label label2 = new Label();
63   private final Label label3 = new Label();
64   private final Label label4 = new Label();
65   private final Label label5 = new Label();
66   private final Label label6 = new Label();
67   private final Label label7 = new Label();
68   private final Label label8 = new Label();
69   private final Label label9 = new Label();
70   private final Label label10 = new Label();
71   private final Label label11 = new Label();
72   private final Label label12 = new Label();
73 
74   /**
75    * Tests a method which has the most basic <code>try{}finally{}</code> form imaginable. That is,
76    * repeated one or more times:
77    *
78    * <pre>
79    * public void a() {
80    *   int a = 0;
81    *   try {
82    *     a++;
83    *   } finally {
84    *     a--;
85    *   }
86    *   // ... same try {} finally {} repeated 0 or more times ...
87    * }
88    * </pre>
89    */
90   @ParameterizedTest
91   @ValueSource(ints = {1, 31, 32, 33})
testVisitMaxs_basic(final int numSubroutines)92   void testVisitMaxs_basic(final int numSubroutines) {
93     TestCaseBuilder testCase = new TestCaseBuilder();
94     for (int i = 0; i < numSubroutines; ++i) {
95       Label k0 = new Label();
96       Label k1 = new Label();
97       Label k2 = new Label();
98       Label k3 = new Label();
99       Label k4 = new Label();
100       testCase
101           .iconst_0() // N0
102           .istore(1)
103           // Body of try block.
104           .label(k0) // N2
105           .iinc(1, 1)
106           .go(k3)
107           // Exception handler.
108           .label(k1) // N8
109           .astore(3)
110           .jsr(k2)
111           .aload(3) // N12
112           .athrow()
113           // Subroutine.
114           .label(k2) // N14
115           .astore(2)
116           .iinc(1, -1)
117           .push()
118           .push()
119           .ret(2)
120           // Non-exceptional exit from try block.
121           .label(k3) // N22
122           .jsr(k2)
123           .push() // N25
124           .push()
125           .label(k4) // N27
126           .vreturn()
127           .trycatch(k0, k1, k1)
128           .trycatch(k3, k4, k1);
129     }
130 
131     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
132     byte[] classFile = testCase.build();
133 
134     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
135     assertEquals(4, methodInfo.maxStack);
136     assertEquals(4, methodInfo.maxLocals);
137     if (numSubroutines == 1) {
138       Map<String, Set<String>> expectedControlFlowGraph =
139           controlFlowGraph(
140               "N0=N2",
141               "N2=N22,N8",
142               "N8=N14",
143               "N12=",
144               "N14=N12,N25",
145               "N22=N14,N8",
146               "N25=N27,N8",
147               "N27=");
148       assertEquals(expectedControlFlowGraph, controlFlowGraph);
149     }
150     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
151   }
152 
153   /**
154    * Tests a method which has an if/else-if w/in the finally clause. More specifically:
155    *
156    * <pre>
157    * public void a() {
158    *   int a = 0;
159    *   try {
160    *       a++;
161    *   } finally {
162    *     if (a == 0) {
163    *       a += 2;
164    *     } else {
165    *       a += 3;
166    *     }
167    *   }
168    * }
169    * </pre>
170    */
171   @Test
testVisitMaxs_ifElseInFinally()172   void testVisitMaxs_ifElseInFinally() {
173     TestCaseBuilder testCase =
174         new TestCaseBuilder()
175             .iconst_0() // N0
176             .istore(1)
177             // Body of try block.
178             .label(label0) // N2
179             .iinc(1, 1)
180             .go(label5)
181             // Exception handler.
182             .label(label1) // N8
183             .astore(3)
184             .jsr(label2)
185             .push() // N12
186             .push()
187             .aload(3)
188             .athrow()
189             // Subroutine.
190             .label(label2) // N16
191             .astore(2)
192             .push()
193             .push()
194             .iload(1)
195             .ifne(label3)
196             .iinc(1, 2)
197             .go(label4)
198             .label(label3) // N29
199             .iinc(1, 3)
200             .label(label4) // N32, common exit.
201             .ret(2)
202             // Non-exceptional exit from try block.
203             .label(label5) // N34
204             .jsr(label2)
205             .label(label6) // N37
206             .vreturn()
207             .trycatch(label0, label1, label1)
208             .trycatch(label5, label6, label1);
209 
210     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
211     byte[] classFile = testCase.build();
212 
213     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
214     assertEquals(5, methodInfo.maxStack);
215     assertEquals(4, methodInfo.maxLocals);
216     Map<String, Set<String>> expectedControlFlowGraph =
217         controlFlowGraph(
218             "N0=N2",
219             "N2=N34,N8",
220             "N8=N16",
221             "N12=",
222             "N16=N29,N32",
223             "N29=N32",
224             "N32=N37,N12",
225             "N34=N16,N8",
226             "N37=");
227     assertEquals(expectedControlFlowGraph, controlFlowGraph);
228     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
229   }
230 
231   /**
232    * Tests a simple nested finally. More specifically:
233    *
234    * <pre>
235    * public void a1() {
236    *   int a = 0;
237    *   try {
238    *     a += 1;
239    *   } finally {
240    *     try {
241    *       a += 2;
242    *     } finally {
243    *       a += 3;
244    *     }
245    *   }
246    * }
247    * </pre>
248    */
249   @Test
testVisitMaxs_simpleNestedFinally()250   void testVisitMaxs_simpleNestedFinally() {
251     TestCaseBuilder testCase =
252         new TestCaseBuilder()
253             .iconst_0() // N0
254             .istore(1)
255             // Body of try block.
256             .label(label0) // N2
257             .iinc(1, 1)
258             .jsr(label2)
259             .go(label5) // N8
260             // First exception handler.
261             .label(label1) // N11
262             .astore(4)
263             .jsr(label2)
264             .aload(4) // N16
265             .athrow()
266             // First subroutine.
267             .label(label2) // N19
268             .astore(2)
269             .iinc(1, 2)
270             .jsr(label4)
271             .push() // N26
272             .push()
273             .ret(2)
274             // Second exception handler.
275             .label(label3) // N30
276             .astore(5)
277             .jsr(label4)
278             .aload(5) // N35
279             .athrow()
280             // Second subroutine.
281             .label(label4) // N38
282             .astore(3)
283             .push()
284             .push()
285             .iinc(1, 3)
286             .ret(3)
287             // On normal exit, try block jumps here.
288             .label(label5) // N46
289             .vreturn()
290             .trycatch(label0, label1, label1)
291             .trycatch(label2, label3, label3);
292 
293     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
294     byte[] classFile = testCase.build();
295 
296     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
297     assertEquals(5, methodInfo.maxStack);
298     assertEquals(6, methodInfo.maxLocals);
299     Map<String, Set<String>> expectedControlFlowGraph =
300         controlFlowGraph(
301             "N0=N2",
302             "N2=N11,N19",
303             "N8=N11,N46",
304             "N11=N19",
305             "N16=",
306             "N19=N30,N38",
307             "N26=N16,N30,N8",
308             "N30=N38",
309             "N35=",
310             "N38=N26,N35",
311             "N46=");
312     assertEquals(expectedControlFlowGraph, controlFlowGraph);
313     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
314   }
315 
316   /**
317    * This tests a subroutine which has no ret statement, but ends in a "return" instead.
318    *
319    * <p>We structure this as a try/finally with a break in the finally. Because the while loop is
320    * infinite, it's clear from the byte code that the only path which reaches the RETURN instruction
321    * is through the subroutine.
322    *
323    * <pre>
324    * public void a1() {
325    *   int a = 0;
326    *   while (true) {
327    *     try {
328    *       a += 1;
329    *     } finally {
330    *       a += 2;
331    *       break;
332    *     }
333    *   }
334    * }
335    * </pre>
336    */
337   @Test
testVisitMaxs_subroutineWithNoRet()338   void testVisitMaxs_subroutineWithNoRet() {
339     TestCaseBuilder testCase =
340         new TestCaseBuilder()
341             .iconst_0() // N0
342             .istore(1)
343             // While loop header/try block.
344             .label(label0) // N2
345             .iinc(1, 1)
346             .jsr(label2)
347             .go(label3) // N8
348             // Implicit catch block.
349             .label(label1) // N11
350             .astore(2)
351             .jsr(label2)
352             .push() // N15
353             .push()
354             .aload(2)
355             .athrow()
356             // Subroutine which does not return.
357             .label(label2) // N19
358             .astore(3)
359             .iinc(1, 2)
360             .go(label4)
361             // End of the loop, goes back to the top.
362             .label(label3) // N26
363             .go(label0)
364             .label(label4) // N29
365             .vreturn()
366             .trycatch(label0, label1, label1);
367 
368     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
369     byte[] classFile = testCase.build();
370 
371     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
372     assertEquals(1, methodInfo.maxStack);
373     assertEquals(4, methodInfo.maxLocals);
374     Map<String, Set<String>> expectedControlFlowGraph =
375         controlFlowGraph(
376             "N0=N2", "N2=N11,N19", "N8=N11,N26", "N11=N19", "N15=", "N19=N29", "N26=N2", "N29=");
377     assertEquals(expectedControlFlowGraph, controlFlowGraph);
378     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
379   }
380 
381   /**
382    * This tests a subroutine which has no ret statement, but ends in a "return" instead.
383    *
384    * <pre>
385    *   aconst_null
386    *   jsr l0
387    * l0:
388    *   astore 0
389    *   astore 0
390    *   return
391    * </pre>
392    */
393   @Test
testVisitMaxs_subroutineWithNoRet2()394   void testVisitMaxs_subroutineWithNoRet2() {
395     TestCaseBuilder testCase =
396         new TestCaseBuilder()
397             .aconst_null() // N0
398             .jsr(label0)
399             .nop() // N4
400             .label(label0) // N5
401             .astore(0)
402             .astore(0)
403             .vreturn()
404             .label(label1) // N8
405             .localVariable("i", "I", null, label0, label1, 1);
406 
407     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
408     byte[] classFile = testCase.build();
409 
410     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
411     assertEquals(2, methodInfo.maxStack);
412     assertEquals(2, methodInfo.maxLocals);
413     Map<String, Set<String>> expectedControlFlowGraph =
414         controlFlowGraph("N0=N5", "N4=N5", "N5=", "N8=");
415     assertEquals(expectedControlFlowGraph, controlFlowGraph);
416     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
417   }
418 
419   /**
420    * This tests a subroutine which has no ret statement, but instead exits implicitly by branching
421    * to code which is not part of the subroutine. (Sadly, this is legal)
422    *
423    * <p>We structure this as a try/finally in a loop with a break in the finally. The loop is not
424    * trivially infinite, so the RETURN statement is reachable both from the JSR subroutine and from
425    * the main entry point.
426    *
427    * <pre>
428    * public void a1() {
429    *   int a = 0;
430    *   while (null == null) {
431    *     try {
432    *       a += 1;
433    *     } finally {
434    *       a += 2;
435    *       break;
436    *     }
437    *   }
438    * }
439    * </pre>
440    */
441   @Test
testVisitMaxs_implicitExit()442   void testVisitMaxs_implicitExit() {
443     TestCaseBuilder testCase =
444         new TestCaseBuilder()
445             .iconst_0() // N0
446             .istore(1)
447             // While loop header.
448             .label(label0) // N2
449             .aconst_null()
450             .ifnonnull(label5)
451             // Try block.
452             .label(label1) // N6
453             .iinc(1, 1)
454             .jsr(label3)
455             .go(label4) // N12
456             // Implicit catch block.
457             .label(label2) // N15
458             .astore(2)
459             .jsr(label3)
460             .aload(2) // N19
461             .push()
462             .push()
463             .athrow()
464             // Subroutine which does not return.
465             .label(label3) // N23
466             .astore(3)
467             .iinc(1, 2)
468             .go(label5)
469             // End of the loop, goes back to the top.
470             .label(label4) // N30
471             .go(label1)
472             .label(label5) // N33
473             .vreturn()
474             .trycatch(label1, label2, label2);
475 
476     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
477     byte[] classFile = testCase.build();
478 
479     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
480     assertEquals(1, methodInfo.maxStack);
481     assertEquals(4, methodInfo.maxLocals);
482     Map<String, Set<String>> expectedControlFlowGraph =
483         controlFlowGraph(
484             "N0=N2",
485             "N2=N6,N33",
486             "N6=N23,N15",
487             "N12=N30,N15",
488             "N15=N23",
489             "N19=",
490             "N23=N33",
491             "N30=N6",
492             "N33=");
493     assertEquals(expectedControlFlowGraph, controlFlowGraph);
494     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
495   }
496 
497   /**
498    * Tests a nested try/finally with implicit exit from one subroutine to the other subroutine.
499    * Equivalent to the following java code:
500    *
501    * <pre>
502    * void m(boolean b) {
503    *   try {
504    *     return;
505    *   } finally {
506    *     while (b) {
507    *       try {
508    *         return;
509    *       } finally {
510    *         // NOTE --- this break avoids the second return above (weird)
511    *         if (b) {
512    *           break;
513    *         }
514    *       }
515    *     }
516    *   }
517    * }
518    * </pre>
519    *
520    * <p>This example is from the paper, "Subroutine Inlining and Bytecode Abstraction to Simplify
521    * Static and Dynamic Analysis" by Cyrille Artho and Armin Biere.
522    */
523   @Test
testVisitMaxs_implicitExitToAnotherSubroutine()524   void testVisitMaxs_implicitExitToAnotherSubroutine() {
525     TestCaseBuilder testCase =
526         new TestCaseBuilder()
527             .iconst_0() // N0
528             .istore(1)
529             // First try.
530             .label(label0) // N2
531             .jsr(label2)
532             .vreturn() // N5
533             // Exception handler for first try.
534             .label(label1) // N6
535             .astore(LOCAL2)
536             .jsr(label2)
537             .push() // N10
538             .push()
539             .aload(LOCAL2)
540             .athrow()
541             // First finally handler.
542             .label(label2) // N14
543             .astore(LOCAL4)
544             .push()
545             .push()
546             .go(label6)
547             // Body of while loop, also second try.
548             .label(label3) // N21
549             .jsr(label5)
550             .vreturn() // N24
551             // Exception handler for second try.
552             .label(label4) // N25
553             .astore(LOCAL3)
554             .push()
555             .push()
556             .jsr(label5)
557             .aload(LOCAL3) // N31
558             .athrow()
559             // Second finally handler.
560             .label(label5) // N33
561             .astore(LOCAL5)
562             .iload(LOCAL1)
563             .ifne(label7)
564             .ret(LOCAL5)
565             // Test for the while loop.
566             .label(label6) // N41
567             .iload(LOCAL1)
568             .ifne(label3) // falls through to X.
569             // Exit from finally block.
570             .label(label7) // N45
571             .ret(LOCAL4)
572             .trycatch(label0, label1, label1)
573             .trycatch(label3, label4, label4);
574 
575     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
576     byte[] classFile = testCase.build();
577 
578     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
579     assertEquals(5, methodInfo.maxStack);
580     assertEquals(6, methodInfo.maxLocals);
581     Map<String, Set<String>> expectedControlFlowGraph =
582         controlFlowGraph(
583             "N0=N2",
584             "N2=N6,N14",
585             "N5=N6",
586             "N6=N14",
587             "N10=",
588             "N14=N41",
589             "N21=N25,N33",
590             "N24=N25",
591             "N25=N33",
592             "N31=",
593             "N33=N31,N45,N24",
594             "N41=N45,N21",
595             "N45=N5,N10");
596     assertEquals(expectedControlFlowGraph, controlFlowGraph);
597     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
598   }
599 
600   @Test
testVisitMaxs_implicitExitToAnotherSubroutine2()601   void testVisitMaxs_implicitExitToAnotherSubroutine2() {
602     TestCaseBuilder testCase =
603         new TestCaseBuilder()
604             .iconst_0() // N0
605             .istore(1)
606             .jsr(label0)
607             .vreturn() // N5
608             .label(label0) // N6
609             .astore(2)
610             .jsr(label1)
611             .go(label2) // N10
612             .label(label1) // N13
613             .astore(3)
614             .iload(1)
615             .ifne(label2)
616             .ret(3)
617             .label(label2) // N20
618             .ret(2);
619 
620     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
621     byte[] classFile = testCase.build();
622 
623     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
624     assertEquals(1, methodInfo.maxStack);
625     assertEquals(4, methodInfo.maxLocals);
626     Map<String, Set<String>> expectedControlFlowGraph =
627         controlFlowGraph("N0=N6", "N5=", "N6=N13", "N10=N20", "N13=N20,N10", "N20=N5");
628     assertEquals(expectedControlFlowGraph, controlFlowGraph);
629     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
630   }
631 
632   /**
633    * This tests a simple subroutine where the control flow jumps back and forth between the
634    * subroutine and the caller.
635    *
636    * <p>This would not normally be produced by a Java compiler.
637    */
638   @Test
testVisitMaxs_interleavedCode()639   void testVisitMaxs_interleavedCode() {
640     TestCaseBuilder testCase =
641         new TestCaseBuilder()
642             .iconst_0() // N0
643             .istore(1)
644             .jsr(label0)
645             .go(label1) // N5
646             // Subroutine 1.
647             .label(label0) // N8
648             .astore(2)
649             .iinc(1, 1)
650             .go(label2)
651             // Second part of main subroutine.
652             .label(label1) // N15
653             .iinc(1, 2)
654             .go(label3)
655             // Second part of subroutine 1.
656             .label(label2) // N21
657             .iinc(1, 4)
658             .push()
659             .push()
660             .ret(2)
661             // Third part of main subroutine.
662             .label(label3) // N28
663             .push()
664             .push()
665             .vreturn();
666 
667     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
668     byte[] classFile = testCase.build();
669 
670     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
671     assertEquals(4, methodInfo.maxStack);
672     assertEquals(3, methodInfo.maxLocals);
673     Map<String, Set<String>> expectedControlFlowGraph =
674         controlFlowGraph("N0=N8", "N5=N15", "N8=N21", "N15=N28", "N21=N5", "N28=");
675     assertEquals(expectedControlFlowGraph, controlFlowGraph);
676     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
677   }
678 
679   /**
680    * Tests a nested try/finally with implicit exit from one subroutine to the other subroutine, and
681    * with a surrounding try/catch thrown in the mix. Equivalent to the following java code:
682    *
683    * <pre>
684    * void m(int b) {
685    *   try {
686    *     try {
687    *       return;
688    *     } finally {
689    *       while (b) {
690    *         try {
691    *           return;
692    *         } finally {
693    *           // NOTE --- this break avoids the second return above (weird)
694    *           if (b) {
695    *             break;
696    *           }
697    *         }
698    *       }
699    *     }
700    *   } catch (Exception e) {
701    *     b += 3;
702    *     return;
703    *   }
704    * }
705    * </pre>
706    */
707   @Test
testVisitMaxs_implicitExitInTryCatch()708   void testVisitMaxs_implicitExitInTryCatch() {
709     TestCaseBuilder testCase =
710         new TestCaseBuilder()
711             .iconst_0() // N0
712             .istore(1)
713             // First try.
714             .label(label0) // N2
715             .jsr(label2)
716             .vreturn() // N5
717             // Exception handler for first try.
718             .label(label1) // N6
719             .astore(LOCAL2)
720             .jsr(label2)
721             .aload(LOCAL2) // N10
722             .athrow()
723             // First finally handler.
724             .label(label2) // N12
725             .astore(LOCAL4)
726             .go(label6)
727             // Body of while loop, also second try.
728             .label(label3) // N17
729             .jsr(label5)
730             .push() // N20
731             .push()
732             .vreturn()
733             // Exception handler for second try.
734             .label(label4) // N23
735             .astore(LOCAL3)
736             .jsr(label5)
737             .aload(LOCAL3) // N27
738             .athrow()
739             // Second finally handler.
740             .label(label5) // N29
741             .astore(LOCAL5)
742             .iload(LOCAL1)
743             .ifne(label7)
744             .push()
745             .push()
746             .ret(LOCAL5)
747             // Test for the while loop.
748             .label(label6) // N39
749             .iload(LOCAL1)
750             .ifne(label3) // Falls through.
751             // Exit from finally block.
752             .label(label7) // N43
753             .ret(LOCAL4)
754             // Outermost catch.
755             .label(label8) // N45
756             .iinc(LOCAL1, 3)
757             .vreturn()
758             .trycatch(label0, label1, label1)
759             .trycatch(label3, label4, label4)
760             .trycatch(label0, label8, label8);
761 
762     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
763     byte[] classFile = testCase.build();
764 
765     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
766     assertEquals(4, methodInfo.maxStack);
767     assertEquals(6, methodInfo.maxLocals);
768     Map<String, Set<String>> expectedControlFlowGraph =
769         controlFlowGraph(
770             "N0=N2",
771             "N2=N6,N45,N12",
772             "N5=N6,N45",
773             "N6=N45,N12",
774             "N10=N45",
775             "N12=N39,N45",
776             "N17=N23,N45,N29",
777             "N20=N23,N45",
778             "N23=N45,N29",
779             "N27=N45",
780             "N29=N43,N45,N20,N27",
781             "N39=N43,N45,N17",
782             "N43=N45,N5,N10",
783             "N45=");
784     assertEquals(expectedControlFlowGraph, controlFlowGraph);
785     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
786   }
787 
788   /**
789    * Tests an example coming from distilled down version of
790    * com/sun/corba/ee/impl/protocol/CorbaClientDelegateImpl from GlassFish 2. See issue #317823.
791    */
792   @Test
testVisitMaxs_glassFish2CorbaClientDelegateImplExample()793   void testVisitMaxs_glassFish2CorbaClientDelegateImplExample() {
794     TestCaseBuilder testCase =
795         new TestCaseBuilder()
796             .label(label0) // N0
797             .jsr(label4)
798             .label(label1) // N3
799             .go(label5)
800             .label(label2) // N6
801             .pop()
802             .jsr(label4)
803             .label(label3) // N10
804             .aconst_null()
805             .athrow()
806             .label(label4) // N12
807             .astore(1)
808             .ret(1)
809             .label(label5) // N15
810             .aconst_null()
811             .aconst_null()
812             .aconst_null()
813             .pop()
814             .pop()
815             .pop()
816             .label(label6) // N21
817             .go(label8)
818             .label(label7) // N24
819             .pop()
820             .go(label8)
821             .aconst_null()
822             .athrow()
823             .label(label8) // N30
824             .iconst_0()
825             .ifne(label0)
826             .jsr(label12)
827             .label(label9) // N37
828             .vreturn()
829             .label(label10) // N38
830             .pop()
831             .jsr(label12)
832             .label(label11) // N42
833             .aconst_null()
834             .athrow()
835             .label(label12) // N44
836             .astore(2)
837             .ret(2)
838             .trycatch(label0, label1, label2)
839             .trycatch(label2, label3, label2)
840             .trycatch(label0, label6, label7)
841             .trycatch(label0, label9, label10)
842             .trycatch(label10, label11, label10);
843 
844     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
845     byte[] classFile = testCase.build();
846 
847     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
848     assertEquals(3, methodInfo.maxStack);
849     assertEquals(3, methodInfo.maxLocals);
850     Map<String, Set<String>> expectedControlFlowGraph =
851         controlFlowGraph(
852             "N0=N6,N12,N24,N38",
853             "N3=N15,N24,N38",
854             "N6=N6,N12,N24,N38",
855             "N10=N24,N38",
856             "N12=N3,N10,N24,N38",
857             "N15=N21,N24,N38",
858             "N21=N30,N38",
859             "N24=N30,N38",
860             "N30=N0,N38,N44",
861             "N37=",
862             "N38=N38,N44",
863             "N42=",
864             "N44=N37,N42");
865     assertEquals(expectedControlFlowGraph, controlFlowGraph);
866     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
867   }
868 
869   /**
870    * Tests a nested subroutine with implicit exit from the nested subroutine to the outer one, with
871    * the second subroutine coming first in the bytecode instructions sequence.
872    */
873   @Test
testVisitMaxs_implicitExitToAnotherSubroutineInverted()874   void testVisitMaxs_implicitExitToAnotherSubroutineInverted() {
875     TestCaseBuilder testCase =
876         new TestCaseBuilder()
877             .go(label3) // N0
878             // Second subroutine, returns to caller of first subroutine.
879             .label(label0) // N3
880             .astore(2)
881             .label(label1) // N4
882             .ret(1)
883             // First subroutine.
884             .label(label2) // N6
885             .astore(1)
886             .aload(0)
887             .ifnonnull(label1)
888             .jsr(label0) // This JSR never returns, the following code is unreachable.
889             .aconst_null() // N14
890             .aconst_null()
891             .aconst_null()
892             .vreturn()
893             // Main "subroutine".
894             .label(label3) // N18
895             .jsr(label2)
896             .label(label4) // N21
897             .vreturn();
898 
899     Map<String, Set<String>> controlFlowGraph = testCase.visitMaxs();
900     byte[] classFile = testCase.build();
901 
902     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
903     assertEquals(1, methodInfo.maxStack);
904     assertEquals(3, methodInfo.maxLocals);
905     Map<String, Set<String>> expectedControlFlowGraph =
906         controlFlowGraph("N0=N18", "N3=N4", "N4=N21", "N6=N3,N4", "N14=", "N18=N6", "N21=");
907     assertEquals(expectedControlFlowGraph, controlFlowGraph);
908     assertDoesNotThrow(() -> new ClassFile(classFile).newInstance());
909   }
910 
911   /**
912    * Tests computing the maximum stack size from the existing stack map frames and the instructions
913    * in between, when dead code is present.
914    */
915   @Test
testVisitMaxs_framesWithDeadCode()916   void testVisitMaxs_framesWithDeadCode() {
917     TestCaseBuilder testCase =
918         new TestCaseBuilder(Opcodes.V1_7)
919             .vreturn()
920             // With the default compute maxs algorithm, this dead code block is not considered for
921             // the maximum stack size, which works fine for classes up to V1_6. Starting with V1_7,
922             // stack map frames are mandatory, even for dead code, and the maximum stack size must
923             // take dead code into account. Hopefully it can be computed from the stack map frames,
924             // and the instructions in between (without any control flow graph construction or
925             // algorithm).
926             .label(label0)
927             .frame(Opcodes.F_SAME, 0, null, 0, null)
928             .aconst_null()
929             .vreturn();
930 
931     testCase.visitMaxs();
932     byte[] classFile = testCase.build();
933 
934     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
935     assertEquals(1, methodInfo.maxStack);
936     assertEquals(1, methodInfo.maxLocals);
937   }
938 
939   @Test
testVisitMaxs_frameWithLong()940   void testVisitMaxs_frameWithLong() {
941     TestCaseBuilder testCase =
942         new TestCaseBuilder(Opcodes.V1_7)
943             .push2()
944             .go(label0)
945             .label(label0)
946             .frame(Opcodes.F_NEW, 0, null, 1, new Object[] {Opcodes.LONG})
947             .aconst_null()
948             .vreturn();
949 
950     testCase.visitMaxs();
951     byte[] classFile = testCase.build();
952 
953     MethodInfo methodInfo = readMaxStackAndLocals(classFile);
954     assertEquals(3, methodInfo.maxStack);
955     assertEquals(1, methodInfo.maxLocals);
956   }
957 
readMaxStackAndLocals(final byte[] classFile)958   private static MethodInfo readMaxStackAndLocals(final byte[] classFile) {
959     AtomicReference<MethodInfo> methodInfo = new AtomicReference<>();
960     ClassReader classReader = new ClassReader(classFile);
961     classReader.accept(
962         new ClassVisitor(Opcodes.ASM5) {
963           @Override
964           public MethodVisitor visitMethod(
965               final int access,
966               final String name,
967               final String descriptor,
968               final String signature,
969               final String[] exceptions) {
970             if (name.equals("m")) {
971               return new MethodVisitor(Opcodes.ASM5) {
972                 @Override
973                 public void visitMaxs(final int maxStack, final int maxLocals) {
974                   methodInfo.set(new MethodInfo(maxStack, maxLocals));
975                 }
976               };
977             } else {
978               return null;
979             }
980           }
981         },
982         0);
983     return methodInfo.get();
984   }
985 
controlFlowGraph(final String... nodes)986   private static Map<String, Set<String>> controlFlowGraph(final String... nodes) {
987     Map<String, Set<String>> graph = new HashMap<>();
988     for (String node : nodes) {
989       StringTokenizer stringTokenizer = new StringTokenizer(node, "=,");
990       String key = stringTokenizer.nextToken();
991       Set<String> values = new HashSet<>();
992       while (stringTokenizer.hasMoreTokens()) {
993         values.add(stringTokenizer.nextToken());
994       }
995       graph.put(key, values);
996     }
997     return graph;
998   }
999 
1000   private static class MethodInfo {
1001 
1002     public final int maxStack;
1003     public final int maxLocals;
1004 
MethodInfo(final int maxStack, final int maxLocals)1005     public MethodInfo(final int maxStack, final int maxLocals) {
1006       this.maxStack = maxStack;
1007       this.maxLocals = maxLocals;
1008     }
1009   }
1010 
1011   private static final class TestCaseBuilder { // NOPMD(TestClassWithoutTestCases)
1012 
1013     private final ClassWriter classWriter;
1014     private final MethodVisitor methodVisitor;
1015     private final Label start;
1016 
TestCaseBuilder()1017     TestCaseBuilder() {
1018       this(Opcodes.V1_1);
1019     }
1020 
TestCaseBuilder(final int classVersion)1021     TestCaseBuilder(final int classVersion) {
1022       classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
1023       classWriter.visit(classVersion, Opcodes.ACC_PUBLIC, "C", null, "java/lang/Object", null);
1024       MethodVisitor constructor =
1025           classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
1026       constructor.visitCode();
1027       constructor.visitVarInsn(Opcodes.ALOAD, 0);
1028       constructor.visitMethodInsn(
1029           Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
1030       constructor.visitInsn(Opcodes.RETURN);
1031       constructor.visitMaxs(1, 1);
1032       constructor.visitEnd();
1033       methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
1034       methodVisitor.visitCode();
1035       start = new Label();
1036       label(start);
1037     }
1038 
nop()1039     TestCaseBuilder nop() {
1040       methodVisitor.visitInsn(Opcodes.NOP);
1041       return this;
1042     }
1043 
push()1044     TestCaseBuilder push() {
1045       methodVisitor.visitInsn(Opcodes.ICONST_0);
1046       return this;
1047     }
1048 
push2()1049     TestCaseBuilder push2() {
1050       methodVisitor.visitInsn(Opcodes.LCONST_0);
1051       return this;
1052     }
1053 
pop()1054     TestCaseBuilder pop() {
1055       methodVisitor.visitInsn(Opcodes.POP);
1056       return this;
1057     }
1058 
iconst_0()1059     TestCaseBuilder iconst_0() {
1060       methodVisitor.visitInsn(Opcodes.ICONST_0);
1061       return this;
1062     }
1063 
istore(final int varIndex)1064     TestCaseBuilder istore(final int varIndex) {
1065       methodVisitor.visitVarInsn(Opcodes.ISTORE, varIndex);
1066       return this;
1067     }
1068 
aload(final int varIndex)1069     TestCaseBuilder aload(final int varIndex) {
1070       methodVisitor.visitVarInsn(Opcodes.ALOAD, varIndex);
1071       return this;
1072     }
1073 
iload(final int varIndex)1074     TestCaseBuilder iload(final int varIndex) {
1075       methodVisitor.visitVarInsn(Opcodes.ILOAD, varIndex);
1076       return this;
1077     }
1078 
astore(final int varIndex)1079     TestCaseBuilder astore(final int varIndex) {
1080       methodVisitor.visitVarInsn(Opcodes.ASTORE, varIndex);
1081       return this;
1082     }
1083 
ret(final int varIndex)1084     TestCaseBuilder ret(final int varIndex) {
1085       methodVisitor.visitVarInsn(Opcodes.RET, varIndex);
1086       return this;
1087     }
1088 
athrow()1089     TestCaseBuilder athrow() {
1090       methodVisitor.visitInsn(Opcodes.ATHROW);
1091       return this;
1092     }
1093 
aconst_null()1094     TestCaseBuilder aconst_null() {
1095       methodVisitor.visitInsn(Opcodes.ACONST_NULL);
1096       return this;
1097     }
1098 
vreturn()1099     TestCaseBuilder vreturn() {
1100       methodVisitor.visitInsn(Opcodes.RETURN);
1101       return this;
1102     }
1103 
label(final Label label)1104     TestCaseBuilder label(final Label label) {
1105       methodVisitor.visitLabel(label);
1106       return this;
1107     }
1108 
iinc(final int varIndex, final int increment)1109     TestCaseBuilder iinc(final int varIndex, final int increment) {
1110       methodVisitor.visitIincInsn(varIndex, increment);
1111       return this;
1112     }
1113 
frame( final int type, final int numLocal, final Object[] local, final int numStack, final Object[] stack)1114     TestCaseBuilder frame(
1115         final int type,
1116         final int numLocal,
1117         final Object[] local,
1118         final int numStack,
1119         final Object[] stack) {
1120       methodVisitor.visitFrame(type, numLocal, local, numStack, stack);
1121       return this;
1122     }
1123 
go(final Label label)1124     TestCaseBuilder go(final Label label) {
1125       methodVisitor.visitJumpInsn(Opcodes.GOTO, label);
1126       return this;
1127     }
1128 
jsr(final Label label)1129     TestCaseBuilder jsr(final Label label) {
1130       methodVisitor.visitJumpInsn(Opcodes.JSR, label);
1131       return this;
1132     }
1133 
ifnonnull(final Label label)1134     TestCaseBuilder ifnonnull(final Label label) {
1135       methodVisitor.visitJumpInsn(Opcodes.IFNONNULL, label);
1136       return this;
1137     }
1138 
ifne(final Label label)1139     TestCaseBuilder ifne(final Label label) {
1140       methodVisitor.visitJumpInsn(Opcodes.IFNE, label);
1141       return this;
1142     }
1143 
trycatch(final Label start, final Label end, final Label handler)1144     TestCaseBuilder trycatch(final Label start, final Label end, final Label handler) {
1145       methodVisitor.visitTryCatchBlock(start, end, handler, null);
1146       return this;
1147     }
1148 
localVariable( final String name, final String descriptor, final String signature, final Label start, final Label end, final int index)1149     TestCaseBuilder localVariable(
1150         final String name,
1151         final String descriptor,
1152         final String signature,
1153         final Label start,
1154         final Label end,
1155         final int index) {
1156       methodVisitor.visitLocalVariable(name, descriptor, signature, start, end, index);
1157       return this;
1158     }
1159 
1160     /**
1161      * Calls the visitMaxs method and returns the computed control flow graph.
1162      *
1163      * @return the computed control flow graph.
1164      */
visitMaxs()1165     Map<String, Set<String>> visitMaxs() {
1166       methodVisitor.visitMaxs(0, 0);
1167 
1168       Map<String, Set<String>> graph = new HashMap<>();
1169       Label currentLabel = start;
1170       while (currentLabel != null) {
1171         String key = "N" + currentLabel.getOffset();
1172         Set<String> value = new HashSet<>();
1173         Edge outgoingEdge = currentLabel.outgoingEdges;
1174         if ((currentLabel.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) {
1175           // Ignore the first outgoing edge of the basic blocks ending with a jsr: these are virtual
1176           // edges which lead to the instruction just after the jsr, and do not correspond to a
1177           // possible execution path (see {@link #visitJumpInsn} and
1178           // {@link Label#FLAG_SUBROUTINE_CALLER}).
1179           assertNotNull(outgoingEdge);
1180           outgoingEdge = outgoingEdge.nextEdge;
1181         }
1182         while (outgoingEdge != null) {
1183           value.add("N" + outgoingEdge.successor.getOffset());
1184           outgoingEdge = outgoingEdge.nextEdge;
1185         }
1186         graph.put(key, value);
1187         currentLabel = currentLabel.nextBasicBlock;
1188       }
1189       return graph;
1190     }
1191 
build()1192     byte[] build() {
1193       methodVisitor.visitEnd();
1194       classWriter.visitEnd();
1195       return classWriter.toByteArray();
1196     }
1197   }
1198 }
1199