8236210: javac generates wrong annotation for fields generated from record components

Reviewed-by: mcimadamore
This commit is contained in:
Vicente Romero 2020-01-23 19:20:11 -05:00
parent 9e4830fc30
commit 0f98701e87
5 changed files with 505 additions and 64 deletions

View File

@ -29,6 +29,7 @@ import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
@ -58,6 +59,7 @@ import com.sun.tools.javac.comp.Env;
import com.sun.tools.javac.jvm.*;
import com.sun.tools.javac.jvm.PoolConstant;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.Tag;
@ -1274,6 +1276,9 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
/** the annotation metadata attached to this class */
private AnnotationTypeMetadata annotationTypeMetadata;
/* the list of any of record components, only non empty if the class is a record
* and it has at least one record component
*/
private List<RecordComponent> recordComponents = List.nil();
public ClassSymbol(long flags, Name name, Type type, Symbol owner) {
@ -1471,15 +1476,24 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
return Flags.asModifierSet(flags & ~DEFAULT);
}
public RecordComponent getRecordComponent(VarSymbol field, boolean addIfMissing) {
public RecordComponent getRecordComponent(VarSymbol field) {
for (RecordComponent rc : recordComponents) {
if (rc.name == field.name) {
return rc;
}
}
return null;
}
public RecordComponent getRecordComponent(JCVariableDecl var, boolean addIfMissing) {
for (RecordComponent rc : recordComponents) {
if (rc.name == var.name) {
return rc;
}
}
RecordComponent rc = null;
if (addIfMissing) {
recordComponents = recordComponents.append(rc = new RecordComponent(PUBLIC, field.name, field.type, field.owner));
recordComponents = recordComponents.append(rc = new RecordComponent(var));
}
return rc;
}
@ -1735,14 +1749,18 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
public static class RecordComponent extends VarSymbol implements RecordComponentElement {
public MethodSymbol accessor;
public JCTree.JCMethodDecl accessorMeth;
private final List<JCAnnotation> originalAnnos;
/**
* Construct a record component, given its flags, name, type and owner.
*/
public RecordComponent(long flags, Name name, Type type, Symbol owner) {
super(flags, name, type, owner);
public RecordComponent(JCVariableDecl fieldDecl) {
super(PUBLIC, fieldDecl.sym.name, fieldDecl.sym.type, fieldDecl.sym.owner);
this.originalAnnos = fieldDecl.mods.annotations;
}
public List<JCAnnotation> getOriginalAnnos() { return originalAnnos; }
@Override @DefinedBy(Api.LANGUAGE_MODEL)
@SuppressWarnings("preview")
public ElementKind getKind() {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2020, 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
@ -267,25 +267,9 @@ public class SymbolMetadata {
return lb.toList();
}
private List<Attribute.TypeCompound> removeFromTypeCompoundList(List<Attribute.TypeCompound> l, Attribute.TypeCompound compound) {
ListBuffer<Attribute.TypeCompound> lb = new ListBuffer<>();
for (Attribute.TypeCompound c : l) {
if (c != compound) {
lb.add(c);
}
}
return lb.toList();
}
public void remove(Attribute.Compound compound) {
public void removeDeclarationMetadata(Attribute.Compound compound) {
if (attributes.contains(compound)) {
attributes = removeFromCompoundList(attributes, compound);
} else if (type_attributes.contains(compound)) {
type_attributes = removeFromTypeCompoundList(type_attributes, (TypeCompound)compound);
} else if (init_type_attributes.contains(compound)) {
init_type_attributes = removeFromTypeCompoundList(init_type_attributes, (TypeCompound)compound);
} else if (clinit_type_attributes.contains(compound)) {
clinit_type_attributes = removeFromTypeCompoundList(clinit_type_attributes, (TypeCompound)compound);
} else {
// slow path, it could be that attributes list contains annotation containers, so we have to dig deeper
for (Attribute.Compound attrCompound : attributes) {

View File

@ -2908,7 +2908,7 @@ public class Check {
* the corresponding record component
*/
ClassSymbol recordClass = (ClassSymbol) s.owner;
RecordComponent rc = recordClass.getRecordComponent((VarSymbol)s, false);
RecordComponent rc = recordClass.getRecordComponent((VarSymbol)s);
SymbolMetadata metadata = rc.getMetadata();
if (metadata == null || metadata.isEmpty()) {
/* if not is empty then we have already been here, which is the case if multiple annotations are applied
@ -2917,34 +2917,93 @@ public class Check {
rc.appendAttributes(s.getRawAttributes().stream().filter(anno ->
Arrays.stream(getTargetNames(anno.type.tsym)).anyMatch(name -> name == names.RECORD_COMPONENT)
).collect(List.collector()));
rc.appendUniqueTypeAttributes(s.getRawTypeAttributes());
rc.setTypeAttributes(s.getRawTypeAttributes());
// to get all the type annotations applied to the type
rc.type = s.type;
}
}
}
if (a.type.tsym.isAnnotationType() && !annotationApplicable(a, s)) {
if (isRecordMember && (s.flags_field & Flags.GENERATED_MEMBER) != 0) {
/* so we have found an annotation that is not applicable to a record member that was generated by the
* compiler. This was intentionally done at TypeEnter, now is the moment strip away the annotations
* that are not applicable to the given record member
*/
JCModifiers modifiers = TreeInfo.getModifiers(declarationTree);
// lets first remove the annotation from the modifier
if (modifiers != null) {
ListBuffer<JCAnnotation> newAnnotations = new ListBuffer<>();
for (JCAnnotation anno : modifiers.annotations) {
if (anno != a) {
newAnnotations.add(anno);
/* the section below is tricky. Annotations applied to record components are propagated to the corresponding
* record member so if an annotation has target: FIELD, it is propagated to the corresponding FIELD, if it has
* target METHOD, it is propagated to the accessor and so on. But at the moment when method members are generated
* there is no enough information to propagate only the right annotations. So all the annotations are propagated
* to all the possible locations.
*
* At this point we need to remove all the annotations that are not in place before going on with the annotation
* party. On top of the above there is the issue that there is no AST representing record components, just symbols
* so the corresponding field has been holding all the annotations and it's metadata has been modified as if it
* was both a field and a record component.
*
* So there are two places where we need to trim annotations from: the metadata of the symbol and / or the modifiers
* in the AST. Whatever is in the metadata will be written to the class file, whatever is in the modifiers could
* be see by annotation processors.
*
* The metadata contains both type annotations and declaration annotations. At this point of the game we don't
* need to care about type annotations, they are all in the right place. But we could need to remove declaration
* annotations. So for declaration annotations if they are not applicable to the record member, excluding type
* annotations which are already correct, then we will remove it. For the AST modifiers if the annotation is not
* applicable either as type annotation and or declaration annotation, only in that case it will be removed.
*
* So it could be that annotation is removed as a declaration annotation but it is kept in the AST modifier for
* further inspection by annotation processors.
*
* For example:
*
* import java.lang.annotation.*;
*
* @Target({ElementType.TYPE_USE, ElementType.RECORD_COMPONENT})
* @Retention(RetentionPolicy.RUNTIME)
* @interface Anno { }
*
* record R(@Anno String s) {}
*
* at this point we will have for the case of the generated field:
* - @Anno in the modifier
* - @Anno as a type annotation
* - @Anno as a declaration annotation
*
* the last one should be removed because the annotation has not FIELD as target but it was applied as a
* declaration annotation because the field was being treated both as a field and as a record component
* as we have already copied the annotations to the record component, now the field doesn't need to hold
* annotations that are not intended for it anymore. Still @Anno has to be kept in the AST's modifiers as it
* is applicable as a type annotation to the type of the field.
*/
if (a.type.tsym.isAnnotationType()) {
Optional<Set<Name>> applicableTargetsOp = getApplicableTargets(a, s);
if (!applicableTargetsOp.isEmpty()) {
Set<Name> applicableTargets = applicableTargetsOp.get();
boolean notApplicableOrIsTypeUseOnly = applicableTargets.isEmpty() ||
applicableTargets.size() == 1 && applicableTargets.contains(names.TYPE_USE);
boolean isRecordMemberWithNonApplicableDeclAnno =
isRecordMember && (s.flags_field & Flags.GENERATED_MEMBER) != 0 && notApplicableOrIsTypeUseOnly;
if (applicableTargets.isEmpty() || isRecordMemberWithNonApplicableDeclAnno) {
if (isRecordMemberWithNonApplicableDeclAnno) {
/* so we have found an annotation that is not applicable to a record member that was generated by the
* compiler. This was intentionally done at TypeEnter, now is the moment strip away the annotations
* that are not applicable to the given record member
*/
JCModifiers modifiers = TreeInfo.getModifiers(declarationTree);
/* lets first remove the annotation from the modifier if it is not applicable, we have to check again as
* it could be a type annotation
*/
if (modifiers != null && applicableTargets.isEmpty()) {
ListBuffer<JCAnnotation> newAnnotations = new ListBuffer<>();
for (JCAnnotation anno : modifiers.annotations) {
if (anno != a) {
newAnnotations.add(anno);
}
}
modifiers.annotations = newAnnotations.toList();
}
// now lets remove it from the symbol
s.getMetadata().removeDeclarationMetadata(a.attribute);
} else {
log.error(a.pos(), Errors.AnnotationTypeNotApplicable);
}
modifiers.annotations = newAnnotations.toList();
}
// now lets remove it from the symbol
s.getMetadata().remove(a.attribute);
} else {
log.error(a.pos(), Errors.AnnotationTypeNotApplicable);
}
}
@ -3221,10 +3280,20 @@ public class Check {
return targets;
}
@SuppressWarnings("preview")
boolean annotationApplicable(JCAnnotation a, Symbol s) {
Optional<Set<Name>> targets = getApplicableTargets(a, s);
/* the optional could be emtpy if the annotation is unknown in that case
* we return that it is applicable and if it is erroneous that should imply
* an error at the declaration site
*/
return targets.isEmpty() || targets.isPresent() && !targets.get().isEmpty();
}
@SuppressWarnings("preview")
Optional<Set<Name>> getApplicableTargets(JCAnnotation a, Symbol s) {
Attribute.Array arr = getAttributeTargetAttribute(a.annotationType.type.tsym);
Name[] targets;
Set<Name> applicableTargets = new HashSet<>();
if (arr == null) {
targets = defaultTargetMetaInfo();
@ -3234,7 +3303,8 @@ public class Check {
for (int i=0; i<arr.values.length; ++i) {
Attribute app = arr.values[i];
if (!(app instanceof Attribute.Enum)) {
return true; // recovery
// recovery
return Optional.empty();
}
Attribute.Enum e = (Attribute.Enum) app;
targets[i] = e.value.name;
@ -3243,55 +3313,55 @@ public class Check {
for (Name target : targets) {
if (target == names.TYPE) {
if (s.kind == TYP)
return true;
applicableTargets.add(names.TYPE);
} else if (target == names.FIELD) {
if (s.kind == VAR && s.owner.kind != MTH)
return true;
applicableTargets.add(names.FIELD);
} else if (target == names.RECORD_COMPONENT) {
if (s.getKind() == ElementKind.RECORD_COMPONENT) {
return true;
applicableTargets.add(names.RECORD_COMPONENT);
}
} else if (target == names.METHOD) {
if (s.kind == MTH && !s.isConstructor())
return true;
applicableTargets.add(names.METHOD);
} else if (target == names.PARAMETER) {
if (s.kind == VAR &&
(s.owner.kind == MTH && (s.flags() & PARAMETER) != 0)) {
return true;
applicableTargets.add(names.PARAMETER);
}
} else if (target == names.CONSTRUCTOR) {
if (s.kind == MTH && s.isConstructor())
return true;
applicableTargets.add(names.CONSTRUCTOR);
} else if (target == names.LOCAL_VARIABLE) {
if (s.kind == VAR && s.owner.kind == MTH &&
(s.flags() & PARAMETER) == 0) {
return true;
applicableTargets.add(names.LOCAL_VARIABLE);
}
} else if (target == names.ANNOTATION_TYPE) {
if (s.kind == TYP && (s.flags() & ANNOTATION) != 0) {
return true;
applicableTargets.add(names.ANNOTATION_TYPE);
}
} else if (target == names.PACKAGE) {
if (s.kind == PCK)
return true;
applicableTargets.add(names.PACKAGE);
} else if (target == names.TYPE_USE) {
if (s.kind == VAR && s.owner.kind == MTH && s.type.hasTag(NONE)) {
//cannot type annotate implicitly typed locals
return false;
continue;
} else if (s.kind == TYP || s.kind == VAR ||
(s.kind == MTH && !s.isConstructor() &&
!s.type.getReturnType().hasTag(VOID)) ||
(s.kind == MTH && s.isConstructor())) {
return true;
applicableTargets.add(names.TYPE_USE);
}
} else if (target == names.TYPE_PARAMETER) {
if (s.kind == TYP && s.type.hasTag(TYPEVAR))
return true;
applicableTargets.add(names.TYPE_PARAMETER);
} else
return true; // Unknown ElementType. This should be an error at declaration site,
// assume applicable.
return Optional.empty(); // Unknown ElementType. This should be an error at declaration site,
// assume applicable.
}
return false;
return Optional.of(applicableTargets);
}
Attribute.Array getAttributeTargetAttribute(TypeSymbol s) {

View File

@ -918,7 +918,7 @@ public class TypeEnter implements Completer {
List<JCVariableDecl> fields = TreeInfo.recordFields(tree);
memberEnter.memberEnter(fields, env);
for (JCVariableDecl field : fields) {
sym.getRecordComponent(field.sym, true);
sym.getRecordComponent(field, true);
}
enterThisAndSuper(sym, env);
@ -1034,15 +1034,16 @@ public class TypeEnter implements Completer {
private void addAccessor(JCVariableDecl tree, Env<AttrContext> env) {
MethodSymbol implSym = lookupMethod(env.enclClass.sym, tree.sym.name, List.nil());
RecordComponent rec = ((ClassSymbol) tree.sym.owner).getRecordComponent(tree.sym, false);
RecordComponent rec = ((ClassSymbol) tree.sym.owner).getRecordComponent(tree.sym);
if (implSym == null || (implSym.flags_field & GENERATED_MEMBER) != 0) {
/* here we are pushing the annotations present in the corresponding field down to the accessor
* it could be that some of those annotations are not applicable to the accessor, they will be striped
* away later at Check::validateAnnotation
*/
List<JCAnnotation> originalAnnos = rec.getOriginalAnnos();
JCMethodDecl getter = make.at(tree.pos).
MethodDef(
make.Modifiers(Flags.PUBLIC | Flags.GENERATED_MEMBER, tree.mods.annotations),
make.Modifiers(Flags.PUBLIC | Flags.GENERATED_MEMBER, originalAnnos),
tree.sym.name,
/* we need to special case for the case when the user declared the type as an ident
* if we don't do that then we can have issues if type annotations are applied to the
@ -1349,7 +1350,8 @@ public class TypeEnter implements Completer {
/* at this point we are passing all the annotations in the field to the corresponding
* parameter in the constructor.
*/
arg.mods.annotations = tmpRecordFieldDecls.head.mods.annotations;
RecordComponent rc = ((ClassSymbol) owner).getRecordComponent(arg.sym);
arg.mods.annotations = rc.getOriginalAnnos();
arg.vartype = tmpRecordFieldDecls.head.vartype;
tmpRecordFieldDecls = tmpRecordFieldDecls.tail;
}

View File

@ -28,10 +28,12 @@
*
* @test
* @summary Negative compilation tests, and positive compilation (smoke) tests for records
* @library /lib/combo
* @library /lib/combo /tools/lib /tools/javac/lib
* @modules
* jdk.compiler/com.sun.tools.javac.code
* jdk.compiler/com.sun.tools.javac.util
* jdk.jdeps/com.sun.tools.classfile
* @build JavacTestingAbstractProcessor
* @compile --enable-preview -source ${jdk.version} RecordCompilationTests.java
* @run testng/othervm --enable-preview RecordCompilationTests
*/
@ -39,17 +41,54 @@
import java.io.File;
import java.lang.annotation.ElementType;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.sun.tools.javac.util.Assert;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeMirror;
import com.sun.tools.classfile.Annotation;
import com.sun.tools.classfile.Attribute;
import com.sun.tools.classfile.Attributes;
import com.sun.tools.classfile.ClassFile;
import com.sun.tools.classfile.ConstantPool;
import com.sun.tools.classfile.ConstantPool.CPInfo;
import com.sun.tools.classfile.Field;
import com.sun.tools.classfile.Method;
import com.sun.tools.classfile.Record_attribute;
import com.sun.tools.classfile.Record_attribute.ComponentInfo;
import com.sun.tools.classfile.RuntimeAnnotations_attribute;
import com.sun.tools.classfile.RuntimeTypeAnnotations_attribute;
import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute;
import com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute;
import com.sun.tools.classfile.RuntimeVisibleTypeAnnotations_attribute;
import com.sun.tools.classfile.TypeAnnotation;
import com.sun.tools.javac.code.Attribute.TypeCompound;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import org.testng.annotations.Test;
import tools.javac.combo.CompilationTestCase;
@ -585,4 +624,332 @@ public class RecordCompilationTests extends CompilationTestCase {
}
Assert.check(numberOfFieldRefs == 1);
}
public void testAnnos() throws Exception {
String srcTemplate =
"""
import java.lang.annotation.*;
@Target({#TARGET})
@Retention(RetentionPolicy.RUNTIME)
@interface Anno { }
record R(@Anno String s) {}
""";
// testing several combinations, adding even more combinations won't add too much value
List<String> annoApplicableTargets = List.of(
"ElementType.FIELD",
"ElementType.METHOD",
"ElementType.PARAMETER",
"ElementType.RECORD_COMPONENT",
"ElementType.TYPE_USE",
"ElementType.TYPE_USE,ElementType.FIELD",
"ElementType.TYPE_USE,ElementType.METHOD",
"ElementType.TYPE_USE,ElementType.PARAMETER",
"ElementType.TYPE_USE,ElementType.RECORD_COMPONENT",
"ElementType.TYPE_USE,ElementType.FIELD,ElementType.METHOD",
"ElementType.TYPE_USE,ElementType.FIELD,ElementType.PARAMETER",
"ElementType.TYPE_USE,ElementType.FIELD,ElementType.RECORD_COMPONENT",
"ElementType.FIELD,ElementType.TYPE_USE",
"ElementType.METHOD,ElementType.TYPE_USE",
"ElementType.PARAMETER,ElementType.TYPE_USE",
"ElementType.RECORD_COMPONENT,ElementType.TYPE_USE",
"ElementType.FIELD,ElementType.METHOD,ElementType.TYPE_USE",
"ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE_USE",
"ElementType.FIELD,ElementType.RECORD_COMPONENT,ElementType.TYPE_USE"
);
String[] generalOptions = {
"--enable-preview",
"-source", Integer.toString(Runtime.version().feature()),
"-processor", Processor.class.getName(),
"-Atargets="
};
for (String target : annoApplicableTargets) {
String code = srcTemplate.replaceFirst("#TARGET", target);
String[] testOptions = generalOptions.clone();
testOptions[testOptions.length - 1] = testOptions[testOptions.length - 1] + target;
setCompileOptions(testOptions);
File dir = assertOK(true, code);
ClassFile classFile = ClassFile.read(findClassFileOrFail(dir, "R.class"));
// field first
Assert.check(classFile.fields.length == 1);
Field field = classFile.fields[0];
/* if FIELD is one of the targets then there must be a declaration annotation applied to the field, apart from
* the type annotation
*/
if (target.contains("FIELD")) {
checkAnno(classFile,
(RuntimeAnnotations_attribute)findAttributeOrFail(
field.attributes,
RuntimeVisibleAnnotations_attribute.class),
"Anno");
} else {
assertAttributeNotPresent(field.attributes, RuntimeVisibleAnnotations_attribute.class);
}
// lets check now for the type annotation
if (target.contains("TYPE_USE")) {
checkTypeAnno(
classFile,
(RuntimeVisibleTypeAnnotations_attribute)findAttributeOrFail(field.attributes, RuntimeVisibleTypeAnnotations_attribute.class),
"FIELD",
"Anno");
} else {
assertAttributeNotPresent(field.attributes, RuntimeVisibleTypeAnnotations_attribute.class);
}
// checking for the annotation on the corresponding parameter of the canonical constructor
Method init = findMethodOrFail(classFile, "<init>");
/* if PARAMETER is one of the targets then there must be a declaration annotation applied to the parameter, apart from
* the type annotation
*/
if (target.contains("PARAMETER")) {
checkParameterAnno(classFile,
(RuntimeVisibleParameterAnnotations_attribute)findAttributeOrFail(
init.attributes,
RuntimeVisibleParameterAnnotations_attribute.class),
"Anno");
} else {
assertAttributeNotPresent(init.attributes, RuntimeVisibleAnnotations_attribute.class);
}
// let's check now for the type annotation
if (target.contains("TYPE_USE")) {
checkTypeAnno(
classFile,
(RuntimeVisibleTypeAnnotations_attribute) findAttributeOrFail(init.attributes, RuntimeVisibleTypeAnnotations_attribute.class),
"METHOD_FORMAL_PARAMETER", "Anno");
} else {
assertAttributeNotPresent(init.attributes, RuntimeVisibleTypeAnnotations_attribute.class);
}
// checking for the annotation in the accessor
Method accessor = findMethodOrFail(classFile, "s");
/* if METHOD is one of the targets then there must be a declaration annotation applied to the accessor, apart from
* the type annotation
*/
if (target.contains("METHOD")) {
checkAnno(classFile,
(RuntimeAnnotations_attribute)findAttributeOrFail(
accessor.attributes,
RuntimeVisibleAnnotations_attribute.class),
"Anno");
} else {
assertAttributeNotPresent(accessor.attributes, RuntimeVisibleAnnotations_attribute.class);
}
// let's check now for the type annotation
if (target.contains("TYPE_USE")) {
checkTypeAnno(
classFile,
(RuntimeVisibleTypeAnnotations_attribute)findAttributeOrFail(accessor.attributes, RuntimeVisibleTypeAnnotations_attribute.class),
"METHOD_RETURN", "Anno");
} else {
assertAttributeNotPresent(accessor.attributes, RuntimeVisibleTypeAnnotations_attribute.class);
}
// checking for the annotation in the Record attribute
Record_attribute record = (Record_attribute)findAttributeOrFail(classFile.attributes, Record_attribute.class);
Assert.check(record.component_count == 1);
/* if RECORD_COMPONENT is one of the targets then there must be a declaration annotation applied to the
* field, apart from the type annotation
*/
if (target.contains("RECORD_COMPONENT")) {
checkAnno(classFile,
(RuntimeAnnotations_attribute)findAttributeOrFail(
record.component_info_arr[0].attributes,
RuntimeVisibleAnnotations_attribute.class),
"Anno");
} else {
assertAttributeNotPresent(record.component_info_arr[0].attributes, RuntimeVisibleAnnotations_attribute.class);
}
// lets check now for the type annotation
if (target.contains("TYPE_USE")) {
checkTypeAnno(
classFile,
(RuntimeVisibleTypeAnnotations_attribute)findAttributeOrFail(
record.component_info_arr[0].attributes,
RuntimeVisibleTypeAnnotations_attribute.class),
"FIELD", "Anno");
} else {
assertAttributeNotPresent(record.component_info_arr[0].attributes, RuntimeVisibleTypeAnnotations_attribute.class);
}
}
// let's reset the default compiler options for other tests
setCompileOptions(PREVIEW_OPTIONS);
}
private void checkTypeAnno(ClassFile classFile,
RuntimeTypeAnnotations_attribute rtAnnos,
String positionType,
String annoName) throws Exception {
// containing only one type annotation
Assert.check(rtAnnos.annotations.length == 1);
TypeAnnotation tAnno = (TypeAnnotation)rtAnnos.annotations[0];
Assert.check(tAnno.position.type.toString().equals(positionType));
String annotationName = classFile.constant_pool.getUTF8Value(tAnno.annotation.type_index).toString().substring(1);
Assert.check(annotationName.startsWith(annoName));
}
private void checkAnno(ClassFile classFile,
RuntimeAnnotations_attribute rAnnos,
String annoName) throws Exception {
// containing only one type annotation
Assert.check(rAnnos.annotations.length == 1);
Annotation anno = (Annotation)rAnnos.annotations[0];
String annotationName = classFile.constant_pool.getUTF8Value(anno.type_index).toString().substring(1);
Assert.check(annotationName.startsWith(annoName));
}
// special case for parameter annotations
private void checkParameterAnno(ClassFile classFile,
RuntimeVisibleParameterAnnotations_attribute rAnnos,
String annoName) throws Exception {
// containing only one type annotation
Assert.check(rAnnos.parameter_annotations.length == 1);
Assert.check(rAnnos.parameter_annotations[0].length == 1);
Annotation anno = (Annotation)rAnnos.parameter_annotations[0][0];
String annotationName = classFile.constant_pool.getUTF8Value(anno.type_index).toString().substring(1);
Assert.check(annotationName.startsWith(annoName));
}
private File findClassFileOrFail(File dir, String name) {
for (final File fileEntry : dir.listFiles()) {
if (fileEntry.getName().equals("R.class")) {
return fileEntry;
}
}
throw new AssertionError("file not found");
}
private Method findMethodOrFail(ClassFile classFile, String name) throws Exception {
for (Method method : classFile.methods) {
if (method.getName(classFile.constant_pool).equals(name)) {
return method;
}
}
throw new AssertionError("method not found");
}
private Attribute findAttributeOrFail(Attributes attributes, Class<? extends Attribute> attrClass) {
for (Attribute attribute : attributes) {
if (attribute.getClass() == attrClass) {
return attribute;
}
}
throw new AssertionError("attribute not found");
}
private void assertAttributeNotPresent(Attributes attributes, Class<? extends Attribute> attrClass) {
for (Attribute attribute : attributes) {
if (attribute.getClass() == attrClass) {
throw new AssertionError("attribute not expected");
}
}
}
@SupportedAnnotationTypes("*")
public static final class Processor extends JavacTestingAbstractProcessor {
String targets;
int numberOfTypeAnnotations;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
targets = processingEnv.getOptions().get("targets");
System.out.println("targets------------------------------------------------- " + targets);
for (TypeElement te : annotations) {
if (te.toString().equals("Anno")) {
checkElements(te, roundEnv, targets);
if (targets.contains("TYPE_USE")) {
Element element = processingEnv.getElementUtils().getTypeElement("R");
numberOfTypeAnnotations = 0;
System.out.println("element found --------------------------------- " + element);
checkTypeAnnotations(element);
Assert.check(numberOfTypeAnnotations == 4);
}
}
}
return true;
}
void checkElements(TypeElement te, RoundEnvironment renv, String targets) {
Set<? extends Element> annoElements = renv.getElementsAnnotatedWith(te);
Set<String> targetSet = new HashSet<>(Arrays.asList(targets.split(",")));
// we will check for type annotation in another method
targetSet.remove("ElementType.TYPE_USE");
for (Element e : annoElements) {
Symbol s = (Symbol) e;
switch (s.getKind()) {
case FIELD:
Assert.check(targetSet.contains("ElementType.FIELD"));
targetSet.remove("ElementType.FIELD");
break;
case METHOD:
Assert.check(targetSet.contains("ElementType.METHOD"));
targetSet.remove("ElementType.METHOD");
break;
case PARAMETER:
Assert.check(targetSet.contains("ElementType.PARAMETER"));
targetSet.remove("ElementType.PARAMETER");
break;
case RECORD_COMPONENT:
Assert.check(targetSet.contains("ElementType.RECORD_COMPONENT"));
targetSet.remove("ElementType.RECORD_COMPONENT");
break;
default:
throw new AssertionError("unexpected element kind");
}
}
Assert.check(targetSet.isEmpty(), targetSet.toString());
}
private void checkTypeAnnotations(Element rootElement) {
new ElementScanner<Void, Void>() {
@Override public Void visitVariable(VariableElement e, Void p) {
Symbol s = (Symbol) e;
if (s.getKind() == ElementKind.FIELD ||
s.getKind() == ElementKind.PARAMETER &&
s.name.toString().equals("s")) {
int currentTAs = numberOfTypeAnnotations;
verifyTypeAnnotations(e.asType().getAnnotationMirrors());
Assert.check(currentTAs + 1 == numberOfTypeAnnotations);
}
return null;
}
@Override
public Void visitExecutable(ExecutableElement e, Void p) {
Symbol s = (Symbol) e;
if (s.getKind() == ElementKind.METHOD &&
s.name.toString().equals("s")) {
int currentTAs = numberOfTypeAnnotations;
verifyTypeAnnotations(e.getReturnType().getAnnotationMirrors());
Assert.check(currentTAs + 1 == numberOfTypeAnnotations);
}
scan(e.getParameters(), p);
return null;
}
@Override public Void visitRecordComponent(RecordComponentElement e, Void p) {
int currentTAs = numberOfTypeAnnotations;
verifyTypeAnnotations(e.asType().getAnnotationMirrors());
Assert.check(currentTAs + 1 == numberOfTypeAnnotations);
return null;
}
}.scan(rootElement, null);
}
private void verifyTypeAnnotations(Iterable<? extends AnnotationMirror> annotations) {
for (AnnotationMirror mirror : annotations) {
Assert.check(mirror.toString().startsWith("@Anno"));
if (mirror instanceof TypeCompound) {
numberOfTypeAnnotations++;
}
}
}
}
}