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