diff --git a/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java b/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java index 5e3cd01a4b8..03cb62429c1 100644 --- a/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java +++ b/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2023, 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 @@ -2384,7 +2384,8 @@ public class CreateSymbols { MethodDescription method = (MethodDescription) feature; method.methodParameters = new ArrayList<>(); for (MethodParameters_attribute.Entry e : params.method_parameter_table) { - String name = cf.constant_pool.getUTF8Value(e.name_index); + String name = e.name_index == 0 ? null + : cf.constant_pool.getUTF8Value(e.name_index); MethodDescription.MethodParam param = new MethodDescription.MethodParam(e.flags, name); method.methodParameters.add(param); 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 63d4fa11b6b..1fda5ed74fc 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 @@ -202,7 +202,13 @@ public class ClassReader { /** A table to hold the constant pool indices for method parameter * names, as given in LocalVariableTable attributes. */ - int[] parameterNameIndices; + int[] parameterNameIndicesLvt; + + /** + * A table to hold the constant pool indices for method parameter + * names, as given in the MethodParameters attribute. + */ + int[] parameterNameIndicesMp; /** * A table to hold the access flags of the method parameters. @@ -229,18 +235,6 @@ public class ClassReader { } } - /** - * Whether or not any parameter names have been found. - */ - boolean haveParameterNameIndices; - - /** Set this to false every time we start reading a method - * and are saving parameter names. Set it to true when we see - * MethodParameters, if it's set when we see a LocalVariableTable, - * then we ignore the parameter names from the LVT. - */ - boolean sawMethodParameters; - /** * The set of attribute names for which warnings have been generated for the current class */ @@ -939,7 +933,7 @@ public class ClassReader { new AttributeReader(names.LocalVariableTable, V45_3, CLASS_OR_MEMBER_ATTRIBUTE) { protected void read(Symbol sym, int attrLen) { int newbp = bp + attrLen; - if (saveParameterNames && !sawMethodParameters) { + if (saveParameterNames) { // Pick up parameter names from the variable table. // Parameter names are not explicitly identified as such, // but all parameter name entries in the LocalVariableTable @@ -958,14 +952,13 @@ public class ClassReader { int register = nextChar(); if (start_pc == 0) { // ensure array large enough - if (register >= parameterNameIndices.length) { + if (register >= parameterNameIndicesLvt.length) { int newSize = - Math.max(register + 1, parameterNameIndices.length + 8); - parameterNameIndices = - Arrays.copyOf(parameterNameIndices, newSize); + Math.max(register + 1, parameterNameIndicesLvt.length + 8); + parameterNameIndicesLvt = + Arrays.copyOf(parameterNameIndicesLvt, newSize); } - parameterNameIndices[register] = nameIndex; - haveParameterNameIndices = true; + parameterNameIndicesLvt[register] = nameIndex; } } } @@ -1116,11 +1109,9 @@ public class ClassReader { protected void read(Symbol sym, int attrlen) { int newbp = bp + attrlen; if (saveParameterNames) { - sawMethodParameters = true; int numEntries = nextByte(); - parameterNameIndices = new int[numEntries]; + parameterNameIndicesMp = new int[numEntries]; parameterAccessFlags = new int[numEntries]; - haveParameterNameIndices = true; int index = 0; for (int i = 0; i < numEntries; i++) { int nameIndex = nextChar(); @@ -1128,7 +1119,7 @@ public class ClassReader { if ((flags & (Flags.MANDATED | Flags.SYNTHETIC)) != 0) { continue; } - parameterNameIndices[index] = nameIndex; + parameterNameIndicesMp[index] = nameIndex; parameterAccessFlags[index] = flags; index++; } @@ -2396,13 +2387,11 @@ public class ClassReader { final int excessSlots = 4; int expectedParameterSlots = Code.width(sym.type.getParameterTypes()) + excessSlots; - if (parameterNameIndices == null - || parameterNameIndices.length < expectedParameterSlots) { - parameterNameIndices = new int[expectedParameterSlots]; + if (parameterNameIndicesLvt == null + || parameterNameIndicesLvt.length < expectedParameterSlots) { + parameterNameIndicesLvt = new int[expectedParameterSlots]; } else - Arrays.fill(parameterNameIndices, 0); - haveParameterNameIndices = false; - sawMethodParameters = false; + Arrays.fill(parameterNameIndicesLvt, 0); } /** @@ -2417,46 +2406,47 @@ public class ClassReader { * anonymous synthetic parameters. */ void setParameters(MethodSymbol sym, Type jvmType) { - // If we get parameter names from MethodParameters, then we - // don't need to skip. - int firstParam = 0; - if (!sawMethodParameters) { - firstParam = ((sym.flags() & STATIC) == 0) ? 1 : 0; - // the code in readMethod may have skipped the first - // parameter when setting up the MethodType. If so, we - // make a corresponding allowance here for the position of - // the first parameter. Note that this assumes the - // skipped parameter has a width of 1 -- i.e. it is not - // a double width type (long or double.) - if (sym.name == names.init && currentOwner.hasOuterInstance()) { - // Sometimes anonymous classes don't have an outer - // instance, however, there is no reliable way to tell so - // we never strip this$n - if (!currentOwner.name.isEmpty()) - firstParam += 1; - } + int firstParamLvt = ((sym.flags() & STATIC) == 0) ? 1 : 0; + // the code in readMethod may have skipped the first + // parameter when setting up the MethodType. If so, we + // make a corresponding allowance here for the position of + // the first parameter. Note that this assumes the + // skipped parameter has a width of 1 -- i.e. it is not + // a double width type (long or double.) + if (sym.name == names.init && currentOwner.hasOuterInstance()) { + // Sometimes anonymous classes don't have an outer + // instance, however, there is no reliable way to tell so + // we never strip this$n + if (!currentOwner.name.isEmpty()) + firstParamLvt += 1; + } - if (sym.type != jvmType) { - // reading the method attributes has caused the - // symbol's type to be changed. (i.e. the Signature - // attribute.) This may happen if there are hidden - // (synthetic) parameters in the descriptor, but not - // in the Signature. The position of these hidden - // parameters is unspecified; for now, assume they are - // at the beginning, and so skip over them. The - // primary case for this is two hidden parameters - // passed into Enum constructors. - int skip = Code.width(jvmType.getParameterTypes()) - - Code.width(sym.type.getParameterTypes()); - firstParam += skip; - } + if (sym.type != jvmType) { + // reading the method attributes has caused the + // symbol's type to be changed. (i.e. the Signature + // attribute.) This may happen if there are hidden + // (synthetic) parameters in the descriptor, but not + // in the Signature. The position of these hidden + // parameters is unspecified; for now, assume they are + // at the beginning, and so skip over them. The + // primary case for this is two hidden parameters + // passed into Enum constructors. + int skip = Code.width(jvmType.getParameterTypes()) + - Code.width(sym.type.getParameterTypes()); + firstParamLvt += skip; } Set paramNames = new HashSet<>(); ListBuffer params = new ListBuffer<>(); - int nameIndex = firstParam; + // we maintain two index pointers, one for the LocalVariableTable attribute + // and the other for the MethodParameters attribute. + // This is needed as the MethodParameters attribute may contain + // name_index = 0 in which case we want to fall back to the LocalVariableTable. + // In such case, we still want to read the flags from the MethodParameters with that index. + int nameIndexLvt = firstParamLvt; + int nameIndexMp = 0; int annotationIndex = 0; for (Type t: sym.type.getParameterTypes()) { - VarSymbol param = parameter(nameIndex, t, sym, paramNames); + VarSymbol param = parameter(nameIndexMp, nameIndexLvt, t, sym, paramNames); params.append(param); if (parameterAnnotations != null) { ParameterAnnotations annotations = parameterAnnotations[annotationIndex]; @@ -2465,7 +2455,8 @@ public class ClassReader { annotate.normal(new AnnotationCompleter(param, annotations.proxies)); } } - nameIndex += sawMethodParameters ? 1 : Code.width(t); + nameIndexLvt += Code.width(t); + nameIndexMp++; annotationIndex++; } if (parameterAnnotations != null && parameterAnnotations.length != annotationIndex) { @@ -2474,24 +2465,34 @@ public class ClassReader { Assert.checkNull(sym.params); sym.params = params.toList(); parameterAnnotations = null; - parameterNameIndices = null; + parameterNameIndicesLvt = null; + parameterNameIndicesMp = null; parameterAccessFlags = null; } - - // Returns the name for the parameter at position 'index', either using - // names read from the MethodParameters, or by synthesizing a name that - // is not on the 'exclude' list. - private VarSymbol parameter(int index, Type t, MethodSymbol owner, Set exclude) { + /** + * Creates the parameter at the position {@code mpIndex} in the parameter list of the owning method. + * Flags are optionally read from the MethodParameters attribute. + * Names are optionally read from the MethodParameters attribute. If the constant pool index + * of the name is 0, then the name is optionally read from the LocalVariableTable attribute. + * @param mpIndex the index of the parameter in the MethodParameters attribute + * @param lvtIndex the index of the parameter in the LocalVariableTable attribute + */ + private VarSymbol parameter(int mpIndex, int lvtIndex, Type t, MethodSymbol owner, Set exclude) { long flags = PARAMETER; Name argName; - if (parameterAccessFlags != null && index < parameterAccessFlags.length - && parameterAccessFlags[index] != 0) { - flags |= parameterAccessFlags[index]; + if (parameterAccessFlags != null && mpIndex < parameterAccessFlags.length + && parameterAccessFlags[mpIndex] != 0) { + flags |= parameterAccessFlags[mpIndex]; } - if (parameterNameIndices != null && index < parameterNameIndices.length - && parameterNameIndices[index] != 0) { - argName = optPoolEntry(parameterNameIndices[index], poolReader::getName, names.empty); + if (parameterNameIndicesMp != null + // if name_index is 0, then we might still get a name from the LocalVariableTable + && parameterNameIndicesMp[mpIndex] != 0) { + argName = optPoolEntry(parameterNameIndicesMp[mpIndex], poolReader::getName, names.empty); + flags |= NAME_FILLED; + } else if (parameterNameIndicesLvt != null && lvtIndex < parameterNameIndicesLvt.length + && parameterNameIndicesLvt[lvtIndex] != 0) { + argName = optPoolEntry(parameterNameIndicesLvt[lvtIndex], poolReader::getName, names.empty); flags |= NAME_FILLED; } else { String prefix = "arg"; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java index 8cf4e8f1486..c0d8d9b00cc 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2023, 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 @@ -40,7 +40,6 @@ import javax.tools.JavaFileObject; import com.sun.tools.javac.code.*; import com.sun.tools.javac.code.Attribute.RetentionPolicy; import com.sun.tools.javac.code.Directive.*; -import com.sun.tools.javac.code.Source.Feature; import com.sun.tools.javac.code.Symbol.*; import com.sun.tools.javac.code.Type.*; import com.sun.tools.javac.code.Types.SignatureGenerator.InvalidSignatureException; @@ -383,7 +382,7 @@ public class ClassWriter extends ClassFile { /** * Write method parameter names attribute. */ - int writeMethodParametersAttr(MethodSymbol m) { + int writeMethodParametersAttr(MethodSymbol m, boolean writeParamNames) { MethodType ty = m.externalType(types).asMethodType(); final int allparams = ty.argtypes.size(); if (m.params != null && allparams != 0) { @@ -394,7 +393,10 @@ public class ClassWriter extends ClassFile { final int flags = ((int) s.flags() & (FINAL | SYNTHETIC | MANDATED)) | ((int) m.flags() & SYNTHETIC); - databuf.appendChar(poolWriter.putName(s.name)); + if (writeParamNames) + databuf.appendChar(poolWriter.putName(s.name)); + else + databuf.appendChar(0); databuf.appendChar(flags); } // Now write the real parameters @@ -402,7 +404,10 @@ public class ClassWriter extends ClassFile { final int flags = ((int) s.flags() & (FINAL | SYNTHETIC | MANDATED)) | ((int) m.flags() & SYNTHETIC); - databuf.appendChar(poolWriter.putName(s.name)); + if (writeParamNames) + databuf.appendChar(poolWriter.putName(s.name)); + else + databuf.appendChar(0); databuf.appendChar(flags); } // Now write the captured locals @@ -410,7 +415,10 @@ public class ClassWriter extends ClassFile { final int flags = ((int) s.flags() & (FINAL | SYNTHETIC | MANDATED)) | ((int) m.flags() & SYNTHETIC); - databuf.appendChar(poolWriter.putName(s.name)); + if (writeParamNames) + databuf.appendChar(poolWriter.putName(s.name)); + else + databuf.appendChar(0); databuf.appendChar(flags); } endAttr(attrIndex); @@ -1009,9 +1017,12 @@ public class ClassWriter extends ClassFile { endAttr(alenIdx); acount++; } - if (target.hasMethodParameters() && (options.isSet(PARAMETERS) || m.isConstructor() && (m.flags_field & RECORD) != 0)) { - if (!m.isLambdaMethod()) // Per JDK-8138729, do not emit parameters table for lambda bodies. - acount += writeMethodParametersAttr(m); + if (target.hasMethodParameters()) { + if (!m.isLambdaMethod()) { // Per JDK-8138729, do not emit parameters table for lambda bodies. + boolean requiresParamNames = requiresParamNames(m); + if (requiresParamNames || requiresParamFlags(m)) + acount += writeMethodParametersAttr(m, requiresParamNames); + } } acount += writeMemberAttrs(m, false); if (!m.isLambdaMethod()) @@ -1020,6 +1031,25 @@ public class ClassWriter extends ClassFile { endAttrs(acountIdx, acount); } + private boolean requiresParamNames(MethodSymbol m) { + if (options.isSet(PARAMETERS)) + return true; + if (m.isConstructor() && (m.flags_field & RECORD) != 0) + return true; + return false; + } + + private boolean requiresParamFlags(MethodSymbol m) { + if (!m.extraParams.isEmpty()) { + return m.extraParams.stream().anyMatch(p -> (p.flags_field & (SYNTHETIC | MANDATED)) != 0); + } + if (m.params != null) { + // parameter is stored in params for Enum#valueOf(name) + return m.params.stream().anyMatch(p -> (p.flags_field & (SYNTHETIC | MANDATED)) != 0); + } + return false; + } + /** Write code attribute of method. */ void writeCode(Code code) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index 5a9d6ca5705..30aa73095e2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -4153,7 +4153,7 @@ public class JavacParser implements Parser { for (JCVariableDecl param : headerFields) { tmpParams.add(F.at(param) // we will get flags plus annotations from the record component - .VarDef(F.Modifiers(Flags.PARAMETER | Flags.GENERATED_MEMBER | param.mods.flags & Flags.VARARGS, + .VarDef(F.Modifiers(Flags.PARAMETER | Flags.GENERATED_MEMBER | Flags.MANDATED | param.mods.flags & Flags.VARARGS, param.mods.annotations), param.name, param.vartype, null)); } diff --git a/test/jdk/java/lang/reflect/AccessFlag/RequiredMethodParameterFlagTest.java b/test/jdk/java/lang/reflect/AccessFlag/RequiredMethodParameterFlagTest.java new file mode 100644 index 00000000000..7343ccc82b8 --- /dev/null +++ b/test/jdk/java/lang/reflect/AccessFlag/RequiredMethodParameterFlagTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2023, 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. + */ + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.lang.reflect.AccessFlag; +import java.lang.reflect.Executable; +import java.lang.reflect.Parameter; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/* + * @test + * @bug 8292275 + * @summary Test required flags on parameters + * @compile RequiredMethodParameterFlagTest.java + * @run junit RequiredMethodParameterFlagTest + */ +class RequiredMethodParameterFlagTest { + + private static final Set CHECKED_FLAGS = Set.of(AccessFlag.MANDATED, AccessFlag.SYNTHETIC); + + static Stream testCases() throws ReflectiveOperationException { + Set mandated = Set.of(AccessFlag.MANDATED); + Set synthetic = Set.of(AccessFlag.SYNTHETIC); + + return Stream.of( + // test for implicit parameters + // inner class + Arguments.of(Outer.Inner.class.getDeclaredConstructors()[0], + List.of(mandated, Set.of())), + // anonymous class extending an inner class + Arguments.of(Class.forName("Outer$1") + .getDeclaredConstructors()[0], + List.of(mandated, Set.of(), Set.of())), + // anonymous class + Arguments.of(Class.forName("Outer$2") + .getDeclaredConstructors()[0], + List.of(mandated)), + // enum class + Arguments.of(Outer.MyEnum.class.getDeclaredMethod("valueOf", String.class), + List.of(mandated)), + // record class + Arguments.of(Outer.MyRecord.class.getDeclaredConstructors()[0], + List.of(mandated, mandated)), + // local class + Arguments.of(Class.forName("Outer$1Task") + .getDeclaredConstructors()[0], + List.of(mandated, Set.of(), synthetic)), + // test for synthetic parameters + // assuming javac creates two synthetic parameters corresponding to + // Enum(String name, int ordinal) + Arguments.of(Outer.MyEnum.class.getDeclaredConstructors()[0], + List.of(synthetic, synthetic, Set.of(), Set.of())) + ); + } + + @ParameterizedTest + @MethodSource("testCases") + void check(Executable method, List> paramFlags) { + Parameter[] parameters = method.getParameters(); + assertEquals(paramFlags.size(), parameters.length, () -> "Parameter count of " + method); + + for (int i = 0; i < parameters.length; i++) { + Set expected = new HashSet<>(paramFlags.get(i)); + expected.retainAll(CHECKED_FLAGS); + Set found = new HashSet<>(parameters[i].accessFlags()); + found.retainAll(CHECKED_FLAGS); + final int index = i; + assertEquals(expected, found, () -> "Parameter " + index + " in " + method); + } + } +} + +// keep this in sync with test/langtools/tools/javac/RequiredParameterFlags/ImplicitParameters.java +class Outer { + class Inner { + public Inner(Inner notMandated) {} + } + + Inner anonymousInner = this.new Inner(null) {}; + + Object anonymous = new Object() {}; + + private void instanceMethod(int i) { + class Task implements Runnable { + final int j; + + Task(int j) { + this.j = j; + } + + @Override + public void run() { + System.out.println(Outer.this.toString() + (i * j)); + } + } + + new Task(5).run(); + } + + enum MyEnum { + ; + MyEnum(String s, int i) {} + } + + record MyRecord(int a, Object b) { + MyRecord {} + } +} diff --git a/test/langtools/tools/javac/RequiredParameterFlags/ImplicitParameters.java b/test/langtools/tools/javac/RequiredParameterFlags/ImplicitParameters.java new file mode 100644 index 00000000000..3683ae42510 --- /dev/null +++ b/test/langtools/tools/javac/RequiredParameterFlags/ImplicitParameters.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2023, 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 8292275 + * @summary check that implicit parameter flags are available by default + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.code + * jdk.jdeps/com.sun.tools.classfile + * @run main ImplicitParameters + */ + +import com.sun.tools.classfile.ClassFile; +import com.sun.tools.classfile.ConstantPoolException; +import com.sun.tools.classfile.MethodParameters_attribute; +import com.sun.tools.javac.code.Flags; +import toolbox.Assert; +import toolbox.JavacTask; +import toolbox.Task; +import toolbox.TestRunner; +import toolbox.ToolBox; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.constant.ConstantDescs; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ImplicitParameters extends TestRunner { + private static final int CHECKED_FLAGS = Flags.MANDATED | Flags.SYNTHETIC; + private static final int NO_FLAGS = 0; + + public ImplicitParameters() { + super(System.err); + } + + public static void main(String[] args) throws Exception { + new ImplicitParameters().runTests(); + } + + @Override + protected void runTests() throws Exception { + Path base = Path.of(".").toAbsolutePath(); + compileClasses(base); + runTests(method -> new Object[]{ readClassFile(base.resolve("classes"), method) }); + } + + private void compileClasses(Path base) throws IOException { + // Keep this in sync with test/jdk/java/lang/reflect/AccessFlag/RequiredMethodParameterFlagTest.java + String outer = """ + class Outer { + class Inner { + public Inner(Inner notMandated) {} + } + + Inner anonymousInner = this.new Inner(null) {}; + + Object anonymous = new Object() {}; + + private void instanceMethod(int i) { + class Task implements Runnable { + final int j; + + Task(int j) { + this.j = j; + } + + @Override + public void run() { + System.out.println(Outer.this.toString() + (i * j)); + } + } + + new Task(5).run(); + } + + enum MyEnum { + ; + MyEnum(String s, int i) {} + } + + record MyRecord(int a, Object b) { + MyRecord {} + } + } + """; + Path src = base.resolve("src"); + ToolBox tb = new ToolBox(); + tb.writeJavaFiles(src, outer); + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .files(tb.findJavaFiles(src)) + .outdir(classes) + .run(Task.Expect.SUCCESS) + .writeAll(); + } + + private ClassFile readClassFile(Path classes, Method method) { + String className = method.getAnnotation(ClassName.class).value(); + try { + return ClassFile.read(classes.resolve("Outer$" + className + ".class")); + } catch (IOException | ConstantPoolException e) { + throw new RuntimeException(e); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @interface ClassName { + String value(); + } + + @Test + @ClassName("Inner") + public void testInnerClassConstructor(ClassFile classFile) { + checkParameters(classFile.methods[0], Flags.MANDATED, 0); + } + + @Test + @ClassName("1Task") + public void testLocalClassConstructor(ClassFile classFile) throws ConstantPoolException { + for (com.sun.tools.classfile.Method method : classFile.methods) { + if (method.getName(classFile.constant_pool).equals(ConstantDescs.INIT_NAME)) { + checkParameters(method, Flags.MANDATED, NO_FLAGS, Flags.SYNTHETIC); + break; + } + } + } + + @Test + @ClassName("1") + public void testAnonymousClassExtendingInnerClassConstructor(ClassFile classFile) { + checkParameters(classFile.methods[0], Flags.MANDATED, NO_FLAGS, NO_FLAGS); + } + + @Test + @ClassName("2") + public void testAnonymousClassConstructor(ClassFile classFile) { + checkParameters(classFile.methods[0], Flags.MANDATED); + } + + @Test + @ClassName("MyEnum") + public void testValueOfInEnum(ClassFile classFile) throws ConstantPoolException { + for (com.sun.tools.classfile.Method method : classFile.methods) { + if (method.getName(classFile.constant_pool).equals("valueOf")) { + checkParameters(method, Flags.MANDATED); + break; + } + } + } + + @Test + @ClassName("MyEnum") + public void testEnumClassConstructor(ClassFile classFile) throws ConstantPoolException { + for (com.sun.tools.classfile.Method method : classFile.methods) { + if (method.getName(classFile.constant_pool).equals(ConstantDescs.INIT_NAME)) { + checkParameters(method, Flags.SYNTHETIC, Flags.SYNTHETIC, NO_FLAGS, NO_FLAGS); + break; + } + } + } + + @Test + @ClassName("MyRecord") + public void testCompactConstructor(ClassFile classFile) { + checkParameters(classFile.methods[0], Flags.MANDATED, Flags.MANDATED); + } + + private void checkParameters(com.sun.tools.classfile.Method method, int... parametersFlags) { + MethodParameters_attribute methodParameters = (MethodParameters_attribute) method.attributes.get("MethodParameters"); + Assert.checkNonNull(methodParameters, "MethodParameters attribute must be present"); + MethodParameters_attribute.Entry[] table = methodParameters.method_parameter_table; + Assert.check(table.length == parametersFlags.length, () -> "Expected " + parametersFlags.length + + " MethodParameters entries, found " + table.length); + for (int i = 0; i < methodParameters.method_parameter_table_length; i++) { + int foundFlags = table[i].flags & CHECKED_FLAGS; + int desiredFlags = parametersFlags[i] & CHECKED_FLAGS; + Assert.check(foundFlags == desiredFlags, () -> "Expected mandated and synthethic flags to be " + + convertFlags(desiredFlags) + ", found " + convertFlags(foundFlags)); + } + } + + private static String convertFlags(int flags) { + return ((flags & Flags.MANDATED) == Flags.MANDATED) + " and " + ((flags & Flags.SYNTHETIC) == Flags.SYNTHETIC); + } +} diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/classfile/AnnotatedExtendsTest.java b/test/langtools/tools/javac/annotations/typeAnnotations/classfile/AnnotatedExtendsTest.java index 3c785debf42..450e2b5956a 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/classfile/AnnotatedExtendsTest.java +++ b/test/langtools/tools/javac/annotations/typeAnnotations/classfile/AnnotatedExtendsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2023, 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 @@ -59,7 +59,7 @@ public class AnnotatedExtendsTest { .classes(classPath.toString()) .run() .getOutput(Task.OutputKind.DIRECT); - if (!javapOut.contains("0: #20(): CLASS_EXTENDS, type_index=65535")) + if (!javapOut.contains("0: #21(): CLASS_EXTENDS, type_index=65535")) throw new AssertionError("Expected output missing: " + javapOut); } } diff --git a/test/langtools/tools/javac/processing/model/util/elements/MethodParameters/ImplicitParametersProcessor.java b/test/langtools/tools/javac/processing/model/util/elements/MethodParameters/ImplicitParametersProcessor.java new file mode 100644 index 00000000000..526aa53de1e --- /dev/null +++ b/test/langtools/tools/javac/processing/model/util/elements/MethodParameters/ImplicitParametersProcessor.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023, 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 8292275 + * @summary Verify specific executables in enums and records have mandated parameters + * @library /tools/javac/lib + * @modules java.compiler + * jdk.compiler + * @build JavacTestingAbstractProcessor ImplicitParametersProcessor + * @compile -processor ImplicitParametersProcessor -proc:only ImplicitParametersProcessor.java + */ + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.Elements; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static javax.lang.model.util.ElementFilter.constructorsIn; +import static javax.lang.model.util.ElementFilter.methodsIn; +import static javax.lang.model.util.ElementFilter.typesIn; + +public class ImplicitParametersProcessor extends JavacTestingAbstractProcessor { + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + return true; + } + boolean hasError = false; + for (TypeElement typeElement : typesIn(roundEnv.getRootElements())) { + for (TypeElement innerType : typesIn(typeElement.getEnclosedElements())) { + System.out.println("Visiting " + innerType); + ExpectedOrigin[] expectedOrigins = innerType.getAnnotationsByType(ExpectedOrigin.class); + hasError |= checkAllExecutables(innerType, Arrays.stream(expectedOrigins) + .collect(Collectors.toMap(ExpectedOrigin::method, ExpectedOrigin::origins))); + } + } + if (hasError) { + throw new IllegalStateException("Wrong element origins found"); + } + return true; + } + + boolean checkAllExecutables(TypeElement element, Map expectations) { + boolean hasError = false; + for (ExecutableElement executable : constructorsIn(element.getEnclosedElements())) { + hasError |= checkExecutable(expectations, executable); + } + for (ExecutableElement executable : methodsIn(element.getEnclosedElements())) { + hasError |= checkExecutable(expectations, executable); + } + return hasError; + } + + private boolean checkExecutable(Map expectations, ExecutableElement executable) { + System.out.println("Looking at executable " + executable); + Elements.Origin[] origins = expectations.get(executable.getSimpleName().toString()); + if (origins == null) { + System.out.println("ignoring this executable due to missing expectations"); + return false; + } + List parameters = executable.getParameters(); + boolean hasError = false; + for (int i = 0; i < parameters.size(); i++) { + VariableElement parameter = parameters.get(i); + Elements.Origin origin = eltUtils.getOrigin(parameter); + if (origin != origins[i]) { + System.err.println("ERROR: Wrong origin for " + executable + ". Expected: " + origins[i] + " but got " + origin + " at index " + i); + hasError = true; + } + } + return hasError; + } + + // the valueOf(String) method has one mandated parameter + @ExpectedOrigin(method = "valueOf", origins = {Elements.Origin.MANDATED}) + enum MyEnum {} + + // the parameters of a compact record constructor are mandated + @ExpectedOrigin(method = "", origins = {Elements.Origin.MANDATED, Elements.Origin.MANDATED}) + record MyRecord(int a, Object b) { + MyRecord {} + } + + @interface ExpectedOrigin { + String method(); + Elements.Origin[] origins(); + } +} diff --git a/test/langtools/tools/javap/AnnoTest.java b/test/langtools/tools/javap/AnnoTest.java index 649d7e7b38a..e7bf4074915 100644 --- a/test/langtools/tools/javap/AnnoTest.java +++ b/test/langtools/tools/javap/AnnoTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2023, 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 @@ -49,50 +49,50 @@ public class AnnoTest { expect(out, "RuntimeVisibleAnnotations:\n" + - " 0: #17(#18=B#19)\n" + + " 0: #18(#19=B#20)\n" + " AnnoTest$ByteAnno(\n" + " value=(byte) 42\n" + " )\n" + - " 1: #20(#18=S#21)\n" + + " 1: #21(#19=S#22)\n" + " AnnoTest$ShortAnno(\n" + " value=(short) 3\n" + " )"); expect(out, "RuntimeInvisibleAnnotations:\n" + - " 0: #23(#18=[J#24,J#26,J#28,J#30,J#32])\n" + + " 0: #24(#19=[J#25,J#27,J#29,J#31,J#33])\n" + " AnnoTest$ArrayAnno(\n" + " value=[1l,2l,3l,4l,5l]\n" + " )\n" + - " 1: #34(#18=Z#35)\n" + + " 1: #35(#19=Z#36)\n" + " AnnoTest$BooleanAnno(\n" + " value=false\n" + " )\n" + - " 2: #36(#37=c#38)\n" + + " 2: #37(#38=c#39)\n" + " AnnoTest$ClassAnno(\n" + " type=class Ljava/lang/Object;\n" + " )\n" + - " 3: #39(#40=e#41.#42)\n" + + " 3: #40(#41=e#42.#43)\n" + " AnnoTest$EnumAnno(\n" + " kind=Ljavax/lang/model/element/ElementKind;.PACKAGE\n" + " )\n" + - " 4: #43(#18=I#44)\n" + + " 4: #44(#19=I#45)\n" + " AnnoTest$IntAnno(\n" + " value=2\n" + " )\n" + - " 5: #45()\n" + + " 5: #46()\n" + " AnnoTest$IntDefaultAnno\n" + - " 6: #46(#47=s#48)\n" + + " 6: #47(#48=s#49)\n" + " AnnoTest$NameAnno(\n" + " name=\"NAME\"\n" + " )\n" + - " 7: #49(#50=D#51,#53=F#54)\n" + + " 7: #50(#51=D#52,#54=F#55)\n" + " AnnoTest$MultiAnno(\n" + " d=3.14159d\n" + " f=2.71828f\n" + " )\n" + - " 8: #55()\n" + + " 8: #56()\n" + " AnnoTest$SimpleAnno\n" + - " 9: #56(#18=@#43(#18=I#57))\n" + + " 9: #57(#19=@#44(#19=I#58))\n" + " AnnoTest$AnnoAnno(\n" + " value=@AnnoTest$IntAnno(\n" + " value=5\n" + @@ -100,7 +100,7 @@ public class AnnoTest { " )"); expect(out, "RuntimeInvisibleTypeAnnotations:\n" + - " 0: #59(): CLASS_EXTENDS, type_index=0\n" + + " 0: #60(): CLASS_EXTENDS, type_index=0\n" + " AnnoTest$TypeAnno"); if (errors > 0)