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:
parent
6d6d00b69c
commit
b3dbf28bc0
@ -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);
|
||||
|
@ -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";
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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 {}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user