diff --git a/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java b/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java index 08d5cb85ef2..fe5938ce0e3 100644 --- a/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java +++ b/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java @@ -46,6 +46,8 @@ import java.io.OutputStream; import java.io.StringWriter; import java.io.Writer; import java.lang.classfile.*; +import java.lang.classfile.TypeAnnotation.TargetInfo; +import java.lang.classfile.TypeAnnotation.TypePathComponent; import java.lang.classfile.attribute.*; import java.lang.classfile.constantpool.ClassEntry; import java.lang.classfile.constantpool.ConstantPoolBuilder; @@ -991,6 +993,12 @@ public class CreateSymbols { if (desc.runtimeAnnotations != null && !desc.runtimeAnnotations.isEmpty()) { builder.accept(RuntimeVisibleAnnotationsAttribute.of(createAnnotations(desc.runtimeAnnotations))); } + if (desc.classTypeAnnotations != null && !desc.classTypeAnnotations.isEmpty()) { + builder.accept(RuntimeInvisibleTypeAnnotationsAttribute.of(createTypeAnnotations(desc.classTypeAnnotations))); + } + if (desc.runtimeTypeAnnotations != null && !desc.runtimeTypeAnnotations.isEmpty()) { + builder.accept(RuntimeVisibleTypeAnnotationsAttribute.of(createTypeAnnotations(desc.runtimeTypeAnnotations))); + } } private List createAnnotations(List desc) { @@ -1066,6 +1074,44 @@ public class CreateSymbols { default -> throw new IllegalArgumentException(value.getClass().getName()); }; } + + private List createTypeAnnotations(List desc) { + return desc.stream().map(this::createTypeAnnotation).collect(Collectors.toList()); + } + + private TypeAnnotation createTypeAnnotation(TypeAnnotationDescription desc) { + Annotation baseAnn = createAnnotation(desc.annotation); + TargetInfo targetInfo = switch ((String) desc.targetInfo.get("targetType")) { + case "CLASS_TYPE_PARAMETER" -> //TODO: test! + TargetInfo.ofClassTypeParameter((int) desc.targetInfo.get("typeParameterIndex")); + case "METHOD_TYPE_PARAMETER" -> + TargetInfo.ofMethodTypeParameter((int) desc.targetInfo.get("typeParameterIndex")); + case "CLASS_EXTENDS" -> + TargetInfo.ofClassExtends((int) desc.targetInfo.get("supertypeIndex")); + case "CLASS_TYPE_PARAMETER_BOUND" -> + TargetInfo.ofClassTypeParameterBound((int) desc.targetInfo.get("typeParameterIndex"), + (int) desc.targetInfo.get("boundIndex")); + case "METHOD_TYPE_PARAMETER_BOUND" -> + TargetInfo.ofMethodTypeParameterBound((int) desc.targetInfo.get("typeParameterIndex"), + (int) desc.targetInfo.get("boundIndex")); + case "METHOD_RETURN" -> + TargetInfo.ofMethodReturn(); + case "METHOD_RECEIVER" -> + TargetInfo.ofMethodReceiver(); + case "METHOD_FORMAL_PARAMETER" -> + TargetInfo.ofMethodFormalParameter((int) desc.targetInfo.get("formalParameterIndex")); + case "THROWS" -> + TargetInfo.ofThrows((int) desc.targetInfo.get("throwsTargetIndex")); + case "FIELD" -> + TargetInfo.ofField(); + case String targetType -> + throw new IllegalStateException("Unsupported targetType: " + targetType); + }; + + List typePath = desc.typePath.stream().map(d -> TypePathComponent.of(TypePathComponent.Kind.valueOf(d.tag()), d.index())).toList(); + + return TypeAnnotation.of(targetInfo, typePath, baseAnn); + } // // @@ -2213,7 +2259,10 @@ public class CreateSymbols { chd.permittedSubclasses = a.permittedSubclasses().stream().map(ClassEntry::asInternalName).collect(Collectors.toList()); } case ModuleMainClassAttribute a -> ((ModuleHeaderDescription) feature).moduleMainClass = a.mainClass().asInternalName(); - case RuntimeVisibleTypeAnnotationsAttribute a -> {/* do nothing for now */} + case RuntimeInvisibleTypeAnnotationsAttribute a -> + feature.classTypeAnnotations = typeAnnotations2Descriptions(a.annotations()); + case RuntimeVisibleTypeAnnotationsAttribute a -> + feature.runtimeTypeAnnotations = typeAnnotations2Descriptions(a.annotations()); default -> throw new IllegalArgumentException("Unhandled attribute: " + attr.attributeName()); // Do nothing } @@ -2270,6 +2319,31 @@ public class CreateSymbols { return new AnnotationDescription(annotationType, values); } + + private List typeAnnotations2Descriptions(List annos) { + return annos.stream().map(ta -> { + TypeAnnotationDescription desc = new TypeAnnotationDescription(); + desc.annotation = annotation2Description(ta.annotation()); + desc.targetInfo = new HashMap<>(); + desc.targetInfo.put("targetType", ta.targetInfo().targetType().name()); + switch (ta.targetInfo()) { + case TypeAnnotation.TypeParameterTarget tpt -> desc.targetInfo.put("typeParameterIndex", tpt.typeParameterIndex()); + case TypeAnnotation.SupertypeTarget st -> desc.targetInfo.put("supertypeIndex", st.supertypeIndex()); + case TypeAnnotation.TypeParameterBoundTarget tpbt -> { + desc.targetInfo.put("typeParameterIndex", tpbt.typeParameterIndex()); + desc.targetInfo.put("boundIndex", tpbt.boundIndex()); + } + case TypeAnnotation.EmptyTarget _ -> { + // nothing to write + } + case TypeAnnotation.FormalParameterTarget fpt -> desc.targetInfo.put("formalParameterIndex", fpt.formalParameterIndex()); + case TypeAnnotation.ThrowsTarget tt -> desc.targetInfo.put("throwsTargetIndex", tt.throwsTargetIndex()); + default -> throw new IllegalStateException(ta.targetInfo().targetType().name()); + } + desc.typePath = ta.targetPath().stream().map(tpc -> new TypeAnnotationDescription.TypePathComponentDesc(tpc.typePathKind().name(), tpc.typeArgumentIndex())).toList(); + return desc; + }).toList(); + } // protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) { @@ -2391,6 +2465,8 @@ public class CreateSymbols { String versions = ""; List classAnnotations; List runtimeAnnotations; + List classTypeAnnotations; + List runtimeTypeAnnotations; protected void writeAttributes(Appendable output) throws IOException { if (flags != 0) @@ -2413,6 +2489,18 @@ public class CreateSymbols { output.append(quote(a.toString(), false)); } } + if (classTypeAnnotations != null && !classTypeAnnotations.isEmpty()) { + output.append(" classTypeAnnotations "); + for (TypeAnnotationDescription a : classTypeAnnotations) { + output.append(quote(a.toString(), false)); + } + } + if (runtimeTypeAnnotations != null && !runtimeTypeAnnotations.isEmpty()) { + output.append(" runtimeTypeAnnotations "); + for (TypeAnnotationDescription a : runtimeTypeAnnotations) { + output.append(quote(a.toString(), false)); + } + } } protected boolean shouldIgnore(String baselineVersion, String version) { @@ -2442,6 +2530,14 @@ public class CreateSymbols { if (inRuntimeAnnotations != null) { runtimeAnnotations = parseAnnotations(inRuntimeAnnotations, new int[1]); } + String inClassTypeAnnotations = reader.attributes.get("classTypeAnnotations"); + if (inClassTypeAnnotations != null) { + classTypeAnnotations = parseTypeAnnotations(inClassTypeAnnotations, new int[1]); + } + String inRuntimeTypeAnnotations = reader.attributes.get("runtimeTypeAnnotations"); + if (inRuntimeTypeAnnotations != null) { + runtimeTypeAnnotations = parseTypeAnnotations(inRuntimeTypeAnnotations, new int[1]); + } } public abstract boolean read(LineBasedReader reader) throws IOException; @@ -2454,6 +2550,8 @@ public class CreateSymbols { hash = 89 * hash + Objects.hashCode(this.signature); hash = 89 * hash + listHashCode(this.classAnnotations); hash = 89 * hash + listHashCode(this.runtimeAnnotations); + hash = 89 * hash + listHashCode(this.classTypeAnnotations); + hash = 89 * hash + listHashCode(this.runtimeTypeAnnotations); return hash; } @@ -2481,6 +2579,12 @@ public class CreateSymbols { if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) { return false; } + if (!listEquals(this.classTypeAnnotations, other.classTypeAnnotations)) { + return false; + } + if (!listEquals(this.runtimeTypeAnnotations, other.runtimeTypeAnnotations)) { + return false; + } return true; } @@ -3285,6 +3389,8 @@ public class CreateSymbols { hash = 59 * hash + Objects.hashCode(this.descriptor); hash = 59 * hash + Objects.hashCode(this.thrownTypes); hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue); + hash = 59 * hash + Objects.hashCode(this.classParameterAnnotations); + hash = 59 * hash + Objects.hashCode(this.runtimeParameterAnnotations); return hash; } @@ -3309,6 +3415,12 @@ public class CreateSymbols { if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) { return false; } + if (!Objects.equals(this.classParameterAnnotations, other.classParameterAnnotations)) { + return false; + } + if (!Objects.equals(this.runtimeParameterAnnotations, other.runtimeParameterAnnotations)) { + return false; + } return true; } @@ -3636,6 +3748,40 @@ public class CreateSymbols { } } + static final class TypeAnnotationDescription { + AnnotationDescription annotation; + Map targetInfo; + List typePath; + + public TypeAnnotationDescription() { + } + + public TypeAnnotationDescription(AnnotationDescription annotation, Map targetInfo, List typePath) { + this.annotation = annotation; + this.targetInfo = targetInfo; + this.typePath = typePath; + } + + @Override + public String toString() { + return annotation.toString() + "{" + targetInfo.entrySet().stream().map(e -> e.getKey() + "=" + quote(printValue(e.getValue()), false)).collect(Collectors.joining(",")) + "}" + + (!typePath.isEmpty() ? "[" + typePath.stream().map(desc -> desc.tag + ":" + desc.index).collect(Collectors.joining(",")) + "]" : ""); + } + + private String printValue(Object obj) { + if (obj instanceof String s) { + return "\"" + s + "\""; + } else if (obj instanceof Integer i) { + return "I" + String.valueOf(i); + } else { + throw new IllegalStateException("Unsupported value: " + obj.getClass()); + } + } + + //TODO: path + record TypePathComponentDesc(String tag, int index) {} + } + static final class EnumConstant { String type; String constant; @@ -3975,23 +4121,69 @@ public class CreateSymbols { private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) { String className = className(value, valuePointer); - Map attribute2Value = new HashMap<>(); + Map attribute2Value = Map.of(); if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') { - while (value.charAt(valuePointer[0]) != ')') { + attribute2Value = parseMap(value, valuePointer, ')'); + } + + return new AnnotationDescription(className, attribute2Value); + } + + private static Map parseMap(String value, int[] valuePointer, char endBracket) { + Map attribute2Value = new HashMap<>(); + + while (value.charAt(valuePointer[0]) != endBracket) { + int nameStart = ++valuePointer[0]; + + while (value.charAt(valuePointer[0]++) != '='); + + String name = value.substring(nameStart, valuePointer[0] - 1); + + attribute2Value.put(name, parseAnnotationValue(value, valuePointer)); + } + + valuePointer[0]++; + + return attribute2Value; + } + + public static List parseTypeAnnotations(String encoded, int[] pointer) { + List result = new ArrayList<>(); + + while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') { + pointer[0]++; + result.add(parseTypeAnnotation(encoded, pointer)); + } + + return result; + } + + private static TypeAnnotationDescription parseTypeAnnotation(String value, int[] valuePointer) { + AnnotationDescription ann = parseAnnotation(value, valuePointer); + Map targetInfo = Map.of(); + + if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '{') { + targetInfo = parseMap(value, valuePointer, '}'); + } + + List typePath = new ArrayList<>(); + + if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '[') { + while (value.charAt(valuePointer[0]) != ']') { int nameStart = ++valuePointer[0]; - while (value.charAt(valuePointer[0]++) != '='); + while (value.charAt(valuePointer[0]++) != ':'); String name = value.substring(nameStart, valuePointer[0] - 1); - attribute2Value.put(name, parseAnnotationValue(value, valuePointer)); + typePath.add(new TypeAnnotationDescription.TypePathComponentDesc(name, Integer.parseInt(readDigits(value, valuePointer)))); } valuePointer[0]++; } - return new AnnotationDescription(className, attribute2Value); + return new TypeAnnotationDescription(ann, targetInfo, typePath); } // diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java index 53abf99ed7d..2d1ebcfebe8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java @@ -399,6 +399,11 @@ public class Flags { */ public static final long RESTRICTED = 1L<<62; // MethodSymbols + /** + * Flag to indicate parameters that require identity. + */ + public static final long REQUIRES_IDENTITY = 1L<<62; // VarSymbols (parameters) + /** * Flag to indicate type annotations have been queued for field initializers. */ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java index 54b3fe8a562..e5eac0786f6 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java @@ -969,7 +969,8 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem boolean isCurrentSymbolsAnnotation(Attribute.TypeCompound anno, int index) { return (anno.position.type == TargetType.CLASS_TYPE_PARAMETER || anno.position.type == TargetType.METHOD_TYPE_PARAMETER) && - anno.position.parameter_index == index; + anno.position.parameter_index == index && + anno.type.tsym.flatName() != name.table.names.requiresIdentityInternal; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java index 2079532eb2c..762932fe904 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java @@ -384,9 +384,15 @@ public class Symtab { // Enter a synthetic class that is used to mark classes in ct.sym. // This class does not have a class file. private Type enterSyntheticAnnotation(String name) { + return enterSyntheticAnnotation(names.fromString(name)); + } + + // Enter a synthetic class that is used to mark classes in ct.sym. + // This class does not have a class file. + private Type enterSyntheticAnnotation(Name name) { // for now, leave the module null, to prevent problems from synthesizing the // existence of a class in any specific module, including noModule - ClassType type = (ClassType)enterClass(java_base, names.fromString(name)).type; + ClassType type = (ClassType)enterClass(java_base, name).type; ClassSymbol sym = (ClassSymbol)type.tsym; sym.completer = Completer.NULL_COMPLETER; sym.flags_field = PUBLIC|ACYCLIC|ANNOTATION|INTERFACE; @@ -613,7 +619,7 @@ public class Symtab { valueBasedType = enterClass("jdk.internal.ValueBased"); valueBasedInternalType = enterSyntheticAnnotation("jdk.internal.ValueBased+Annotation"); requiresIdentityType = enterClass("jdk.internal.RequiresIdentity"); - requiresIdentityInternalType = enterSyntheticAnnotation("jdk.internal.RequiresIdentity+Annotation"); + requiresIdentityInternalType = enterSyntheticAnnotation(names.requiresIdentityInternal); classDescType = enterClass("java.lang.constant.ClassDesc"); enumDescType = enterClass("java.lang.Enum$EnumDesc"); // For serialization lint checking diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java index 35672b2b1bc..3da83248436 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java @@ -384,6 +384,12 @@ public class Annotate { && types.isSameType(c.type, syms.restrictedType)) { toAnnotate.flags_field |= Flags.RESTRICTED; } + + if (!c.type.isErroneous() + && toAnnotate.kind == VAR + && types.isSameType(c.type, syms.requiresIdentityType)) { + toAnnotate.flags_field |= Flags.REQUIRES_IDENTITY; + } } List buf = List.nil(); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 05730e3f257..89ab5b13dd1 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -5729,14 +5729,14 @@ public class Check { if (!argExps.isEmpty() && msym instanceof MethodSymbol ms && ms.params != null) { VarSymbol lastParam = ms.params.head; for (VarSymbol param: ms.params) { - if (param.attribute(syms.requiresIdentityType.tsym) != null && argExps.head.type.isValueBased()) { + if ((param.flags_field & REQUIRES_IDENTITY) != 0 && argExps.head.type.isValueBased()) { lint.logIfEnabled(argExps.head.pos(), LintWarnings.AttemptToUseValueBasedWhereIdentityExpected); } lastParam = param; argExps = argExps.tail; } while (argExps != null && !argExps.isEmpty() && lastParam != null) { - if (lastParam.attribute(syms.requiresIdentityType.tsym) != null && argExps.head.type.isValueBased()) { + if ((lastParam.flags_field & REQUIRES_IDENTITY) != 0 && argExps.head.type.isValueBased()) { lint.logIfEnabled(argExps.head.pos(), LintWarnings.AttemptToUseValueBasedWhereIdentityExpected); } argExps = argExps.tail; @@ -5813,7 +5813,7 @@ public class Check { SymbolMetadata sm = t.tsym.getMetadata(); if (sm != null && !t.getTypeArguments().isEmpty()) { if (sm.getTypeAttributes().stream() - .filter(ta -> ta.type.tsym == syms.requiresIdentityType.tsym && + .filter(ta -> isRequiresIdentityAnnotation(ta.type.tsym) && t.getTypeArguments().get(ta.position.parameter_index) != null && t.getTypeArguments().get(ta.position.parameter_index).isValueBased()).findAny().isPresent()) { requiresWarning = true; @@ -5838,11 +5838,16 @@ public class Check { } if (sm != null) sm.getTypeAttributes().stream() - .filter(ta -> (ta.type.tsym == syms.requiresIdentityType.tsym) && + .filter(ta -> isRequiresIdentityAnnotation(ta.type.tsym) && typeParamTrees.get(ta.position.parameter_index).type != null && typeParamTrees.get(ta.position.parameter_index).type.isValueBased()) .forEach(ta -> lint.logIfEnabled(typeParamTrees.get(ta.position.parameter_index).pos(), CompilerProperties.LintWarnings.AttemptToUseValueBasedWhereIdentityExpected)); } } + + private boolean isRequiresIdentityAnnotation(TypeSymbol annoType) { + return annoType == syms.requiresIdentityType.tsym || + annoType.flatName() == syms.requiresIdentityInternalType.tsym.flatName(); + } } 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 0173ab109d7..cf751ff6b30 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 @@ -1556,6 +1556,9 @@ public class ClassReader { } else if (proxy.type.tsym.flatName() == syms.restrictedInternalType.tsym.flatName()) { Assert.check(sym.kind == MTH); sym.flags_field |= RESTRICTED; + } else if (proxy.type.tsym.flatName() == syms.requiresIdentityInternalType.tsym.flatName()) { + Assert.check(sym.kind == VAR); + sym.flags_field |= REQUIRES_IDENTITY; } else { if (proxy.type.tsym == syms.annotationTargetType.tsym) { target = proxy; @@ -1572,6 +1575,9 @@ public class ClassReader { } else if (proxy.type.tsym == syms.restrictedType.tsym) { Assert.check(sym.kind == MTH); sym.flags_field |= RESTRICTED; + } else if (proxy.type.tsym == syms.requiresIdentityType.tsym) { + Assert.check(sym.kind == VAR); + sym.flags_field |= REQUIRES_IDENTITY; } proxies.append(proxy); } @@ -2809,9 +2815,8 @@ public class ClassReader { params.append(param); if (parameterAnnotations != null) { ParameterAnnotations annotations = parameterAnnotations[annotationIndex]; - if (annotations != null && annotations.proxies != null - && !annotations.proxies.isEmpty()) { - annotate.normal(new AnnotationCompleter(param, annotations.proxies)); + if (annotations != null && annotations.proxies != null) { + attachAnnotations(param, annotations.proxies); } } nameIndexLvt += Code.width(t); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java index d9aec7c4592..f5df8baddbd 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java @@ -228,6 +228,9 @@ public class Names { public final Name enumSwitch; public final Name enumConstant; + // special annotation names + public final Name requiresIdentityInternal; + public final Name.Table table; @SuppressWarnings("this-escape") @@ -412,6 +415,9 @@ public class Names { typeSwitch = fromString("typeSwitch"); enumSwitch = fromString("enumSwitch"); enumConstant = fromString("enumConstant"); + + // special annotations: + requiresIdentityInternal = fromString("jdk.internal.RequiresIdentity+Annotation"); } protected Name.Table createTable(Options options) { diff --git a/test/langtools/tools/javac/platform/RequiresIdentityTest.java b/test/langtools/tools/javac/platform/RequiresIdentityTest.java new file mode 100644 index 00000000000..f10d6e8054c --- /dev/null +++ b/test/langtools/tools/javac/platform/RequiresIdentityTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2025, 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 8356894 + * @summary Verify source level checks are performed properly + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * @build toolbox.ToolBox toolbox.JavacTask + * @run main RequiresIdentityTest +*/ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import toolbox.TestRunner; +import toolbox.JavacTask; +import toolbox.Task; +import toolbox.ToolBox; + +public class RequiresIdentityTest extends TestRunner { + + ToolBox tb; + + public static void main(String... args) throws Exception { + new RequiresIdentityTest().runTests(); + } + + RequiresIdentityTest() { + super(System.err); + tb = new ToolBox(); + } + + public void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + @Test + public void testReleaseWorksAsCurrentVersion(Path base) throws Exception { + Path src = base.resolve("src"); + Path classes = base.resolve("classes"); + tb.writeJavaFiles(src, + """ + import java.util.WeakHashMap; + import java.util.Optional; + + public class Test { + void test() { + WeakHashMap, Object> m = null; + m.put(Optional.empty(), 1); + } + } + """); + + Files.createDirectories(classes); + + var expectedErrors = List.of( + "Test.java:6:20: compiler.warn.attempt.to.use.value.based.where.identity.expected", + "Test.java:7:29: compiler.warn.attempt.to.use.value.based.where.identity.expected", + "2 warnings" + ); + + { + var actualErrors = + new JavacTask(tb) + .options("-XDrawDiagnostics") + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + if (!expectedErrors.equals(actualErrors)) { + throw new AssertionError("Incorrect errors, expected: " + List.of(expectedErrors) + + ", actual: " + actualErrors); + } + } + + { + var actualErrors = + new JavacTask(tb) + .options("--release", System.getProperty("java.specification.version"), + "-XDrawDiagnostics") + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + if (!expectedErrors.equals(actualErrors)) { + throw new AssertionError("Incorrect errors, expected: " + List.of(expectedErrors) + + ", actual: " + actualErrors); + } + } + } + + @Test + public void testModel(Path base) throws Exception { + { + List printed = + new JavacTask(tb) + .options("-Xprint") + .classes("java.util.WeakHashMap") + .run() + .writeAll() + .getOutputLines(Task.OutputKind.STDOUT); + + printed.removeIf(l -> !l.contains("put(") && !l.contains("class WeakHashMap<")); + + List expected = List.of( + "public class WeakHashMap<@jdk.internal.RequiresIdentity K, V> extends java.util.AbstractMap implements java.util.Map {", + " public V put(@jdk.internal.RequiresIdentity sealed K key," + ); + if (!expected.equals(printed)) { + throw new AssertionError("Expected: " + expected + + ", but got: " + printed); + } + } + + { + List printed = + new JavacTask(tb) + .options("--release", System.getProperty("java.specification.version"), + "-Xprint") + .classes("java.util.WeakHashMap") + .run() + .writeAll() + .getOutputLines(Task.OutputKind.STDOUT); + + printed.removeIf(l -> !l.contains("put(") && !l.contains("class WeakHashMap<")); + + List expected = List.of( + "public class WeakHashMap extends java.util.AbstractMap implements java.util.Map {", + " public V put(sealed K arg0," + ); + if (!expected.equals(printed)) { + throw new AssertionError("Expected: " + expected + + ", but got: " + printed); + } + } + } + +} diff --git a/test/langtools/tools/javac/platform/RequiresIdentityTest.out b/test/langtools/tools/javac/platform/RequiresIdentityTest.out new file mode 100644 index 00000000000..7b93ce830b7 --- /dev/null +++ b/test/langtools/tools/javac/platform/RequiresIdentityTest.out @@ -0,0 +1,4 @@ +RequiresIdentityTest.java:16:18: compiler.warn.attempt.to.use.value.based.where.identity.expected +- compiler.err.warnings.and.werror +1 error +1 warning diff --git a/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTest.java b/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTest.java index 76c13582b73..ac42c26488a 100644 --- a/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTest.java +++ b/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTest.java @@ -21,19 +21,6 @@ * questions. */ -/** - * @test - * @bug 8072480 8277106 8331027 - * @summary Unit test for CreateSymbols - * @modules java.compiler - * jdk.compiler/com.sun.tools.javac.api - * jdk.compiler/com.sun.tools.javac.jvm - * jdk.compiler/com.sun.tools.javac.main - * jdk.compiler/com.sun.tools.javac.util - * @clean * - * @run main/othervm CreateSymbolsTest - */ - import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; diff --git a/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java b/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java index c66cd08852a..2680ca6488f 100644 --- a/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java +++ b/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, 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 @@ -21,6 +21,19 @@ * questions. */ +/** + * @test + * @bug 8072480 8277106 8331027 + * @summary Unit test for CreateSymbols + * @modules java.compiler + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.jvm + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * @clean * + * @run main/othervm CreateSymbolsTest + */ + import java.io.File; import java.io.InputStream; import java.io.Writer; @@ -908,6 +921,109 @@ public class CreateSymbolsTestImpl { """); } + @Test + void testTypeAnnotations() throws Exception { + doPrintElementTest(""" + package t; + public class T { + } + """, + """ + package t; + import java.lang.annotation.*; + import java.util.*; + public class T<@AnnInvisible @AnnVisible E extends @AnnInvisible @AnnVisible ArrayList<@AnnInvisible @AnnVisible ArrayList>> extends @AnnInvisible @AnnVisible ArrayList { + public @AnnInvisible @AnnVisible List<@AnnInvisible @AnnVisible E> field; + public <@AnnInvisible @AnnVisible M extends @AnnInvisible @AnnVisible ArrayList<@AnnInvisible @AnnVisible ArrayList>> @AnnInvisible @AnnVisible List<@AnnInvisible @AnnVisible M> convert(@AnnInvisible @AnnVisible T this, @AnnInvisible @AnnVisible M e1, @AnnInvisible @AnnVisible List<@AnnInvisible @AnnVisible E> e2) throws @AnnInvisible @AnnVisible IllegalStateException, @AnnInvisible @AnnVisible IllegalArgumentException { + return null; + } + } + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE_USE) + @interface AnnVisible { + } + @Retention(RetentionPolicy.CLASS) + @Target(ElementType.TYPE_USE) + @interface AnnInvisible { + } + """, + "t.T", + """ + package t; + + public class T { + + public T(); + } + """, + "t.T", + """ + package t; + + public class T<@t.AnnInvisible @t.AnnVisible E extends java.util.@t.AnnInvisible @t.AnnVisible ArrayList> extends java.util.@t.AnnInvisible @t.AnnVisible ArrayList { + public java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible E> field; + + public T(); + + public <@t.AnnInvisible @t.AnnVisible M extends java.util.@t.AnnInvisible @t.AnnVisible ArrayList> java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible M> convert(@t.AnnInvisible @t.AnnVisible M arg0, + java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible E> arg1) throws java.lang.@t.AnnInvisible @t.AnnVisible IllegalStateException,\s + java.lang.@t.AnnInvisible @t.AnnVisible IllegalArgumentException; + } + """); + } + + @Test + void testParameterAnnotations() throws Exception { + doPrintElementTest(""" + package t; + public class T { + public void test(int p1, int p2) { + } + } + """, + """ + package t; + import java.lang.annotation.*; + import java.util.*; + public class T { + public void test(@AnnVisible int p1, @AnnInvisible int p2) { + } + } + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + @interface AnnVisible { + } + @Retention(RetentionPolicy.CLASS) + @Target(ElementType.PARAMETER) + @interface AnnInvisible { + } + """, + "t.T", + """ + package t; + + public class T { + + public T(); + + public void test(int arg0, + int arg1); + } + """, + "t.T", + """ + package t; + + public class T { + + public T(); + + public void test(@t.AnnVisible int arg0, + @t.AnnInvisible int arg1); + } + """); + } + void doTestData(String data, String... code) throws Exception { String testClasses = System.getProperty("test.classes");