1 /* 2 * Copyright (c) 2020, 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 * @test 26 * @bug 8255560 27 * @summary Class::isRecord should check that the current class is final and not abstract 28 * @modules java.base/jdk.internal.org.objectweb.asm 29 * @library /test/lib 30 * @run testng/othervm IsRecordTest 31 * @run testng/othervm/java.security.policy=allPermissions.policy IsRecordTest 32 */ 33 34 import java.io.IOException; 35 import java.io.UncheckedIOException; 36 import java.util.List; 37 import java.util.Map; 38 import jdk.internal.org.objectweb.asm.ClassWriter; 39 import jdk.internal.org.objectweb.asm.Opcodes; 40 import jdk.test.lib.ByteCodeLoader; 41 import org.testng.annotations.DataProvider; 42 import org.testng.annotations.Test; 43 import static java.lang.System.out; 44 import static jdk.internal.org.objectweb.asm.ClassWriter.*; 45 import static org.testng.Assert.assertEquals; 46 import static org.testng.Assert.assertFalse; 47 import static org.testng.Assert.assertTrue; 48 49 public class IsRecordTest { 50 51 @DataProvider(name = "scenarios") 52 public Object[][] scenarios() { 53 return new Object[][] { 54 // isFinal, isAbstract, extendJLR, withRecAttr, expectIsRecord 55 { false, false, false, true, false }, 56 { false, false, true, true, false }, 57 { false, true, false, true, false }, 58 { false, true, true, true, false }, 59 { true, false, false, true, false }, 60 { true, false, true, true, true }, 61 62 { false, false, false, false, false }, 63 { false, false, true, false, false }, 64 { false, true, false, false, false }, 65 { false, true, true, false, false }, 66 { true, false, false, false, false }, 67 { true, false, true, false, false }, 68 }; 69 } 70 71 /** 72 * Tests the valid combinations of i) final/non-final, ii) abstract/non-abstract, 73 * iii) direct subclass of j.l.Record (or not), along with the presence or 74 * absence of a record attribute. 75 */ 76 @Test(dataProvider = "scenarios") 77 public void testDirectSubClass(boolean isFinal, 78 boolean isAbstract, 79 boolean extendsJLR, 80 boolean withRecordAttr, 81 boolean expectIsRecord) throws Exception { 82 out.println("\n--- testDirectSubClass isFinal=%s, isAbstract=%s, extendsJLR=%s, withRecordAttr=%s, expectIsRecord=%s ---" 83 .formatted(isFinal, isAbstract, extendsJLR, withRecordAttr, expectIsRecord)); 84 85 List<RecordComponentEntry> rc = null; 86 if (withRecordAttr) 87 rc = List.of(new RecordComponentEntry("x", "I")); 88 String superName = extendsJLR ? "java/lang/Record" : "java/lang/Object"; 89 var classBytes = generateClassBytes("C", isFinal, isAbstract, superName, rc); 90 Class<?> cls = ByteCodeLoader.load("C", classBytes); 91 out.println("cls=%s, Record::isAssignable=%s, isRecord=%s" 92 .formatted(cls, Record.class.isAssignableFrom(cls), cls.isRecord())); 93 assertEquals(cls.isRecord(), expectIsRecord); 94 var getRecordComponents = cls.getRecordComponents(); 95 assertTrue(expectIsRecord ? getRecordComponents != null : getRecordComponents == null); 96 } 97 98 /** 99 * Tests the valid combinations of i) final/non-final, ii) abstract/non-abstract, 100 * along with the presence or absence of a record attribute, where the class has 101 * a superclass whose superclass is j.l.Record. 102 */ 103 @Test(dataProvider = "scenarios") 104 public void testIndirectSubClass(boolean isFinal, 105 boolean isAbstract, 106 boolean unused1, 107 boolean withRecordAttr, 108 boolean unused2) throws Exception { 109 out.println("\n--- testIndirectSubClass isFinal=%s, isAbstract=%s withRecordAttr=%s ---" 110 .formatted(isFinal, isAbstract, withRecordAttr)); 111 112 List<RecordComponentEntry> rc = null; 113 if (withRecordAttr) 114 rc = List.of(new RecordComponentEntry("x", "I")); 115 var supFooClassBytes = generateClassBytes("SupFoo", false, isAbstract, "java/lang/Record", rc); 116 var subFooClassBytes = generateClassBytes("SubFoo", isFinal, isAbstract, "SupFoo", rc); 117 var allClassBytes = Map.of("SupFoo", supFooClassBytes, 118 "SubFoo", subFooClassBytes); 119 120 ClassLoader loader = new ByteCodeLoader(allClassBytes, null); 121 Class<?> supFooCls = loader.loadClass("SupFoo"); 122 Class<?> subFooCls = loader.loadClass("SubFoo"); 123 for (var cls : List.of(supFooCls, subFooCls)) 124 out.println("cls=%s, Record::isAssignable=%s, isRecord=%s" 125 .formatted(cls, Record.class.isAssignableFrom(cls), cls.isRecord())); 126 assertFalse(supFooCls.isRecord()); 127 assertFalse(subFooCls.isRecord()); 128 assertEquals(supFooCls.getRecordComponents(), null); 129 assertEquals(subFooCls.getRecordComponents(), null); 130 } 131 132 /** Tests record-ness properties of traditionally compiled classes. */ 133 @Test 134 public void testBasicRecords() { 135 out.println("\n--- testBasicRecords ---"); 136 record EmptyRecord () { } 137 assertTrue(EmptyRecord.class.isRecord()); 138 assertEquals(EmptyRecord.class.getRecordComponents().length, 0); 139 140 record FooRecord (int x) { } 141 assertTrue(FooRecord.class.isRecord()); 142 assertTrue(FooRecord.class.getRecordComponents() != null); 143 144 final record FinalFooRecord (int x) { } 145 assertTrue(FinalFooRecord.class.isRecord()); 146 assertTrue(FinalFooRecord.class.getRecordComponents() != null); 147 148 class A { } 149 assertFalse(A.class.isRecord()); 150 assertFalse(A.class.getRecordComponents() != null); 151 152 final class B { } 153 assertFalse(B.class.isRecord()); 154 assertFalse(B.class.getRecordComponents() != null); 155 } 156 157 // -- infra 158 159 // Generates a class with the given properties. 160 byte[] generateClassBytes(String className, 161 boolean isFinal, 162 boolean isAbstract, 163 String superName, 164 List<RecordComponentEntry> components) { 165 ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); 166 167 int access = 0; 168 if (isFinal) 169 access = access | Opcodes.ACC_FINAL; 170 if (isAbstract) 171 access = access | Opcodes.ACC_ABSTRACT; 172 173 cw.visit(Opcodes.V16, 174 access, 175 className, 176 null, 177 superName, 178 null); 179 180 if (components != null) 181 components.forEach(rc -> cw.visitRecordComponent(rc.name(), rc.descriptor(), null)); 182 183 cw.visitEnd(); 184 return cw.toByteArray(); 185 } 186 187 record RecordComponentEntry (String name, String descriptor) { } 188 189 }