changeset 57029:e9de706a3aae records-and-sealed

delta applying: allow records to implement serialization methods, stop generating the readResolve method, until all the support for record serialization has been pushed
author vromero
date Thu, 22 Aug 2019 10:59:08 -0400
parents 238e9ac74503
children 8ce09d289118 94d968db1049
files src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java test/langtools/tools/javac/records/RecordCompilationTests.java test/langtools/tools/javac/records/mandated_members/read_resolve_method/CheckReadResolveMethodTest.java
diffstat 4 files changed, 176 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java	Thu Aug 22 09:41:03 2019 -0400
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java	Thu Aug 22 10:59:08 2019 -0400
@@ -2502,7 +2502,8 @@
                 generateRecordMethod(tree, names.toString, vars, getterMethHandles),
                 generateRecordMethod(tree, names.hashCode, vars, getterMethHandles),
                 generateRecordMethod(tree, names.equals, vars, getterMethHandles),
-                recordExtractor(tree, getterMethHandlesForExtractor)
+                recordExtractor(tree, getterMethHandlesForExtractor),
+                recordReadResolve(tree)
         ));
         findUserDefinedAccessors(tree);
     }
@@ -2600,6 +2601,23 @@
         return make.MethodDef(extractorSym, make.Block(0, List.of(make.Return(ident))));
     }
 
+    JCTree recordReadResolve(JCClassDecl tree) {
+        make_at(tree.pos());
+        Symbol msym = findMethodOrFailSilently(
+                tree.pos(),
+                attrEnv,
+                tree.sym.type,
+                names.readResolve,
+                List.nil(),
+                List.nil());
+        if (!msym.kind.isResolutionError() && (msym.flags() & RECORD) != 0) {
+            List<JCExpression> args = TreeInfo.recordFields(tree).map(vd -> make.Ident(vd));
+            return make.MethodDef((MethodSymbol)msym, make.Block(0, List.of(make.Return(makeNewClass(tree.sym.type, args)))));
+        } else {
+            return make.Block(SYNTHETIC, List.nil());
+        }
+    }
+
     private String argsTypeSig(List<Type> typeList) {
         LowerSignatureGenerator sg = new LowerSignatureGenerator();
         sg.assembleSig(typeList);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java	Thu Aug 22 09:41:03 2019 -0400
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java	Thu Aug 22 10:59:08 2019 -0400
@@ -1058,6 +1058,9 @@
             List<JCTree> defsToEnter = isRecord ?
                     tree.defs.diff(List.convert(JCTree.class, TreeInfo.recordFields(tree))) : tree.defs;
             memberEnter.memberEnter(defsToEnter, env);
+            if (isRecord) {
+                checkForSerializationMembers(tree, env);
+            }
             List<JCTree> defsBeforeAddingNewMembers = tree.defs;
             if (isRecord) {
                 addRecordMembersIfNeeded(tree, env, defaultConstructorGenerated);
@@ -1210,6 +1213,54 @@
             memberEnter.memberEnter(valueOf, env);
         }
 
+        private void checkForSerializationMembers(JCClassDecl tree, Env<AttrContext> env) {
+            // non-static void writeObject(java.io.ObjectOutputStream) {}
+            MethodSymbol ms = lookupMethod(tree.sym, names.writeObject, List.of(syms.objectOutputStreamType));
+            if (ms != null) {
+                errorOnSerializationMember(tree, names.writeObject, ms, syms.voidType, false);
+            }
+            // non-static Object writeReplace() {}
+            ms = lookupMethod(tree.sym, names.writeReplace, List.nil());
+            if (ms != null) {
+                errorOnSerializationMember(tree, names.writeReplace, ms, syms.objectType, false);
+            }
+            // non-static Object readResolve() {}
+            ms = lookupMethod(tree.sym, names.readResolve, List.nil());
+            if (ms != null) {
+                errorOnSerializationMember(tree, names.readResolve, ms, syms.objectType, false);
+            }
+            // non-static void readObjectNoData() {}
+            ms = lookupMethod(tree.sym, names.readObjectNoData, List.nil());
+            if (ms != null) {
+                errorOnSerializationMember(tree, names.readObjectNoData, ms, syms.voidType, false);
+            }
+            // non-static void readObject(java.io.ObjectInputStream stream) {}
+            ms = lookupMethod(tree.sym, names.readObject, List.of(syms.objectInputStreamType));
+            if (ms != null) {
+                errorOnSerializationMember(tree, names.readObject, ms, syms.voidType, false);
+            }
+            Type objectStreamFieldArr = new ArrayType(syms.objectStreamFieldType, syms.arrayClass);
+            Symbol fieldSym = lookupField(tree.sym, names.serialPersistentFields, objectStreamFieldArr);
+            if (fieldSym != null) {
+                errorOnSerializationMember(tree, names.serialPersistentFields, fieldSym, objectStreamFieldArr, true);
+            }
+        }
+
+        private void errorOnSerializationMember(JCClassDecl tree,
+                                                Name name, Symbol sym, Type expectedType, boolean shouldBeStatic) {
+            Type typeOrReturnType = sym.kind == MTH ? sym.type.asMethodType().getReturnType() : sym.type;
+            if (sym.isStatic() == shouldBeStatic && (typeOrReturnType == expectedType || types.isSameType(typeOrReturnType, expectedType))) {
+                for (JCTree def : tree.defs) {
+                    Symbol sym2 = TreeInfo.symbolFor(def);
+                    if (sym2 == sym) {
+                        log.error(def, Errors.IllegalRecordMember(name));
+                        return;
+                    }
+                }
+                log.error(tree, Errors.IllegalRecordMember(name));
+            }
+        }
+
         /** Add the implicit members for a record
          *  to the symbol table.
          */
--- a/test/langtools/tools/javac/records/RecordCompilationTests.java	Thu Aug 22 09:41:03 2019 -0400
+++ b/test/langtools/tools/javac/records/RecordCompilationTests.java	Thu Aug 22 10:59:08 2019 -0400
@@ -325,6 +325,18 @@
         // TODO: OK to redeclare with or without same annos
     }
 
