8334870: javac does not accept classfiles with certain permitted RuntimeVisibleParameterAnnotations and RuntimeInvisibleParameterAnnotations attributes

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2024-09-10 06:13:36 +00:00
parent 56387a0981
commit 5e822c24bb
6 changed files with 864 additions and 390 deletions

View File

@ -218,6 +218,12 @@ public class ClassReader {
*/
int[] parameterAccessFlags;
/**
* A table to hold the access flags of the method parameters,
* for all parameters including synthetic and mandated ones.
*/
int[] allParameterAccessFlags;
/**
* A table to hold annotations for method parameters.
*/
@ -1146,12 +1152,15 @@ public class ClassReader {
int newbp = bp + attrlen;
if (saveParameterNames) {
int numEntries = nextByte();
allParameterAccessFlags = new int[numEntries];
parameterNameIndicesMp = new int[numEntries];
parameterAccessFlags = new int[numEntries];
int allParamIndex = 0;
int index = 0;
for (int i = 0; i < numEntries; i++) {
int nameIndex = nextChar();
int flags = nextChar();
allParameterAccessFlags[allParamIndex++] = flags;
if ((flags & (Flags.MANDATED | Flags.SYNTHETIC)) != 0) {
continue;
}
@ -1593,7 +1602,16 @@ public class ClassReader {
if (parameterAnnotations == null) {
parameterAnnotations = new ParameterAnnotations[numParameters];
} else if (parameterAnnotations.length != numParameters) {
throw badClassFile("bad.runtime.invisible.param.annotations", meth);
//the RuntimeVisibleParameterAnnotations and RuntimeInvisibleParameterAnnotations
//provide annotations for a different number of parameters, ignore:
if (lintClassfile) {
log.warning(LintCategory.CLASSFILE, Warnings.RuntimeVisibleInvisibleParamAnnotationsMismatch(currentClassFile));
}
for (int pnum = 0; pnum < numParameters; pnum++) {
readAnnotations();
}
parameterAnnotations = null;
return ;
}
for (int pnum = 0; pnum < numParameters; pnum++) {
if (parameterAnnotations[pnum] == null) {
@ -2623,7 +2641,8 @@ public class ClassReader {
char rawFlags = nextChar();
long flags = adjustMethodFlags(rawFlags);
Name name = poolReader.getName(nextChar());
Type type = poolReader.getType(nextChar());
Type descriptorType = poolReader.getType(nextChar());
Type type = descriptorType;
if (currentOwner.isInterface() &&
(flags & ABSTRACT) == 0 && !name.equals(names.clinit)) {
if (majorVersion > Version.V52.major ||
@ -2640,6 +2659,7 @@ public class ClassReader {
}
}
validateMethodType(name, type);
boolean forceLocal = false;
if (name == names.init && currentOwner.hasOuterInstance()) {
// Sometimes anonymous classes don't have an outer
// instance, however, there is no reliable way to tell so
@ -2647,7 +2667,8 @@ public class ClassReader {
// ditto for local classes. Local classes that have an enclosing method set
// won't pass the "hasOuterInstance" check above, but those that don't have an
// enclosing method (i.e. from initializers) will pass that check.
boolean local = !currentOwner.owner.members().includes(currentOwner, LookupKind.NON_RECURSIVE);
boolean local = forceLocal =
!currentOwner.owner.members().includes(currentOwner, LookupKind.NON_RECURSIVE);
if (!currentOwner.name.isEmpty() && !local)
type = new MethodType(adjustMethodParams(flags, type.getParameterTypes()),
type.getReturnType(),
@ -2668,6 +2689,7 @@ public class ClassReader {
currentOwner = prevOwner;
}
validateMethodType(name, m.type);
adjustParameterAnnotations(m, descriptorType, forceLocal);
setParameters(m, type);
if (Integer.bitCount(rawFlags & (PUBLIC | PRIVATE | PROTECTED)) > 1)
@ -2795,17 +2817,141 @@ public class ClassReader {
nameIndexMp++;
annotationIndex++;
}
if (parameterAnnotations != null && parameterAnnotations.length != annotationIndex) {
throw badClassFile("bad.runtime.invisible.param.annotations", sym);
}
Assert.check(parameterAnnotations == null ||
parameterAnnotations.length == annotationIndex);
Assert.checkNull(sym.params);
sym.params = params.toList();
parameterAnnotations = null;
parameterNameIndicesLvt = null;
parameterNameIndicesMp = null;
allParameterAccessFlags = null;
parameterAccessFlags = null;
}
void adjustParameterAnnotations(MethodSymbol sym, Type methodDescriptor,
boolean forceLocal) {
if (parameterAnnotations == null) {
return ;
}
//the specification for Runtime(In)VisibleParameterAnnotations does not
//enforce any mapping between the method parameters and the recorded
//parameter annotation. Attempt a number of heuristics to adjust the
//adjust parameterAnnotations to the percieved number of parameters:
int methodParameterCount = sym.type.getParameterTypes().size();
if (methodParameterCount == parameterAnnotations.length) {
//we've got exactly as many parameter annotations as are parameters
//of the method (after considering a possible Signature attribute),
//no need to do anything. the parameter creation code will use
//the 1-1 mapping to restore the annotations:
return ;
}
if (allParameterAccessFlags != null) {
//MethodParameters attribute present, use it:
//count the number of non-synthetic and non-mandatory parameters:
int realParameters = 0;
for (int i = 0; i < allParameterAccessFlags.length; i++) {
if ((allParameterAccessFlags[i] & (SYNTHETIC | MANDATED)) == 0) {
realParameters++;
}
}
int methodDescriptorParameterCount = methodDescriptor.getParameterTypes().size();
if (realParameters == parameterAnnotations.length &&
allParameterAccessFlags.length == methodDescriptorParameterCount) {
//if we have parameter annotations for each non-synthetic/mandatory parameter,
//and if Signature was not present, expand the parameterAnnotations to cover
//all the method descriptor's parameters:
if (sym.type == methodDescriptor) {
ParameterAnnotations[] newParameterAnnotations =
new ParameterAnnotations[methodParameterCount];
int srcIndex = 0;
for (int i = 0; i < methodParameterCount; i++) {
if ((allParameterAccessFlags[i] & (SYNTHETIC | MANDATED)) == 0) {
newParameterAnnotations[i] = parameterAnnotations[srcIndex++];
}
}
parameterAnnotations = newParameterAnnotations;
} else {
dropParameterAnnotations();
}
} else if (realParameters == methodParameterCount &&
methodDescriptorParameterCount == parameterAnnotations.length &&
allParameterAccessFlags.length == methodDescriptorParameterCount) {
//if there are as many parameter annotations as parameters in
//the method descriptor, and as many real parameters as parameters
//in the method's type (after accounting for Signature), shrink
//the parameterAnnotations to only cover the parameters from
//the method's type:
ParameterAnnotations[] newParameterAnnotations =
new ParameterAnnotations[methodParameterCount];
int targetIndex = 0;
for (int i = 0; i < parameterAnnotations.length; i++) {
if ((allParameterAccessFlags[i] & (SYNTHETIC | MANDATED)) == 0) {
newParameterAnnotations[targetIndex++] = parameterAnnotations[i];
}
}
parameterAnnotations = newParameterAnnotations;
} else {
dropParameterAnnotations();
}
return ;
}
if (!sym.isConstructor()) {
//if the number of parameter annotations and the number of parameters
//don't match, we don't have any heuristics to map one to the other
//unless the method is a constructor:
dropParameterAnnotations();
return ;
}
if (sym.owner.isEnum()) {
if (methodParameterCount == parameterAnnotations.length + 2 &&
sym.type == methodDescriptor) {
//handle constructors of enum types without the Signature attribute -
//there are the two synthetic parameters (name and ordinal) in the
//constructor, but there may be only parameter annotations for the
//real non-synthetic parameters:
ParameterAnnotations[] newParameterAnnotations = new ParameterAnnotations[parameterAnnotations.length + 2];
System.arraycopy(parameterAnnotations, 0, newParameterAnnotations, 2, parameterAnnotations.length);
parameterAnnotations = newParameterAnnotations;
return ;
}
} else if (sym.owner.isDirectlyOrIndirectlyLocal() || forceLocal) {
//local class may capture the enclosing instance (as the first parameter),
//and local variables (as trailing parameters)
//if there are less parameter annotations than parameters, put the existing
//ones starting with offset:
if (methodParameterCount > parameterAnnotations.length &&
sym.type == methodDescriptor) {
ParameterAnnotations[] newParameterAnnotations = new ParameterAnnotations[methodParameterCount];
System.arraycopy(parameterAnnotations, 0, newParameterAnnotations, 1, parameterAnnotations.length);
parameterAnnotations = newParameterAnnotations;
return ;
}
}
//no heuristics worked, drop the annotations:
dropParameterAnnotations();
}
private void dropParameterAnnotations() {
parameterAnnotations = null;
if (lintClassfile) {
log.warning(LintCategory.CLASSFILE, Warnings.RuntimeInvisibleParameterAnnotations(currentClassFile));
}
}
/**
* Creates the parameter at the position {@code mpIndex} in the parameter list of the owning method.
* Flags are optionally read from the MethodParameters attribute.

View File

@ -2547,8 +2547,17 @@ compiler.misc.bad.enclosing.class=\
compiler.misc.bad.enclosing.method=\
bad enclosing method attribute for class {0}
compiler.misc.bad.runtime.invisible.param.annotations=\
bad RuntimeInvisibleParameterAnnotations attribute: {0}
# 0: file name
compiler.warn.runtime.visible.invisible.param.annotations.mismatch=\
the length of parameters in RuntimeVisibleParameterAnnotations attribute and \
RuntimeInvisibleParameterAnnotations attribute in: {0} \
do not match, ignoring both attributes
# 0: file name
compiler.warn.runtime.invisible.parameter.annotations=\
the RuntimeVisibleParameterAnnotations and RuntimeInvisibleParameterAnnotations attributes \
in: {0} \
cannot be mapped to the method''s parameters
compiler.misc.bad.const.pool.tag=\
bad constant pool tag: {0}

View File

@ -1,320 +0,0 @@
class T {
0xCAFEBABE;
0; // minor version
49; // version
[73] { // Constant Pool
; // first element is empty
Utf8 "T"; // #1 at 0x0A
class #1; // #2 at 0x1A
Utf8 "Ljava/lang/Enum<LT;>;"; // #3 at 0x1D
Utf8 "java/lang/Enum"; // #4 at 0x41
class #4; // #5 at 0x52
Utf8 "T.java"; // #6 at 0x55
Utf8 "T1"; // #7 at 0x61
Utf8 "LT;"; // #8 at 0x66
Utf8 "T2"; // #9 at 0x78
Utf8 "T3"; // #10 at 0x7D
Utf8 "myName"; // #11 at 0x82
Utf8 "Ljava/lang/String;"; // #12 at 0x8B
Utf8 "$VALUES"; // #13 at 0xA0
Utf8 "[LT;"; // #14 at 0xAA
Utf8 "values"; // #15 at 0xBD
Utf8 "()[LT;"; // #16 at 0xC6
NameAndType #13 #14; // #17 at 0xDB
Field #2 #17; // #18 at 0xE0
class #14; // #19 at 0xE5
Utf8 "clone"; // #20 at 0xE8
Utf8 "()Ljava/lang/Object;"; // #21 at 0xF0
NameAndType #20 #21; // #22 at 0x0107
Method #19 #22; // #23 at 0x010C
Utf8 "valueOf"; // #24 at 0x0111
Utf8 "(Ljava/lang/String;)LT;"; // #25 at 0x011B
Utf8 "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;"; // #26 at 0x0141
NameAndType #24 #26; // #27 at 0x0179
Method #5 #27; // #28 at 0x017E
Utf8 "name"; // #29 at 0x0183
Utf8 "getName"; // #30 at 0x018A
Utf8 "()Ljava/lang/String;"; // #31 at 0x0194
NameAndType #11 #12; // #32 at 0x01AB
Field #2 #32; // #33 at 0x01B0
Utf8 "this"; // #34 at 0x01B5
Utf8 "<init>"; // #35 at 0x01BC
Utf8 "(Ljava/lang/String;ILjava/lang/String;)V"; // #36 at 0x01C5
Utf8 "LNotNull;"; // #37 at 0x01F0
Utf8 "java/lang/IllegalArgumentException"; // #38 at 0x0216
class #38; // #39 at 0x023B
Utf8 "Argument 0 for @NotNull parameter of T.<init> must not be null"; // #40 at 0x023E
String #40; // #41 at 0x028B
Utf8 "(Ljava/lang/String;)V"; // #42 at 0x028E
NameAndType #35 #42; // #43 at 0x02A6
Method #39 #43; // #44 at 0x02AB
Utf8 "(Ljava/lang/String;I)V"; // #45 at 0x02B0
NameAndType #35 #45; // #46 at 0x02C9
Method #5 #46; // #47 at 0x02CE
Utf8 "<clinit>"; // #48 at 0x02D3
Utf8 "()V"; // #49 at 0x02DE
String #7; // #50 at 0x02E4
Utf8 "type1"; // #51 at 0x02E7
String #51; // #52 at 0x02EF
NameAndType #35 #36; // #53 at 0x02F2
Method #2 #53; // #54 at 0x02F7
NameAndType #7 #8; // #55 at 0x02FC
Field #2 #55; // #56 at 0x0301
String #9; // #57 at 0x0306
Utf8 "type2"; // #58 at 0x0309
String #58; // #59 at 0x0311
NameAndType #9 #8; // #60 at 0x0314
Field #2 #60; // #61 at 0x0319
String #10; // #62 at 0x031E
Utf8 "type3"; // #63 at 0x0321
String #63; // #64 at 0x0329
NameAndType #10 #8; // #65 at 0x032C
Field #2 #65; // #66 at 0x0331
Utf8 "Code"; // #67 at 0x0336
Utf8 "LineNumberTable"; // #68 at 0x033D
Utf8 "LocalVariableTable"; // #69 at 0x034F
Utf8 "Signature"; // #70 at 0x0364
Utf8 "RuntimeInvisibleParameterAnnotations"; // #71 at 0x0370
Utf8 "SourceFile"; // #72 at 0x0397
} // Constant Pool
0x4031; // access
#2;// this_cpx
#5;// super_cpx
[0] { // Interfaces
} // Interfaces
[5] { // fields
{ // Member at 0x03AE
0x4019; // access
#7; // name_cpx
#8; // sig_cpx
[0] { // Attributes
} // Attributes
} // Member
;
{ // Member at 0x03B6
0x4019; // access
#9; // name_cpx
#8; // sig_cpx
[0] { // Attributes
} // Attributes
} // Member
;
{ // Member at 0x03BE
0x4019; // access
#10; // name_cpx
#8; // sig_cpx
[0] { // Attributes
} // Attributes
} // Member
;
{ // Member at 0x03C6
0x0012; // access
#11; // name_cpx
#12; // sig_cpx
[0] { // Attributes
} // Attributes
} // Member
;
{ // Member at 0x03CE
0x101A; // access
#13; // name_cpx
#14; // sig_cpx
[0] { // Attributes
} // Attributes
} // Member
} // fields
[5] { // methods
{ // Member at 0x03D8
0x0019; // access
#15; // name_cpx
#16; // sig_cpx
[1] { // Attributes
Attr(#67, 34) { // Code at 0x03E0
1; // max_stack
0; // max_locals
Bytes[10]{
0xB20012B60017C000;
0x13B0;
};
[0] { // Traps
} // end Traps
[1] { // Attributes
Attr(#68, 6) { // LineNumberTable at 0x03FC
[1] { // LineNumberTable
0 9; // at 0x0408
}
} // end LineNumberTable
} // Attributes
} // end Code
} // Attributes
} // Member
;
{ // Member at 0x0408
0x0009; // access
#24; // name_cpx
#25; // sig_cpx
[1] { // Attributes
Attr(#67, 52) { // Code at 0x0410
2; // max_stack
1; // max_locals
Bytes[10]{
0x12022AB8001CC000;
0x02B0;
};
[0] { // Traps
} // end Traps
[2] { // Attributes
Attr(#69, 12) { // LocalVariableTable at 0x042C
[1] { // LocalVariableTable
0 10 29 12 0; // at 0x043E
}
} // end LocalVariableTable
;
Attr(#68, 6) { // LineNumberTable at 0x043E
[1] { // LineNumberTable
0 9; // at 0x044A
}
} // end LineNumberTable
} // Attributes
} // end Code
} // Attributes
} // Member
;
{ // Member at 0x044A
0x0001; // access
#30; // name_cpx
#31; // sig_cpx
[1] { // Attributes
Attr(#67, 47) { // Code at 0x0452
1; // max_stack
1; // max_locals
Bytes[5]{
0x2AB40021B0;
};
[0] { // Traps
} // end Traps
[2] { // Attributes
Attr(#69, 12) { // LocalVariableTable at 0x0469
[1] { // LocalVariableTable
0 5 34 8 0; // at 0x047B
}
} // end LocalVariableTable
;
Attr(#68, 6) { // LineNumberTable at 0x047B
[1] { // LineNumberTable
0 17; // at 0x0487
}
} // end LineNumberTable
} // Attributes
} // end Code
} // Attributes
} // Member
;
{ // Member at 0x0487
0x0002; // access
#35; // name_cpx
#36; // sig_cpx
[3] { // Attributes
Attr(#67, 86) { // Code at 0x048F
3; // max_stack
4; // max_locals
Bytes[26]{
0x2BC7000DBB002759;
0x1229B7002CBF2A2B;
0x1CB7002F2A2DB500;
0x21B1;
};
[0] { // Traps
} // end Traps
[2] { // Attributes
Attr(#69, 22) { // LocalVariableTable at 0x04BB
[2] { // LocalVariableTable
14 12 34 8 0; // at 0x04CD
14 12 29 12 3; // at 0x04D7
}
} // end LocalVariableTable
;
Attr(#68, 14) { // LineNumberTable at 0x04D7
[3] { // LineNumberTable
14 20; // at 0x04E3
20 21; // at 0x04E7
25 22; // at 0x04EB
}
} // end LineNumberTable
} // Attributes
} // end Code
;
Attr(#70, 2) { // Signature at 0x04EB
#42;
} // end Signature
;
Attr(#71, 11) { // RuntimeInvisibleParameterAnnotations at 0x04F3
[3]b { // parameters
[1] { // annotations
{ // annotation
#37;
[0] { // element_value_pairs
} // element_value_pairs
} // annotation
}
;
[0] { // annotations
}
;
[0] { // annotations
}
}
} // end RuntimeInvisibleParameterAnnotations
} // Attributes
} // Member
;
{ // Member at 0x0504
0x0008; // access
#48; // name_cpx
#49; // sig_cpx
[1] { // Attributes
Attr(#67, 107) { // Code at 0x050C
5; // max_stack
0; // max_locals
Bytes[71]{
0xBB00025912320312;
0x34B70036B30038BB;
0x000259123904123B;
0xB70036B3003DBB00;
0x0259123E051240B7;
0x0036B3004206BD00;
0x025903B200385359;
0x04B2003D535905B2;
0x004253B30012B1;
};
[0] { // Traps
} // end Traps
[1] { // Attributes
Attr(#68, 18) { // LineNumberTable at 0x0565
[4] { // LineNumberTable
0 10; // at 0x0571
15 11; // at 0x0575
30 12; // at 0x0579
45 9; // at 0x057D
}
} // end LineNumberTable
} // Attributes
} // end Code
} // Attributes
} // Member
} // methods
[2] { // Attributes
Attr(#70, 2) { // Signature at 0x057F
#3;
} // end Signature
;
Attr(#72, 2) { // SourceFile at 0x0587
#6;
} // end SourceFile
} // Attributes
} // end class T

View File

@ -1,61 +0,0 @@
/*
* Copyright (c) 2006, 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
* @bug 6435291
* @summary javac shouldn't throw NPE while compiling invalid RuntimeInvisibleParameterAnnotations
* @author Wei Tao
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.code
* jdk.compiler/com.sun.tools.javac.comp
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/com.sun.tools.javac.util
* @build T
* @run main/othervm T6435291
*/
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.code.ClassFinder.BadClassFile;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.util.Names;
import javax.tools.ToolProvider;
public class T6435291 {
public static void main(String... args) {
javax.tools.JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
JavacTaskImpl task = (JavacTaskImpl)tool.getTask(null, null, null, null, null, null);
Symtab syms = Symtab.instance(task.getContext());
Names names = Names.instance(task.getContext());
task.ensureEntered();
try {
syms.enterClass(syms.unnamedModule, names.fromString("T")).complete();
} catch (BadClassFile e) {
System.err.println("Passed: expected completion failure " + e.getClass().getName());
return;
} catch (Exception e) {
throw new RuntimeException("Failed: unexpected exception");
}
throw new RuntimeException("Failed: no error reported");
}
}

View File

@ -0,0 +1,699 @@
/*
* Copyright (c) 2024, 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
* @bug 8024694 8334870
* @summary Check javac can handle various Runtime(In)VisibleParameterAnnotations attribute combinations
* @enablePreview
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/com.sun.tools.javac.util
* @build toolbox.ToolBox toolbox.JavacTask
* @run main ParameterAnnotations
*/
import java.io.OutputStream;
import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassModel;
import java.lang.classfile.ClassTransform;
import java.lang.classfile.MethodBuilder;
import java.lang.classfile.MethodElement;
import java.lang.classfile.MethodTransform;
import java.lang.classfile.attribute.MethodParametersAttribute;
import java.lang.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute;
import java.lang.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute;
import java.lang.classfile.attribute.SignatureAttribute;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import toolbox.TestRunner;
import toolbox.JavacTask;
import toolbox.Task;
import toolbox.ToolBox;
public class ParameterAnnotations extends TestRunner {
ToolBox tb;
public static void main(String... args) throws Exception {
new ParameterAnnotations().runTests();
}
ParameterAnnotations() {
super(System.err);
tb = new ToolBox();
}
public void runTests() throws Exception {
runTests(m -> new Object[] { Paths.get(m.getName()) });
}
@Test
public void testEnum(Path base) throws Exception {
//not parameterized:
doTest(base,
"""
import java.lang.annotation.*;
public enum E {
A(0);
E(@Visible @Invisible long i) {}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"E",
MethodTransform.ACCEPT_ALL,
"@Invisible @Visible long");
//parameterized:
doTest(base,
"""
import java.lang.annotation.*;
public enum E {
A(0);
<T> E(@Visible @Invisible long i) {}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"E",
MethodTransform.ACCEPT_ALL,
"@Invisible @Visible long");
//not parameterized, and no Signature attribute:
doTest(base,
"""
import java.lang.annotation.*;
public enum E {
A(0);
E(@Visible @Invisible long i) {}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"E",
NO_SIGNATURE,
"java.lang.String, int, @Invisible @Visible long");
//not parameterized, and no Signature and MethodParameters attribute:
doTest(base,
"""
import java.lang.annotation.*;
public enum E {
A(0);
E(@Visible @Invisible long i) {}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"E",
NO_SIGNATURE_NO_METHOD_PARAMETERS,
"java.lang.String, int, @Invisible @Visible long");
}
@Test
public void testInnerClass(Path base) throws Exception {
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public class I {
public I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString(); //force outer this capture
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$I",
MethodTransform.ACCEPT_ALL,
"@Invisible @Visible long");
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public class I {
public <T> I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString(); //force outer this capture
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$I",
MethodTransform.ACCEPT_ALL,
"@Invisible @Visible long");
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public class I {
public I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString(); //force outer this capture
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$I",
NO_SIGNATURE,
"@Invisible @Visible long");
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public class I {
public I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString(); //force outer this capture
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$I",
NO_SIGNATURE_NO_METHOD_PARAMETERS,
"@Invisible @Visible long");
}
@Test
public void testCapturingLocal(Path base) throws Exception {
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public void test(int i) {
class I {
public I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString() + i; //force outer this capture
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$1I",
MethodTransform.ACCEPT_ALL,
"@Invisible @Visible long");
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public void test(int i) {
class I {
public <T> I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString() + i; //force outer this capture
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$1I",
MethodTransform.ACCEPT_ALL,
"@Invisible @Visible long");
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public void test(int i) {
class I {
public I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString() + i; //force outer this capture
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$1I",
NO_SIGNATURE,
"T, @Invisible @Visible long, int");
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public void test(int i) {
class I {
public I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString() + i; //force outer this capture
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$1I",
NO_SIGNATURE_NO_METHOD_PARAMETERS,
"T, @Invisible @Visible long, int");
doTest(base,
"""
import java.lang.annotation.*;
public class T {
{
int i = 0;
class I {
public I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString() + i; //force outer this capture
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$1I",
MethodTransform.ACCEPT_ALL,
"@Invisible @Visible long");
doTest(base,
"""
import java.lang.annotation.*;
public class T {
{
int i = 0;
class I {
public <T> I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString() + i; //force outer this capture
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$1I",
MethodTransform.ACCEPT_ALL,
"@Invisible @Visible long");
doTest(base,
"""
import java.lang.annotation.*;
public class T {
{
int i = 0;
class I {
public I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString() + i; //force outer this capture
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$1I",
NO_SIGNATURE,
"T, @Invisible @Visible long, int");
doTest(base,
"""
import java.lang.annotation.*;
public class T {
{
int i = 0;
class I {
public I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString() + i; //force outer this capture
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$1I",
NO_SIGNATURE_NO_METHOD_PARAMETERS,
"T, @Invisible @Visible long, int");
}
@Test
public void testSyntheticTests(Path base) throws Exception {
//Signature attribute will defined one parameter, but the
//Runtime(In)VisibleParameterAnnotations will define 3 parameters:
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public void test(int i) {
class I {
public I(@Visible @Invisible long l) {}
public String toString() {
return T.this.toString() + i; //force outer this capture
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T$1I",
new MethodTransform() {
@Override
public void accept(MethodBuilder builder, MethodElement element) {
if (element instanceof RuntimeInvisibleParameterAnnotationsAttribute annos) {
assert annos.parameterAnnotations().size() == 1;
builder.accept(RuntimeInvisibleParameterAnnotationsAttribute.of(List.of(List.of(), annos.parameterAnnotations().get(0), List.of())));
} else if (element instanceof RuntimeVisibleParameterAnnotationsAttribute annos) {
assert annos.parameterAnnotations().size() == 1;
builder.accept(RuntimeVisibleParameterAnnotationsAttribute.of(List.of(List.of(), annos.parameterAnnotations().get(0), List.of())));
} else {
builder.accept(element);
}
}
},
"@Invisible @Visible long");
//no Signature attribute, no synthetic parameters,
//but less entries in Runtime(In)VisibleParameterAnnotations than parameters
//no way to map anything:
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public T(int i, @Visible @Invisible long l, String s) {}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T",
new MethodTransform() {
@Override
public void accept(MethodBuilder builder, MethodElement element) {
if (element instanceof RuntimeInvisibleParameterAnnotationsAttribute annos) {
assert annos.parameterAnnotations().size() == 3;
builder.accept(RuntimeInvisibleParameterAnnotationsAttribute.of(List.of(annos.parameterAnnotations().get(1))));
} else if (element instanceof RuntimeVisibleParameterAnnotationsAttribute annos) {
assert annos.parameterAnnotations().size() == 3;
builder.accept(RuntimeVisibleParameterAnnotationsAttribute.of(List.of(annos.parameterAnnotations().get(1))));
} else {
builder.accept(element);
}
}
},
"int, long, java.lang.String",
"- compiler.warn.runtime.invisible.parameter.annotations: T.class",
"1 warning");
//no Signature attribute, no synthetic parameters,
//but more entries in Runtime(In)VisibleParameterAnnotations than parameters
//no way to map anything:
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public T(@Visible @Invisible long l) {}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T",
new MethodTransform() {
@Override
public void accept(MethodBuilder builder, MethodElement element) {
if (element instanceof RuntimeInvisibleParameterAnnotationsAttribute annos) {
assert annos.parameterAnnotations().size() == 1;
builder.accept(RuntimeInvisibleParameterAnnotationsAttribute.of(List.of(List.of(), annos.parameterAnnotations().get(0), List.of())));
} else if (element instanceof RuntimeVisibleParameterAnnotationsAttribute annos) {
assert annos.parameterAnnotations().size() == 1;
builder.accept(RuntimeVisibleParameterAnnotationsAttribute.of(List.of(List.of(), annos.parameterAnnotations().get(0), List.of())));
} else {
builder.accept(element);
}
}
},
"long",
"- compiler.warn.runtime.invisible.parameter.annotations: T.class",
"1 warning");
//mismatched lengths on RuntimeVisibleParameterAnnotations and
//RuntimeInvisibleParameterAnnotations:
doTest(base,
"""
import java.lang.annotation.*;
public class T {
public T(@Visible @Invisible long l) {}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"T",
new MethodTransform() {
@Override
public void accept(MethodBuilder builder, MethodElement element) {
if (element instanceof RuntimeInvisibleParameterAnnotationsAttribute annos) {
assert annos.parameterAnnotations().size() == 1;
builder.accept(annos); //keep intact
} else if (element instanceof RuntimeVisibleParameterAnnotationsAttribute annos) {
assert annos.parameterAnnotations().size() == 1;
builder.accept(RuntimeVisibleParameterAnnotationsAttribute.of(List.of(List.of(), annos.parameterAnnotations().get(0), List.of())));
} else {
builder.accept(element);
}
}
},
"long",
"- compiler.warn.runtime.visible.invisible.param.annotations.mismatch: T.class",
"1 warning");
}
@Test
public void testRecord(Path base) throws Exception {
//implicit constructor:
doTest(base,
"""
import java.lang.annotation.*;
public record R(int i, @Visible @Invisible long l, String s) {
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"R",
MethodTransform.ACCEPT_ALL,
"int, @Invisible @Visible long, java.lang.String");
doTest(base,
"""
import java.lang.annotation.*;
public record R(int i, @Visible @Invisible long l, String s) {
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"R",
NO_SIGNATURE,
"int, @Invisible @Visible long, java.lang.String");
doTest(base,
"""
import java.lang.annotation.*;
public record R(int i, @Visible @Invisible long l, String s) {
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"R",
NO_SIGNATURE_NO_METHOD_PARAMETERS,
"int, @Invisible @Visible long, java.lang.String");
//compact constructor:
doTest(base,
"""
import java.lang.annotation.*;
public record R(int i, @Visible @Invisible long l, String s) {
public R {}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"R",
MethodTransform.ACCEPT_ALL,
"int, @Invisible @Visible long, java.lang.String");
doTest(base,
"""
import java.lang.annotation.*;
public record R(int i, @Visible @Invisible long l, String s) {
public R {}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"R",
NO_SIGNATURE,
"int, @Invisible @Visible long, java.lang.String");
doTest(base,
"""
import java.lang.annotation.*;
public record R(int i, @Visible @Invisible long l, String s) {
public R {}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Visible {}
@interface Invisible {}
""",
"R",
NO_SIGNATURE_NO_METHOD_PARAMETERS,
"int, @Invisible @Visible long, java.lang.String");
}
private MethodTransform NO_SIGNATURE =
MethodTransform.dropping(element -> element instanceof SignatureAttribute);
private MethodTransform NO_SIGNATURE_NO_METHOD_PARAMETERS =
MethodTransform.dropping(element -> element instanceof SignatureAttribute ||
element instanceof MethodParametersAttribute);
private void doTest(Path base, String code, String binaryNameToCheck,
MethodTransform changeConstructor, String expectedOutput,
String... expectedDiagnostics) throws Exception {
Path current = base.resolve(".");
Path src = current.resolve("src");
Path classes = current.resolve("classes");
tb.writeJavaFiles(src, code);
Files.createDirectories(classes);
new JavacTask(tb)
.outdir(classes)
.files(tb.findJavaFiles(src))
.run(Task.Expect.SUCCESS)
.writeAll();
Path classfile = classes.resolve(binaryNameToCheck + ".class");
ClassFile cf = ClassFile.of();
ClassModel model = cf.parse(classfile);
byte[] newClassFile = cf.transformClass(model,
ClassTransform.transformingMethods(m -> m.methodName()
.equalsString("<init>"),
changeConstructor));
try (OutputStream out = Files.newOutputStream(classfile)) {
out.write(newClassFile);
}
Task.Result result = new JavacTask(tb)
.options("-classpath", classes.toString(),
"-processor", TestAP.class.getName(),
"-XDrawDiagnostics",
"-Xlint:classfile")
.outdir(classes)
.classes(binaryNameToCheck)
.run(Task.Expect.SUCCESS)
.writeAll();
List<String> out = result.getOutputLines(Task.OutputKind.STDOUT);
if (!out.equals(List.of(expectedOutput))) {
throw new AssertionError("Expected: " + List.of(expectedOutput) + ", but got: " + out);
}
List<String> diagnostics =
new ArrayList<>(result.getOutputLines(Task.OutputKind.DIRECT));
diagnostics.remove("");
if (!diagnostics.equals(List.of(expectedDiagnostics))) {
throw new AssertionError("Expected: " + List.of(expectedDiagnostics) + ", but got: " + diagnostics);
}
}
@SupportedAnnotationTypes("*")
public static final class TestAP extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement clazz : ElementFilter.typesIn(roundEnv.getRootElements())) {
for (ExecutableElement el : ElementFilter.constructorsIn(clazz.getEnclosedElements())) {
String sep = "";
for (VariableElement p : el.getParameters()) {
System.out.print(sep);
if (!p.getAnnotationMirrors().isEmpty()) {
System.out.print(p.getAnnotationMirrors()
.stream()
.map(m -> m.toString())
.collect(Collectors.joining(" ")));
System.out.print(" ");
}
System.out.print(p.asType());
sep = ", ";
}
System.out.println();
}
}
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
}
}

View File

@ -55,7 +55,8 @@ compiler.misc.bad.constant.range # bad class file
compiler.misc.bad.constant.value # bad class file
compiler.misc.bad.enclosing.class # bad class file
compiler.misc.bad.enclosing.method # bad class file
compiler.misc.bad.runtime.invisible.param.annotations # bad class file
compiler.warn.runtime.invisible.parameter.annotations # bad class file
compiler.warn.runtime.visible.invisible.param.annotations.mismatch # bad class file
compiler.misc.bad.signature # bad class file
compiler.misc.bad.requires.flag # bad class file
compiler.misc.bad.utf8.byte.sequence.at # bad class file