From 7ec059ad32d025241df8312b454214eb3a4937c3 Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Fri, 21 May 2021 17:13:16 -0400 Subject: [PATCH] 8258535: TypeElement#getRecordComponents no components for records from a different jar --- .../com/sun/tools/javac/jvm/ClassReader.java | 15 +- ...heckingAccessorsOnLoadedRecordClasses.java | 209 ++++++++++++++++++ 2 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 test/langtools/tools/javac/processing/model/element/CheckingAccessorsOnLoadedRecordClasses.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java index 1e7d8d5ecdc12..d3ef9e7c1b12f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java @@ -2547,10 +2547,23 @@ void readClass(ClassSymbol c) { for (int i = 0; i < fieldCount; i++) enterMember(c, readField()); Assert.check(methodCount == nextChar()); for (int i = 0; i < methodCount; i++) enterMember(c, readMethod()); - + if (c.isRecord()) { + for (RecordComponent rc: c.getRecordComponents()) { + rc.accessor = lookupMethod(c, rc.name, List.nil()); + } + } typevars = typevars.leave(); } + private MethodSymbol lookupMethod(TypeSymbol tsym, Name name, List argtypes) { + for (Symbol s : tsym.members().getSymbolsByName(name, s -> s.kind == MTH)) { + if (types.isSameTypes(s.type.getParameterTypes(), argtypes)) { + return (MethodSymbol) s; + } + } + return null; + } + /** Read inner class info. For each inner/outer pair allocate a * member class. */ diff --git a/test/langtools/tools/javac/processing/model/element/CheckingAccessorsOnLoadedRecordClasses.java b/test/langtools/tools/javac/processing/model/element/CheckingAccessorsOnLoadedRecordClasses.java new file mode 100644 index 0000000000000..914852f758d77 --- /dev/null +++ b/test/langtools/tools/javac/processing/model/element/CheckingAccessorsOnLoadedRecordClasses.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2021, 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 Verify that annotation processing works for records + * @library /tools/lib /tools/javac/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.code + * jdk.compiler/com.sun.tools.javac.util + * @build toolbox.ToolBox toolbox.JavacTask + * @build JavacTestingAbstractProcessor + * @compile CheckingAccessorsOnLoadedRecordClasses.java + * @run main/othervm CheckingAccessorsOnLoadedRecordClasses + */ + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.RecordComponentElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.ElementScanner14; +import javax.tools.Diagnostic.Kind; +import javax.tools.*; + +import java.lang.annotation.*; +import java.util.*; +import javax.annotation.processing.*; +import javax.lang.model.element.*; +import javax.lang.model.type.*; +import javax.tools.Diagnostic.Kind; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.sun.tools.javac.util.Assert; + +import toolbox.JavacTask; +import toolbox.Task; +import toolbox.Task.Mode; +import toolbox.Task.OutputKind; +import toolbox.TestRunner; +import toolbox.ToolBox; + +public class CheckingAccessorsOnLoadedRecordClasses extends TestRunner { + protected ToolBox tb; + + CheckingAccessorsOnLoadedRecordClasses() { + super(System.err); + tb = new ToolBox(); + } + + public static void main(String... args) throws Exception { + new CheckingAccessorsOnLoadedRecordClasses().runTests(); + } + + protected void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + Path[] findJavaFiles(Path... paths) throws IOException { + return tb.findJavaFiles(paths); + } + + static final String RecordSrc = + """ + package pkg1; + import java.util.List; + public record R(List data) {} + """; + + static final String ISrc = + """ + package pkg2; + import pkg1.R; + + @FunctionalInterface + public interface I { + void foo(R r); + } + """; + + @Test + public void testAnnoProcessing(Path base) throws Exception { + Path src = base.resolve("src"); + Path out = base.resolve("out"); + Files.createDirectories(out); + Path pkg1 = src.resolve("pkg1"); + + tb.writeJavaFiles(src, RecordSrc); + // lets first compile the record + new JavacTask(tb) + .files(findJavaFiles(pkg1)) + .outdir(out) + .run(); + + Path pkg2 = src.resolve("pkg2"); + tb.writeJavaFiles(src, ISrc); + /* now the annotated interface which uses the record, given that the record class + * is now in the classpath, we will force jvm.ClassReader to load it and set the + * accessors correctly + */ + new JavacTask(tb, Mode.API) + .options("-nowarn", "-processor", Processor.class.getName()) + .classpath(out) + .files(findJavaFiles(pkg2)) + .outdir(out) + .run(); + } + + /** This processor will look for records in the arguments of the methods annotated with any + * annotation for a given source. Then it will check that those records have at least one + * record component and that the accessor associated with it is not null and that it has the + * same name as its corresponding record component + */ + @SupportedAnnotationTypes("*") + public static final class Processor extends JavacTestingAbstractProcessor { + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (!roundEnv.processingOver()) { + for (TypeElement annotation : annotations) { + Set annotatedElems = roundEnv.getElementsAnnotatedWith(annotation); + for (Element annotatedElement : annotatedElems) { + TypeElement typeElement = (TypeElement) annotatedElement; + + for (Element enclosedElement : typeElement.getEnclosedElements()) { + if (enclosedElement.getKind() == ElementKind.METHOD) { + validateMethod((ExecutableElement) enclosedElement, roundEnv); + } + } + } + } + } + return false; + } + + protected void validateMethod(ExecutableElement method, RoundEnvironment roundEnv) { + for (VariableElement parameter : method.getParameters()) { + TypeMirror parameterType = parameter.asType(); + if (parameterType.getKind() == TypeKind.DECLARED) { + Element parameterElement = ((DeclaredType) parameterType).asElement(); + if (parameterElement.getKind() == ElementKind.RECORD) { + validateRecord((TypeElement) parameterElement, roundEnv); + } + } + } + } + + protected void validateRecord(TypeElement recordElement, RoundEnvironment roundEnv) { + List recordComponents = recordElement.getRecordComponents(); + + if (recordComponents.isEmpty()) { + processingEnv.getMessager() + .printMessage(Diagnostic.Kind.ERROR, "Record element " + recordElement.getQualifiedName() + + " has no record components"); + } else { + for (RecordComponentElement recordComponent : recordComponents) { + ExecutableElement accessor = recordComponent.getAccessor(); + if (accessor == null) { + processingEnv.getMessager() + .printMessage(Diagnostic.Kind.ERROR, + "Record component " + recordComponent.getSimpleName() + " from record " + recordElement + .getQualifiedName() + " has no accessor"); + } + if (!accessor.getSimpleName().equals(recordComponent.getSimpleName())) { + processingEnv.getMessager() + .printMessage(Diagnostic.Kind.ERROR, + "Record component " + recordComponent.getSimpleName() + " from record " + + recordElement.getQualifiedName() + " has an accessor with name " + accessor.getSimpleName()); + } + } + } + } + } +}