Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8246774: implement Record Classes as a standard feature in Java #290

Closed
@@ -3567,12 +3567,6 @@ bool ClassFileParser::supports_sealed_types() {
Arguments::enable_preview();
}

bool ClassFileParser::supports_records() {
return _major_version == JVM_CLASSFILE_MAJOR_VERSION &&
_minor_version == JAVA_PREVIEW_MINOR_VERSION &&
Arguments::enable_preview();
}

void ClassFileParser::parse_classfile_attributes(const ClassFileStream* const cfs,
ConstantPool* cp,
ClassFileParser::ClassAnnotationCollector* parsed_annotations,
@@ -3820,12 +3814,28 @@ void ClassFileParser::parse_classfile_attributes(const ClassFileStream* const cf
"Nest-host class_info_index %u has bad constant type in class file %s",
class_info_index, CHECK);
_nest_host = class_info_index;
} else if (_major_version >= JAVA_14_VERSION) {

} else if (_major_version >= JAVA_15_VERSION) {
// Check for PermittedSubclasses tag
if (tag == vmSymbols::tag_permitted_subclasses()) {
if (supports_sealed_types()) {
if (parsed_permitted_subclasses_attribute) {
classfile_parse_error("Multiple PermittedSubclasses attributes in class file %s", CHECK);
}
// Classes marked ACC_FINAL cannot have a PermittedSubclasses attribute.
if (_access_flags.is_final()) {
classfile_parse_error("PermittedSubclasses attribute in final class file %s", CHECK);
}
parsed_permitted_subclasses_attribute = true;
permitted_subclasses_attribute_start = cfs->current();
permitted_subclasses_attribute_length = attribute_length;
}
cfs->skip_u1(attribute_length, CHECK);

} else if (_major_version >= JAVA_16_VERSION) {
if (tag == vmSymbols::tag_record()) {
// Skip over Record attribute if not supported or if super class is
// not java.lang.Record.
if (supports_records() &&
cp->klass_name_at(_super_class_index) == vmSymbols::java_lang_Record()) {
// Skip over Record attribute if super class is not java.lang.Record.
if (cp->klass_name_at(_super_class_index) == vmSymbols::java_lang_Record()) {
if (parsed_record_attribute) {
classfile_parse_error("Multiple Record attributes in class file %s", THREAD);
return;
@@ -3839,44 +3849,13 @@ void ClassFileParser::parse_classfile_attributes(const ClassFileStream* const cf
record_attribute_start = cfs->current();
record_attribute_length = attribute_length;
} else if (log_is_enabled(Info, class, record)) {
// Log why the Record attribute was ignored. Note that if the
// class file version is JVM_CLASSFILE_MAJOR_VERSION.65535 and
// --enable-preview wasn't specified then a java.lang.UnsupportedClassVersionError
// exception would have been thrown.
ResourceMark rm(THREAD);
if (supports_records()) {
log_info(class, record)(
"Ignoring Record attribute in class %s because super type is not java.lang.Record",
_class_name->as_C_string());
} else {
log_info(class, record)(
"Ignoring Record attribute in class %s because class file version is not %d.65535",
_class_name->as_C_string(), JVM_CLASSFILE_MAJOR_VERSION);
}
}
cfs->skip_u1(attribute_length, CHECK);
} else if (_major_version >= JAVA_15_VERSION) {
// Check for PermittedSubclasses tag
if (tag == vmSymbols::tag_permitted_subclasses()) {
if (supports_sealed_types()) {
if (parsed_permitted_subclasses_attribute) {
classfile_parse_error("Multiple PermittedSubclasses attributes in class file %s", THREAD);
return;
}
// Classes marked ACC_FINAL cannot have a PermittedSubclasses attribute.
if (_access_flags.is_final()) {
classfile_parse_error("PermittedSubclasses attribute in final class file %s", THREAD);
return;
}
parsed_permitted_subclasses_attribute = true;
permitted_subclasses_attribute_start = cfs->current();
permitted_subclasses_attribute_length = attribute_length;
}
cfs->skip_u1(attribute_length, CHECK);
} else {
// Unknown attribute
cfs->skip_u1(attribute_length, CHECK);
}
} else {
// Unknown attribute
cfs->skip_u1(attribute_length, CHECK);
@@ -2342,13 +2342,6 @@ public Method getMethod(String name, Class<?>... parameterTypes)
}

/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This method is associated with <i>records</i>, a preview
* feature of the Java language. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
*
* Returns an array of {@code RecordComponent} objects representing all the
* record components of this record class, or {@code null} if this class is
* not a record class.
@@ -2385,11 +2378,8 @@ public Method getMethod(String name, Class<?>... parameterTypes)
* </ul>
*
* @jls 8.10 Record Types
* @since 14
* @since 16
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
essentialAPI=false)
@SuppressWarnings("preview")
@CallerSensitive
public RecordComponent[] getRecordComponents() {
SecurityManager sm = System.getSecurityManager();
@@ -3688,13 +3678,6 @@ public boolean isEnum() {
}

/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This method is associated with <i>records</i>, a preview
* feature of the Java language. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
*
* Returns {@code true} if and only if this class is a record class.
*
* <p> The {@linkplain #getSuperclass() direct superclass} of a record
@@ -3707,10 +3690,8 @@ public boolean isEnum() {
*
* @return true if and only if this class is a record class, otherwise false
* @jls 8.10 Record Types
* @since 14
* @since 16
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
essentialAPI=false)
public boolean isRecord() {
return getSuperclass() == JAVA_LANG_RECORD_CLASS && isRecord0();
}
@@ -25,14 +25,6 @@
package java.lang;

/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This class is associated with <i>records</i>, a preview
* feature of the Java language. Programs can only use this
* class when preview features are enabled. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
*
* This is the common base class of all Java language record classes.
*
* <p>More information about records, including descriptions of the
@@ -86,10 +78,8 @@
* <a href="{@docRoot}/java.base/java/io/ObjectInputStream.html#record-serialization">record serialization</a>.
*
* @jls 8.10 Record Types
* @since 14
* @since 16
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
essentialAPI=true)
public abstract class Record {
/**
* Constructor for record classes to call.
@@ -118,22 +118,12 @@
MODULE,

/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This constant is associated with <i>records</i>, a preview
* feature of the Java language. Programs can only use this
* constant when preview features are enabled. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
*
* Record component
*
* @jls 8.10.3 Record Members
* @jls 9.7.4 Where Annotations May Appear
*
* @since 14
* @since 16
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
essentialAPI=true)
RECORD_COMPONENT;
}
@@ -38,23 +38,14 @@
import java.util.Objects;

/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This class is associated with <i>records</i>, a preview
* feature of the Java language. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
*
* A {@code RecordComponent} provides information about, and dynamic access to, a
* component of a record class.
*
* @see Class#getRecordComponents()
* @see java.lang.Record
* @jls 8.10 Record Types
* @since 14
* @since 16
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
essentialAPI=false)
public final class RecordComponent implements AnnotatedElement {
// declaring class
private Class<?> clazz;
@@ -38,23 +38,14 @@
import java.util.Objects;

/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This class is associated with <i>records</i>, a preview
* feature of the Java language. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
*
* Bootstrap methods for state-driven implementations of core methods,
* including {@link Object#equals(Object)}, {@link Object#hashCode()}, and
* {@link Object#toString()}. These methods may be used, for example, by
* Java compiler implementations to implement the bodies of {@link Object}
* methods for record classes.
*
* @since 14
* @since 16
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
essentialAPI=false)
public class ObjectMethods {

private ObjectMethods() { }
@@ -92,16 +92,8 @@ public LocationInfo getLocationInfo() {
METHOD_FORMAL_PARAMETER,
THROWS,
/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This enum constant is associated with <i>records</i>, a preview
* feature of the Java language. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
* @since 14
* @since 16
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
essentialAPI=false)
RECORD_COMPONENT;
}

@@ -649,19 +649,9 @@
PROVIDES(ProvidesTree.class),

/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This enum constant is associated with <i>records</i>, a preview
* feature of the Java language. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
*
* Used for instances of {@link ClassTree} representing records.
*
* @since 14
* @since 16
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
essentialAPI=false)
RECORD(ClassTree.class),

/**
@@ -167,7 +167,6 @@ public boolean isEnabled() {
public boolean isPreview(Feature feature) {
if (feature == Feature.PATTERN_MATCHING_IN_INSTANCEOF ||
feature == Feature.REIFIABLE_TYPES_INSTANCEOF ||
feature == Feature.RECORDS ||
feature == Feature.SEALED_CLASSES)
return true;
//Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
@@ -159,8 +159,7 @@ protected Check(Context context) {

deferredLintHandler = DeferredLintHandler.instance(context);

allowRecords = (!preview.isPreview(Feature.RECORDS) || preview.isEnabled()) &&
Feature.RECORDS.allowedInSource(source);
allowRecords = Feature.RECORDS.allowedInSource(source);
allowSealed = (!preview.isPreview(Feature.SEALED_CLASSES) || preview.isEnabled()) &&
Feature.SEALED_CLASSES.allowedInSource(source);
}
@@ -150,8 +150,7 @@ protected Resolve(Context context) {
Feature.POST_APPLICABILITY_VARARGS_ACCESS_CHECK.allowedInSource(source);
polymorphicSignatureScope = WriteableScope.create(syms.noSymbol);
allowModules = Feature.MODULES.allowedInSource(source);
allowRecords = (!preview.isPreview(Feature.RECORDS) || preview.isEnabled()) &&
Feature.RECORDS.allowedInSource(source);
allowRecords = Feature.RECORDS.allowedInSource(source);
}

/** error symbols, which are returned when resolution fails
@@ -272,8 +272,7 @@ protected ClassReader(Context context) {
Source source = Source.instance(context);
preview = Preview.instance(context);
allowModules = Feature.MODULES.allowedInSource(source);
allowRecords = (!preview.isPreview(Feature.RECORDS) || preview.isEnabled()) &&
Feature.RECORDS.allowedInSource(source);
allowRecords = Feature.RECORDS.allowedInSource(source);
allowSealedTypes = (!preview.isPreview(Feature.SEALED_CLASSES) || preview.isEnabled()) &&
Feature.SEALED_CLASSES.allowedInSource(source);

@@ -184,8 +184,7 @@ protected JavacParser(ParserFactory fac,
endPosTable = newEndPosTable(keepEndPositions);
this.allowYieldStatement = (!preview.isPreview(Feature.SWITCH_EXPRESSION) || preview.isEnabled()) &&
Feature.SWITCH_EXPRESSION.allowedInSource(source);
this.allowRecords = (!preview.isPreview(Feature.RECORDS) || preview.isEnabled()) &&
Feature.RECORDS.allowedInSource(source);
this.allowRecords = Feature.RECORDS.allowedInSource(source);
this.allowSealedTypes = (!preview.isPreview(Feature.SEALED_CLASSES) || preview.isEnabled()) &&
Feature.SEALED_CLASSES.allowedInSource(source);
}
@@ -3717,7 +3716,7 @@ protected JCStatement classOrRecordOrInterfaceOrEnumDeclaration(JCModifiers mods
} else {
int pos = token.pos;
List<JCTree> errs;
if (token.kind == IDENTIFIER && token.name() == names.record && preview.isEnabled()) {
if (token.kind == IDENTIFIER && token.name() == names.record) {
checkSourceLevel(Feature.RECORDS);
JCErroneous erroneousTree = syntaxError(token.pos, List.of(mods), Errors.RecordHeaderExpected);
return toP(F.Exec(erroneousTree));
@@ -4214,7 +4213,7 @@ protected boolean isRecordStart() {
(peekToken(TokenKind.IDENTIFIER, TokenKind.LPAREN) ||
peekToken(TokenKind.IDENTIFIER, TokenKind.EOF) ||
peekToken(TokenKind.IDENTIFIER, TokenKind.LT))) {
checkSourceLevel(Feature.RECORDS);
checkSourceLevel(Feature.RECORDS);
return true;
} else {
return false;
@@ -23,15 +23,15 @@

/*
* @test
* @bug 8246774
* @library /test/lib
* @summary Test that a class that is a record can be redefined.
* @modules java.base/jdk.internal.misc
* @modules java.instrument
* jdk.jartool/sun.tools.jar
* @requires vm.jvmti
* @compile --enable-preview -source ${jdk.version} RedefineRecord.java
* @run main/othervm --enable-preview RedefineRecord buildagent
* @run main/othervm/timeout=6000 --enable-preview RedefineRecord runtest
* @run main RedefineRecord buildagent
* @run main/othervm/timeout=6000 RedefineRecord runtest
*/

import java.io.FileNotFoundException;
@@ -100,7 +100,6 @@ public static void main(String argv[]) throws Exception {
"-XX:MetaspaceSize=12m",
"-XX:MaxMetaspaceSize=12m",
"-javaagent:redefineagent.jar",
"--enable-preview",
"RedefineRecord");
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldNotContain("processing of -javaagent failed");
@@ -28,7 +28,7 @@
// ClassFormatError exception.
class abstractRecord {
0xCAFEBABE;
65535; // minor version
0; // minor version
60; // version
[69] { // Constant Pool
; // first element is empty
@@ -28,7 +28,7 @@
// Utf8. It should result in a ClassFormatError exception.
class badRecordAttribute {
0xCAFEBABE;
65535; // minor version
0; // minor version
60; // version
[69] { // Constant Pool
; // first element is empty
ProTip! Use n and p to navigate between commits in a pull request.