From 5e822c24bb42e9027c8d9090d498bca7125d1963 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Tue, 10 Sep 2024 06:13:36 +0000 Subject: [PATCH] 8334870: javac does not accept classfiles with certain permitted RuntimeVisibleParameterAnnotations and RuntimeInvisibleParameterAnnotations attributes Reviewed-by: vromero --- .../com/sun/tools/javac/jvm/ClassReader.java | 158 +++- .../tools/javac/resources/compiler.properties | 13 +- test/langtools/tools/javac/T6435291/T.jcod | 320 -------- .../tools/javac/T6435291/T6435291.java | 61 -- .../parameter/ParameterAnnotations.java | 699 ++++++++++++++++++ .../tools/javac/diags/examples.not-yet.txt | 3 +- 6 files changed, 864 insertions(+), 390 deletions(-) delete mode 100644 test/langtools/tools/javac/T6435291/T.jcod delete mode 100644 test/langtools/tools/javac/T6435291/T6435291.java create mode 100644 test/langtools/tools/javac/annotations/parameter/ParameterAnnotations.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 bd05cf91e91..2373f869b0a 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 @@ -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. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index 7e41f43aeb2..a991ae60601 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -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} diff --git a/test/langtools/tools/javac/T6435291/T.jcod b/test/langtools/tools/javac/T6435291/T.jcod deleted file mode 100644 index 9175f4f4041..00000000000 --- a/test/langtools/tools/javac/T6435291/T.jcod +++ /dev/null @@ -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;"; // #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 ""; // #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. 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 ""; // #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 diff --git a/test/langtools/tools/javac/T6435291/T6435291.java b/test/langtools/tools/javac/T6435291/T6435291.java deleted file mode 100644 index 33c6038b697..00000000000 --- a/test/langtools/tools/javac/T6435291/T6435291.java +++ /dev/null @@ -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"); - } -} diff --git a/test/langtools/tools/javac/annotations/parameter/ParameterAnnotations.java b/test/langtools/tools/javac/annotations/parameter/ParameterAnnotations.java new file mode 100644 index 00000000000..395765fcb80 --- /dev/null +++ b/test/langtools/tools/javac/annotations/parameter/ParameterAnnotations.java @@ -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); + 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 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 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 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(""), + 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 out = result.getOutputLines(Task.OutputKind.STDOUT); + if (!out.equals(List.of(expectedOutput))) { + throw new AssertionError("Expected: " + List.of(expectedOutput) + ", but got: " + out); + } + List 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 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(); + } + + } +} diff --git a/test/langtools/tools/javac/diags/examples.not-yet.txt b/test/langtools/tools/javac/diags/examples.not-yet.txt index b2603338509..329e716a780 100644 --- a/test/langtools/tools/javac/diags/examples.not-yet.txt +++ b/test/langtools/tools/javac/diags/examples.not-yet.txt @@ -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