+    public void testIllegalSerializationMembers() {
+        // @@@ Should fail
+        String template = "record R(int x) { # }";
+        for (String s : List.of("private static final java.io.ObjectStreamField[] serialPersistentFields = {};",
+                                "private void writeObject(java.io.ObjectOutputStream stream) { }",
+                                "private Object writeReplace() { }",
+                                "private Object readResolve() { }",
+                                "private void readObject(java.io.ObjectInputStream stream) { }",
+                                "private void readObjectNoData() { }"))
+            assertFail("compiler.err.illegal.record.member", template, s);
+    }
+
     public void testLocalRecords() {
         assertOK("class R { \n" +
                 "    void m() { \n" +
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/records/mandated_members/read_resolve_method/CheckReadResolveMethodTest.java	Thu Aug 22 10:59:08 2019 -0400
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary check that members of abtract datum has protected access
+ * @ignore currently the compiler is generating a readResolve method but this test is failing now
+ * as the compiler will error if there is an explicit declaration of readResolve.
+ * @modules jdk.jdeps/com.sun.tools.classfile
+ *          jdk.compiler/com.sun.tools.javac.util
+ * @compile CheckReadResolveMethodTest.java
+ * @run main CheckReadResolveMethodTest
+ */
+
+import java.io.*;
+
+import com.sun.tools.classfile.*;
+
+public class CheckReadResolveMethodTest {
+
+    // readResolve should be generated as a constructor is provided
+    record Point1(int i, int j) implements Serializable {
+        public Point1(int i, int j) {
+            this.i = i;
+            this.j = j;
+        }
+    }
+
+    // readResolve should be generated the record implements Serializable
+    record Point2(int i, int j) implements Serializable {}
+
+    // no readResolve should be generated as the record doesnt implement Serializable
+    record Point3(int i, int j) {}
+
+    // no readResolve should be generated as an implementation is already provided
+    record Point4(int i, int j) {
+        private Object readResolve() {
+            return new Point4(i, j);
+        }
+    }
+
+    // no readResolve should be generated as an implementation of readObject is already provided
+    record Point5(int i, int j) {
+        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {}
+    }
+
+    public static void main(String args[]) throws Throwable {
+        new CheckReadResolveMethodTest().run();
+    }
+
+    void run() throws Throwable {
+        checkReadResolveMethod("Point1", true);
+        checkReadResolveMethod("Point2", true);
+        checkReadResolveMethod("Point3", false);
+        checkReadResolveMethod("Point4", true);
+        checkReadResolveMethod("Point5", false);
+    }
+
+    void checkReadResolveMethod(String className, boolean shouldBeThere) throws Throwable {
+        File testClasses = new File(System.getProperty("test.classes"));
+        File file = new File(testClasses,
+                CheckReadResolveMethodTest.class.getName() + "$" + className +".class");
+        ClassFile classFile = ClassFile.read(file);
+        boolean found = false;
+        for (Method f : classFile.methods) {
+            if (f.getName(classFile.constant_pool).equals("readResolve")) {
+                found = true;
+            }
+        }
+        if (found != shouldBeThere) {
+            throw new AssertionError("incorrect generation of readResolve method at class " + className);
+        }
+    }
+}