1 /* 2 * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * ValueObjectCompilationTests 26 * 27 * @test 28 * @bug 8287136 8292630 8279368 8287136 8287770 8279840 8279672 8292753 8287763 8279901 8287767 8293183 8293120 29 * 8329345 30 * @summary Negative compilation tests, and positive compilation (smoke) tests for Value Objects 31 * @library /lib/combo /tools/lib 32 * @modules 33 * jdk.compiler/com.sun.tools.javac.util 34 * jdk.compiler/com.sun.tools.javac.api 35 * jdk.compiler/com.sun.tools.javac.main 36 * jdk.compiler/com.sun.tools.javac.code 37 * jdk.jdeps/com.sun.tools.classfile 38 * @build toolbox.ToolBox toolbox.JavacTask 39 * @run junit ValueObjectCompilationTests 40 */ 41 42 import java.io.File; 43 44 import java.util.List; 45 import java.util.Set; 46 47 import com.sun.tools.javac.util.Assert; 48 49 import com.sun.tools.classfile.Attribute; 50 import com.sun.tools.classfile.Attributes; 51 import com.sun.tools.classfile.ClassFile; 52 import com.sun.tools.classfile.Code_attribute; 53 import com.sun.tools.classfile.ConstantPool; 54 import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info; 55 import com.sun.tools.classfile.ConstantPool.CONSTANT_Fieldref_info; 56 import com.sun.tools.classfile.ConstantPool.CONSTANT_Methodref_info; 57 import com.sun.tools.classfile.Field; 58 import com.sun.tools.classfile.Instruction; 59 import com.sun.tools.classfile.Method; 60 61 import com.sun.tools.javac.code.Flags; 62 63 import org.junit.jupiter.api.Test; 64 import tools.javac.combo.CompilationTestCase; 65 import toolbox.ToolBox; 66 67 class ValueObjectCompilationTests extends CompilationTestCase { 68 69 private static String[] PREVIEW_OPTIONS = {"--enable-preview", "-source", 70 Integer.toString(Runtime.version().feature())}; 71 72 public ValueObjectCompilationTests() { 73 setDefaultFilename("ValueObjectsTest.java"); 74 setCompileOptions(PREVIEW_OPTIONS); 75 } 76 77 @Test 78 void testAbstractValueClassConstraints() { 79 assertFail("compiler.err.mod.not.allowed.here", 80 """ 81 abstract value class V { 82 synchronized void foo() { 83 // Error, abstract value class may not declare a synchronized instance method. 84 } 85 } 86 """); 87 } 88 89 @Test 90 void testValueModifierConstraints() { 91 assertFail("compiler.err.illegal.combination.of.modifiers", 92 """ 93 value @interface IA {} 94 """); 95 assertFail("compiler.err.illegal.combination.of.modifiers", 96 """ 97 value interface I {} 98 """); 99 assertFail("compiler.err.mod.not.allowed.here", 100 """ 101 class Test { 102 value int x; 103 } 104 """); 105 assertFail("compiler.err.mod.not.allowed.here", 106 """ 107 class Test { 108 value int foo(); 109 } 110 """); 111 assertFail("compiler.err.mod.not.allowed.here", 112 """ 113 value enum Enum {} 114 """); 115 } 116 117 @Test 118 void testSuperClassConstraints() { 119 assertFail("compiler.err.super.class.method.cannot.be.synchronized", 120 """ 121 abstract class I { 122 synchronized void foo() {} 123 } 124 value class V extends I {} 125 """); 126 assertFail("compiler.err.concrete.supertype.for.value.class", 127 """ 128 class ConcreteSuperType { 129 static abstract value class V extends ConcreteSuperType {} // Error: concrete super. 130 } 131 """); 132 assertOK( 133 """ 134 value record Point(int x, int y) {} 135 """); 136 } 137 138 @Test 139 void testRepeatedModifiers() { 140 assertFail("compiler.err.repeated.modifier", "value value class ValueTest {}"); 141 } 142 143 @Test 144 void testParserTest() { 145 assertOK( 146 """ 147 value class Substring implements CharSequence { 148 private String str; 149 private int start; 150 private int end; 151 152 public Substring(String str, int start, int end) { 153 checkBounds(start, end, str.length()); 154 this.str = str; 155 this.start = start; 156 this.end = end; 157 } 158 159 public int length() { 160 return end - start; 161 } 162 163 public char charAt(int i) { 164 checkBounds(0, i, length()); 165 return str.charAt(start + i); 166 } 167 168 public Substring subSequence(int s, int e) { 169 checkBounds(s, e, length()); 170 return new Substring(str, start + s, start + e); 171 } 172 173 public String toString() { 174 return str.substring(start, end); 175 } 176 177 private static void checkBounds(int start, int end, int length) { 178 if (start < 0 || end < start || length < end) 179 throw new IndexOutOfBoundsException(); 180 } 181 } 182 """ 183 ); 184 } 185 186 @Test 187 void testSemanticsViolations() { 188 assertFail("compiler.err.cant.inherit.from.final", 189 """ 190 value class Base {} 191 class Subclass extends Base {} 192 """); 193 assertFail("compiler.err.cant.assign.val.to.var", 194 """ 195 value class Point { 196 int x = 10; 197 int y; 198 Point (int x, int y) { 199 this.x = x; // Error, final field 'x' is already assigned to. 200 this.y = y; // OK. 201 } 202 } 203 """); 204 assertFail("compiler.err.cant.assign.val.to.var", 205 """ 206 value class Point { 207 int x; 208 int y; 209 Point (int x, int y) { 210 this.x = x; 211 this.y = y; 212 } 213 214 void foo(Point p) { 215 this.y = p.y; // Error, y is final and can't be written outside of ctor. 216 } 217 } 218 """); 219 assertFail("compiler.err.var.might.not.have.been.initialized", 220 """ 221 value class Point { 222 int x; 223 int y; 224 Point (int x, int y) { 225 this.x = x; 226 // y hasn't been initialized 227 } 228 } 229 """); 230 assertFail("compiler.err.mod.not.allowed.here", 231 """ 232 value class V { 233 synchronized void foo() {} 234 } 235 """); 236 assertOK( 237 """ 238 value class V { 239 synchronized static void soo() {} 240 } 241 """); 242 assertFail("compiler.err.type.found.req", 243 """ 244 value class V { 245 { synchronized(this) {} } 246 } 247 """); 248 assertFail("compiler.err.mod.not.allowed.here", 249 """ 250 value record R() { 251 synchronized void foo() { } // Error; 252 synchronized static void soo() {} // OK. 253 } 254 """); 255 assertFail("compiler.err.cant.ref.before.ctor.called", 256 """ 257 value class V { 258 int x; 259 V() { 260 foo(this); // Error. 261 x = 10; 262 } 263 void foo(V v) {} 264 } 265 """); 266 assertFail("compiler.err.cant.ref.before.ctor.called", 267 """ 268 value class V { 269 int x; 270 V() { 271 x = 10; 272 foo(this); // error 273 } 274 void foo(V v) {} 275 } 276 """); 277 assertFail("compiler.err.type.found.req", 278 """ 279 interface I {} 280 interface VI extends I {} 281 class C {} 282 value class VC<T extends VC> { 283 void m(T t) { 284 synchronized(t) {} // error 285 } 286 } 287 """); 288 assertFail("compiler.err.type.found.req", 289 """ 290 interface I {} 291 interface VI extends I {} 292 class C {} 293 value class VC<T extends VC> { 294 void foo(Object o) { 295 synchronized ((VC & I)o) {} // error 296 } 297 } 298 """); 299 // OK if the value class is abstract 300 assertOK( 301 """ 302 interface I {} 303 abstract value class VI implements I {} 304 class C {} 305 value class VC<T extends VC> { 306 void bar(Object o) { 307 synchronized ((VI & I)o) {} // error 308 } 309 } 310 """); 311 } 312 313 @Test 314 void testInteractionWithSealedClasses() { 315 assertOK( 316 """ 317 abstract sealed value class SC {} 318 value class VC extends SC {} 319 """ 320 ); 321 assertOK( 322 """ 323 abstract sealed interface SI {} 324 value class VC implements SI {} 325 """ 326 ); 327 assertOK( 328 """ 329 abstract sealed class SC {} 330 final class IC extends SC {} 331 non-sealed class IC2 extends SC {} 332 final class IC3 extends IC2 {} 333 """ 334 ); 335 assertOK( 336 """ 337 abstract sealed interface SI {} 338 final class IC implements SI {} 339 non-sealed class IC2 implements SI {} 340 final class IC3 extends IC2 {} 341 """ 342 ); 343 assertFail("compiler.err.illegal.combination.of.modifiers", 344 """ 345 abstract sealed value class SC {} 346 non-sealed value class VC extends SC {} 347 """ 348 ); 349 assertFail("compiler.err.illegal.combination.of.modifiers", 350 """ 351 sealed value class SI {} 352 non-sealed value class VC extends SI {} 353 """ 354 ); 355 } 356 357 @Test 358 void testCheckClassFileFlags() throws Exception { 359 for (String source : List.of( 360 """ 361 interface I {} 362 class Test { 363 I i = new I() {}; 364 } 365 """, 366 """ 367 class C {} 368 class Test { 369 C c = new C() {}; 370 } 371 """, 372 """ 373 class Test { 374 Object o = new Object() {}; 375 } 376 """, 377 """ 378 class Test { 379 abstract class Inner {} 380 } 381 """ 382 )) { 383 File dir = assertOK(true, source); 384 for (final File fileEntry : dir.listFiles()) { 385 if (fileEntry.getName().contains("$")) { 386 ClassFile classFile = ClassFile.read(fileEntry); 387 Assert.check((classFile.access_flags.flags & Flags.ACC_IDENTITY) != 0); 388 } 389 } 390 } 391 392 for (String source : List.of( 393 """ 394 class C {} 395 """, 396 """ 397 abstract class A { 398 int i; 399 } 400 """, 401 """ 402 abstract class A { 403 synchronized void m() {} 404 } 405 """, 406 """ 407 class C { 408 synchronized void m() {} 409 } 410 """, 411 """ 412 abstract class A { 413 int i; 414 { i = 0; } 415 } 416 """, 417 """ 418 abstract class A { 419 A(int i) {} 420 } 421 """, 422 """ 423 enum E {} 424 """, 425 """ 426 record R() {} 427 """ 428 )) { 429 File dir = assertOK(true, source); 430 for (final File fileEntry : dir.listFiles()) { 431 ClassFile classFile = ClassFile.read(fileEntry); 432 Assert.check(classFile.access_flags.is(Flags.ACC_IDENTITY)); 433 } 434 } 435 436 { 437 String source = 438 """ 439 abstract value class A {} 440 value class Sub extends A {} //implicitly final 441 """; 442 File dir = assertOK(true, source); 443 for (final File fileEntry : dir.listFiles()) { 444 ClassFile classFile = ClassFile.read(fileEntry); 445 switch (classFile.getName()) { 446 case "Sub": 447 Assert.check((classFile.access_flags.flags & (Flags.FINAL)) != 0); 448 break; 449 case "A": 450 Assert.check((classFile.access_flags.flags & (Flags.ABSTRACT)) != 0); 451 break; 452 default: 453 throw new AssertionError("you shoulnd't be here"); 454 } 455 } 456 } 457 458 for (String source : List.of( 459 """ 460 value class V { 461 int i = 0; 462 static int j; 463 } 464 """, 465 """ 466 abstract value class A { 467 static int j; 468 } 469 470 value class V extends A { 471 int i = 0; 472 } 473 """ 474 )) { 475 File dir = assertOK(true, source); 476 for (final File fileEntry : dir.listFiles()) { 477 ClassFile classFile = ClassFile.read(fileEntry); 478 for (Field field : classFile.fields) { 479 if (!field.access_flags.is(Flags.STATIC)) { 480 Set<String> fieldFlags = field.access_flags.getFieldFlags(); 481 Assert.check(fieldFlags.size() == 2 && fieldFlags.contains("ACC_FINAL") && fieldFlags.contains("ACC_STRICT")); 482 } 483 } 484 } 485 } 486 } 487 488 @Test 489 void testConstruction() throws Exception { 490 record Data(String src, boolean isRecord) {} 491 for (Data data : List.of( 492 new Data( 493 """ 494 value class Test { 495 int i = 100; 496 } 497 """, false), 498 new Data( 499 """ 500 value class Test { 501 int i; 502 Test() { 503 i = 100; 504 } 505 } 506 """, false), 507 new Data( 508 """ 509 value class Test { 510 int i; 511 Test() { 512 i = 100; 513 super(); 514 } 515 } 516 """, false), 517 new Data( 518 """ 519 value class Test { 520 int i; 521 Test() { 522 this.i = 100; 523 super(); 524 } 525 } 526 """, false), 527 new Data( 528 """ 529 value record Test(int i) {} 530 """, true) 531 )) { 532 String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,invokespecial,return,"; 533 String expectedCodeSequenceRecord = "aload_0,iload_1,putfield,aload_0,invokespecial,return,"; 534 File dir = assertOK(true, data.src); 535 for (final File fileEntry : dir.listFiles()) { 536 ClassFile classFile = ClassFile.read(fileEntry); 537 for (Method method : classFile.methods) { 538 if (method.getName(classFile.constant_pool).equals("<init>")) { 539 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 540 String foundCodeSequence = ""; 541 for (Instruction inst: code.getInstructions()) { 542 foundCodeSequence += inst.getMnemonic() + ","; 543 } 544 if (!data.isRecord) { 545 Assert.check(expectedCodeSequence.equals(foundCodeSequence)); 546 } else { 547 Assert.check(expectedCodeSequenceRecord.equals(foundCodeSequence)); 548 } 549 } 550 } 551 } 552 } 553 554 String source = 555 """ 556 value class Test { 557 int i = 100; 558 int j; 559 { 560 j = 200; 561 } 562 } 563 """; 564 String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,invokespecial,aload_0,sipush,putfield,return,"; 565 File dir = assertOK(true, source); 566 for (final File fileEntry : dir.listFiles()) { 567 ClassFile classFile = ClassFile.read(fileEntry); 568 for (Method method : classFile.methods) { 569 if (method.getName(classFile.constant_pool).equals("<init>")) { 570 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 571 String foundCodeSequence = ""; 572 for (Instruction inst: code.getInstructions()) { 573 foundCodeSequence += inst.getMnemonic() + ","; 574 } 575 Assert.check(expectedCodeSequence.equals(foundCodeSequence)); 576 } 577 } 578 } 579 580 assertFail("compiler.err.cant.ref.before.ctor.called", 581 """ 582 value class Test { 583 Test() { 584 m(); 585 } 586 void m() {} 587 } 588 """ 589 ); 590 assertFail("compiler.err.cant.ref.after.ctor.called", 591 """ 592 value class Test { 593 int i; 594 Test() { 595 super(); 596 this.i = i; 597 } 598 } 599 """ 600 ); 601 assertOK( 602 """ 603 class UnrelatedThisLeak { 604 value class V { 605 int f; 606 V() { 607 UnrelatedThisLeak x = UnrelatedThisLeak.this; 608 f = 10; 609 x = UnrelatedThisLeak.this; 610 } 611 } 612 } 613 """ 614 ); 615 } 616 617 @Test 618 void testThisCallingConstructor() throws Exception { 619 // make sure that this() calling constructors doesn't initialize final fields 620 String source = 621 """ 622 value class Test { 623 int i; 624 Test() { 625 this(0); 626 } 627 628 Test(int i) { 629 this.i = i; 630 } 631 } 632 """; 633 File dir = assertOK(true, source); 634 File fileEntry = dir.listFiles()[0]; 635 ClassFile classFile = ClassFile.read(fileEntry); 636 String expectedCodeSequenceThisCallingConst = "aload_0,iconst_0,invokespecial,return,"; 637 String expectedCodeSequenceNonThisCallingConst = "aload_0,iload_1,putfield,aload_0,invokespecial,return,"; 638 for (Method method : classFile.methods) { 639 if (method.getName(classFile.constant_pool).equals("<init>")) { 640 if (method.descriptor.getParameterCount(classFile.constant_pool) == 0) { 641 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 642 String foundCodeSequence = ""; 643 for (Instruction inst: code.getInstructions()) { 644 foundCodeSequence += inst.getMnemonic() + ","; 645 } 646 Assert.check(expectedCodeSequenceThisCallingConst.equals(foundCodeSequence)); 647 } else { 648 Code_attribute code = (Code_attribute)method.attributes.get("Code"); 649 String foundCodeSequence = ""; 650 for (Instruction inst: code.getInstructions()) { 651 foundCodeSequence += inst.getMnemonic() + ","; 652 } 653 Assert.check(expectedCodeSequenceNonThisCallingConst.equals(foundCodeSequence)); 654 } 655 } 656 } 657 } 658 659 @Test 660 void testSelectors() throws Exception { 661 assertOK( 662 """ 663 value class V { 664 void selector() { 665 Class<?> c = int.class; 666 } 667 } 668 """ 669 ); 670 assertFail("compiler.err.expected", 671 """ 672 value class V { 673 void selector() { 674 int i = int.some_selector; 675 } 676 } 677 """ 678 ); 679 } 680 681 @Test 682 void testAnonymousValue() throws Exception { 683 assertOK( 684 """ 685 class Test { 686 void m() { 687 Object o = new value Comparable<String>() { 688 @Override 689 public int compareTo(String o) { 690 return 0; 691 } 692 }; 693 } 694 } 695 """ 696 ); 697 assertOK( 698 """ 699 class Test { 700 void m() { 701 Object o = new value Comparable<>() { 702 @Override 703 public int compareTo(Object o) { 704 return 0; 705 } 706 }; 707 } 708 } 709 """ 710 ); 711 } 712 713 @Test 714 void testNullAssigment() throws Exception { 715 assertOK( 716 """ 717 value final class V { 718 final int x = 10; 719 720 value final class X { 721 final V v; 722 final V v2; 723 724 X() { 725 this.v = null; 726 this.v2 = null; 727 } 728 729 X(V v) { 730 this.v = v; 731 this.v2 = v; 732 } 733 734 V foo(X x) { 735 x = new X(null); // OK 736 return x.v; 737 } 738 } 739 V bar(X x) { 740 x = new X(null); // OK 741 return x.v; 742 } 743 744 class Y { 745 V v; 746 V [] va = { null }; // OK: array initialization 747 V [] va2 = new V[] { null }; // OK: array initialization 748 void ooo(X x) { 749 x = new X(null); // OK 750 v = null; // legal assignment. 751 va[0] = null; // legal. 752 va = new V[] { null }; // legal 753 } 754 } 755 } 756 """ 757 ); 758 } 759 760 private File findClassFileOrFail(File dir, String name) { 761 for (final File fileEntry : dir.listFiles()) { 762 if (fileEntry.getName().equals(name)) { 763 return fileEntry; 764 } 765 } 766 throw new AssertionError("file not found"); 767 } 768 769 private Attribute findAttributeOrFail(Attributes attributes, Class<? extends Attribute> attrClass, int numberOfAttributes) { 770 int attrCount = 0; 771 Attribute result = null; 772 for (Attribute attribute : attributes) { 773 if (attribute.getClass() == attrClass) { 774 attrCount++; 775 if (result == null) { 776 result = attribute; 777 } 778 } 779 } 780 if (attrCount == 0) throw new AssertionError("attribute not found"); 781 if (attrCount != numberOfAttributes) throw new AssertionError("incorrect number of attributes found"); 782 return result; 783 } 784 785 private void checkAttributeNotPresent(Attributes attributes, Class<? extends Attribute> attrClass) { 786 for (Attribute attribute : attributes) { 787 if (attribute.getClass() == attrClass) { 788 throw new AssertionError("attribute found"); 789 } 790 } 791 } 792 }