8292275: javac does not emit SYNTHETIC and MANDATED flags for parameters by default

Co-authored-by: Chen Liang <liach@openjdk.org>
Reviewed-by: vromero, jwaters
This commit is contained in:
Hannes Greule 2023-04-30 07:34:09 +00:00 committed by Julian Waters
parent 6d6d00b69c
commit b3dbf28bc0
9 changed files with 602 additions and 105 deletions

View File

@ -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);

View File

@ -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<Name> paramNames = new HashSet<>();
ListBuffer<VarSymbol> 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<Name> 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<Name> 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";

View File

@ -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) {

View File

@ -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));
}

View File

@ -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<AccessFlag> CHECKED_FLAGS = Set.of(AccessFlag.MANDATED, AccessFlag.SYNTHETIC);
static Stream<Arguments> testCases() throws ReflectiveOperationException {
Set<AccessFlag> mandated = Set.of(AccessFlag.MANDATED);
Set<AccessFlag> 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<Set<AccessFlag>> paramFlags) {
Parameter[] parameters = method.getParameters();
assertEquals(paramFlags.size(), parameters.length, () -> "Parameter count of " + method);
for (int i = 0; i < parameters.length; i++) {
Set<AccessFlag> expected = new HashSet<>(paramFlags.get(i));
expected.retainAll(CHECKED_FLAGS);
Set<AccessFlag> 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 {}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<? extends TypeElement> 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<String, Elements.Origin[]> 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<String, Elements.Origin[]> 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<? extends VariableElement> 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 = "<init>", origins = {Elements.Origin.MANDATED, Elements.Origin.MANDATED})
record MyRecord(int a, Object b) {
MyRecord {}
}
@interface ExpectedOrigin {
String method();
Elements.Origin[] origins();
}
}

View File

@ -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)