Skip to content

Commit 5a03e47

Browse files
committed
8255560: Class::isRecord should check that the current class is final and not abstract
Reviewed-by: mchung, darcy
1 parent 8e8e584 commit 5a03e47

File tree

3 files changed

+227
-22
lines changed

3 files changed

+227
-22
lines changed

src/java.base/share/classes/java/lang/Class.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3662,9 +3662,10 @@ public boolean isEnum() {
36623662
* Returns {@code true} if and only if this class is a record class.
36633663
*
36643664
* <p> The {@linkplain #getSuperclass() direct superclass} of a record
3665-
* class is {@code java.lang.Record}. A record class has (possibly zero)
3666-
* record components, that is, {@link #getRecordComponents()} returns a
3667-
* non-null value.
3665+
* class is {@code java.lang.Record}. A record class is {@linkplain
3666+
* Modifier#FINAL final}. A record class has (possibly zero) record
3667+
* components; {@link #getRecordComponents()} returns a non-null but
3668+
* possibly empty value for a record.
36683669
*
36693670
* <p> Note that class {@link Record} is not a record type and thus invoking
36703671
* this method on class {@code Record} returns {@code false}.
@@ -3674,7 +3675,9 @@ public boolean isEnum() {
36743675
* @since 16
36753676
*/
36763677
public boolean isRecord() {
3677-
return getSuperclass() == java.lang.Record.class && isRecord0();
3678+
return getSuperclass() == java.lang.Record.class &&
3679+
(this.getModifiers() & Modifier.FINAL) != 0 &&
3680+
isRecord0();
36783681
}
36793682

36803683
// Fetches the factory for reflective objects
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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+
}

test/lib/jdk/test/lib/ByteCodeLoader.java

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,8 @@
2424
package jdk.test.lib;
2525

2626
import java.security.SecureClassLoader;
27+
import java.util.Map;
28+
import java.util.concurrent.ConcurrentHashMap;
2729

2830
/**
2931
* {@code ByteCodeLoader} can be used for easy loading of byte code already
@@ -35,9 +37,14 @@
3537
* @see InMemoryCompiler
3638
*/
3739
public class ByteCodeLoader extends SecureClassLoader {
38-
private final String className;
39-
private final byte[] byteCode;
40-
private volatile Class<?> holder;
40+
private final Map<String,byte[]> classBytesMap;
41+
private final Map<String,Class<?>> cache;
42+
43+
public ByteCodeLoader(Map<String,byte[]> classBytesMap, ClassLoader parent) {
44+
super(parent);
45+
this.classBytesMap = classBytesMap;
46+
cache = new ConcurrentHashMap<>();
47+
}
4148

4249
/**
4350
* Creates a new {@code ByteCodeLoader} ready to load a class with the
@@ -48,8 +55,9 @@ public class ByteCodeLoader extends SecureClassLoader {
4855
* @param byteCode The byte code of the class
4956
*/
5057
public ByteCodeLoader(String className, byte[] byteCode) {
51-
this.className = className;
52-
this.byteCode = byteCode;
58+
super();
59+
classBytesMap = Map.of(className, byteCode);
60+
cache = new ConcurrentHashMap<>();
5361
}
5462

5563
/**
@@ -59,34 +67,39 @@ public ByteCodeLoader(String className, byte[] byteCode) {
5967
*
6068
* @param className The name of the class
6169
* @param byteCode The byte code of the class
70+
* @param parent The parent class loader for delegation
6271
*/
6372
public ByteCodeLoader(String className, byte[] byteCode, ClassLoader parent) {
64-
super(parent);
65-
this.className = className;
66-
this.byteCode = byteCode;
73+
this(Map.of(className, byteCode), parent);
6774
}
6875

76+
private static final Object lock = new Object();
77+
6978
@Override
7079
public Class<?> loadClass(String name) throws ClassNotFoundException {
71-
if (!name.equals(className)) {
80+
if (classBytesMap.get(name) == null) {
7281
return super.loadClass(name);
7382
}
74-
if (holder == null) {
75-
synchronized(this) {
76-
if (holder == null) {
77-
holder = findClass(name);
78-
}
83+
Class<?> cls = cache.get(name);
84+
if (cls != null) {
85+
return cls;
86+
}
87+
synchronized (lock) {
88+
cls = cache.get(name);
89+
if (cls == null) {
90+
cls = findClass(name);
91+
cache.put(name, cls);
7992
}
8093
}
81-
return holder;
94+
return cls;
8295
}
8396

8497
@Override
8598
protected Class<?> findClass(String name) throws ClassNotFoundException {
86-
if (!name.equals(className)) {
99+
byte[] byteCode = classBytesMap.get(name);
100+
if (byteCode == null) {
87101
throw new ClassNotFoundException(name);
88102
}
89-
90103
return defineClass(name, byteCode, 0, byteCode.length);
91104
}
92105

0 commit comments

Comments
 (0)