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 e5eac0786f6..e8a9b990369 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 @@ -333,7 +333,7 @@ public Symbol location(Type site, Types types) { return location(); } if (owner.type.hasTag(CLASS)) { - Type ownertype = types.asOuterSuper(site, owner); + Type ownertype = types.asEnclosingSuper(site, owner); if (ownertype != null) return ownertype.tsym; } return owner; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java index 417b54ca50c..7a70106886d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java @@ -42,14 +42,12 @@ import com.sun.tools.javac.code.Attribute.RetentionPolicy; import com.sun.tools.javac.code.Lint.LintCategory; -import com.sun.tools.javac.code.Source.Feature; import com.sun.tools.javac.code.Type.UndetVar.InferenceBound; import com.sun.tools.javac.code.TypeMetadata.Annotations; import com.sun.tools.javac.comp.AttrContext; import com.sun.tools.javac.comp.Check; import com.sun.tools.javac.comp.Enter; import com.sun.tools.javac.comp.Env; -import com.sun.tools.javac.comp.LambdaToMethod; import com.sun.tools.javac.jvm.ClassFile; import com.sun.tools.javac.util.*; @@ -2235,61 +2233,48 @@ public Type visitErrorType(ErrorType t, Symbol sym) { } }; - /** - * Return the base type of t or any of its outer types that starts - * with the given symbol. If none exists, return null. - * - * @param t a type - * @param sym a symbol - */ - public Type asOuterSuper(Type t, Symbol sym) { - switch (t.getTag()) { - case CLASS: - do { - Type s = asSuper(t, sym); - if (s != null) return s; - t = t.getEnclosingType(); - } while (t.hasTag(CLASS)); - return null; - case ARRAY: - return isSubtype(t, sym.type) ? sym.type : null; - case TYPEVAR: - return asSuper(t, sym); - case ERROR: - return t; - default: - return null; - } - } - - /** - * Return the base type of t or any of its enclosing types that - * starts with the given symbol. If none exists, return null. - * - * @param t a type - * @param sym a symbol - */ + /// Traverses a sequence of types starting with `t` and returns the first type + /// that can be seen as a supertype of one of those types. + /// + /// The sequence of types starts with `t` and the next type in the sequence + /// is obtained by obtaining innermost lexically enclosing class type of the + /// previous type in the sequence. + /// + /// Example: The type expression `B` is implicitly qualified and the + /// proper generic outer needs to be retrieved. Its site is `C.D` and its + /// outer type is `A`.`asEnclosingSuper` will use the enclosing classes to + /// discover for which type `A` can be seen as super of a type in the + /// sequence `C.D`: + /// + /// Can `A` be seen as super of `D`? No. Going to the next enclosing class. + /// Can `A` be seen as super of `C`? Yes! Its outer results in `A`. + /// + /// ``` + /// class A { class B { } } + /// + /// class C extends A { + /// static class D { + /// B b; // qualifier is A.B + /// } + /// } + /// ``` + /// + /// @implNote this is typically used to compute the implicit qualifier in + /// a type expression. + /// + /// @param t a type + /// @param sym a symbol + /// public Type asEnclosingSuper(Type t, Symbol sym) { - switch (t.getTag()) { - case CLASS: - do { - Type s = asSuper(t, sym); - if (s != null) return s; - Type outer = t.getEnclosingType(); - t = (outer.hasTag(CLASS)) ? outer : - (t.tsym.owner.enclClass() != null) ? t.tsym.owner.enclClass().type : - Type.noType; - } while (t.hasTag(CLASS)); - return null; - case ARRAY: - return isSubtype(t, sym.type) ? sym.type : null; - case TYPEVAR: - return asSuper(t, sym); - case ERROR: - return t; - default: - return null; + Type t1 = t; + while (!t1.hasTag(NONE)) { + Type s = asSuper(t1, sym); + if (s != null) return s; + t1 = (t1.tsym.owner.enclClass() != null) + ? t1.tsym.owner.enclClass().type + : noType; } + return null; } // @@ -2322,7 +2307,7 @@ public Type visitClassType(ClassType t, Symbol sym) { Symbol owner = sym.owner; long flags = sym.flags(); if (((flags & STATIC) == 0) && owner.type.isParameterized()) { - Type base = asOuterSuper(t, owner); + Type base = asEnclosingSuper(t, owner); //if t is an intersection type T = CT & I1 & I2 ... & In //its supertypes CT, I1, ... In might contain wildcards //so we need to go through capture conversion @@ -4514,7 +4499,7 @@ private boolean sideCast(Type from, Type to, Warner warn) { to = from; from = target; } - List commonSupers = superClosure(to, erasure(from)); + List commonSupers = supertypeClosure(to, erasure(from)); boolean giveWarning = commonSupers.isEmpty(); // The arguments to the supers could be unified here to // get a more accurate analysis @@ -4572,13 +4557,13 @@ private boolean giveWarning(Type from, Type to) { return false; } - private List superClosure(Type t, Type s) { + private List supertypeClosure(Type t, Type s) { List cl = List.nil(); for (List l = interfaces(t); l.nonEmpty(); l = l.tail) { if (isSubtype(s, erasure(l.head))) { cl = insert(cl, l.head); } else { - cl = union(cl, superClosure(l.head, s)); + cl = union(cl, supertypeClosure(l.head, s)); } } return cl; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 78aab35a428..629ae522542 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -4673,11 +4673,8 @@ Type checkIdInternal(JCTree tree, // // Then the type of the last expression above is // Tree.Visitor. - else if (ownOuter.hasTag(CLASS) && site != ownOuter) { - Type normOuter = site; - if (normOuter.hasTag(CLASS)) { - normOuter = types.asEnclosingSuper(site, ownOuter.tsym); - } + else if ((ownOuter.hasTag(CLASS) || ownOuter.hasTag(TYPEVAR)) && site != ownOuter) { + Type normOuter = types.asEnclosingSuper(site, ownOuter.tsym); if (normOuter == null) // perhaps from an import normOuter = types.erasure(ownOuter); if (normOuter != ownOuter) @@ -4704,7 +4701,7 @@ else if (ownOuter.hasTag(CLASS) && site != ownOuter) { v.owner.kind == TYP && (v.flags() & STATIC) == 0 && (site.hasTag(CLASS) || site.hasTag(TYPEVAR))) { - Type s = types.asOuterSuper(site, v.owner); + Type s = types.asEnclosingSuper(site, v.owner); if (s != null && s.isRaw() && !types.isSameType(v.type, v.erasure(types))) { @@ -4902,7 +4899,7 @@ public Type checkMethod(Type site, // an unchecked warning if its argument types change under erasure. if ((sym.flags() & STATIC) == 0 && (site.hasTag(CLASS) || site.hasTag(TYPEVAR))) { - Type s = types.asOuterSuper(site, sym.owner); + Type s = types.asEnclosingSuper(site, sym.owner); if (s != null && s.isRaw() && !types.isSameTypes(sym.type.getParameterTypes(), sym.erasure(types).getParameterTypes())) { @@ -5063,8 +5060,8 @@ public void visitTypeApply(JCTypeApply tree) { site = ((JCFieldAccess) clazz).selected.type; } else throw new AssertionError(""+tree); if (clazzOuter.hasTag(CLASS) && site != clazzOuter) { - if (site.hasTag(CLASS)) - site = types.asOuterSuper(site, clazzOuter.tsym); + if (site.hasTag(CLASS) || site.hasTag(TYPEVAR)) + site = types.asEnclosingSuper(site, clazzOuter.tsym); if (site == null) site = types.erasure(clazzOuter); clazzOuter = site; diff --git a/test/langtools/tools/javac/T8357472.java b/test/langtools/tools/javac/T8357472.java new file mode 100644 index 00000000000..5a2486ba29f --- /dev/null +++ b/test/langtools/tools/javac/T8357472.java @@ -0,0 +1,42 @@ +/* + * 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 8357472 + * @summary NPE in Types.containsType for type variable used as a qualifier + * @compile T8357472.java + */ + +class T8357472 { + class A { + protected class B {} + + public static > void f(Object g) { + @SuppressWarnings("unchecked") + M.B mapping = (M.B) g; + M.B[] mapping2 = new M.B[1]; + mapping2[0] = mapping; + } + } +} diff --git a/test/langtools/tools/javac/T8357653.java b/test/langtools/tools/javac/T8357653.java new file mode 100644 index 00000000000..d74ffdb21f5 --- /dev/null +++ b/test/langtools/tools/javac/T8357653.java @@ -0,0 +1,281 @@ +/* + * 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 8357653 + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.jdeps/com.sun.tools.javap + * @summary Inner classes of type parameters emitted as raw types in signatures + * @build toolbox.ToolBox toolbox.JavapTask + * @run main T8357653 + */ +import toolbox.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Gatherers; +import java.util.stream.Stream; + +public class T8357653 extends TestRunner { + private ToolBox tb; + + public static void main(String... args) throws Exception { + new T8357653().runTests(); + } + + T8357653() { + super(System.err); + tb = new ToolBox(); + } + + public void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + @Test + public void testCompilation(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeJavaFiles(src, + """ + package test; + public class Test { + static abstract class Getters { + abstract class Getter { + abstract X get(); + } + } + + static class Usage1> { + public T test(G.Getter getter) { + return getter.get(); + } + } + + static class Usage2, G extends U> { + public T test(G.Getter getter) { + return getter.get(); + } + } + + static class Usage3> { + public T test(G.Getter getter) { + return getter.get(); + } + } + + class G2 extends Getters {} + static class Usage4> { + M test(L.Getter getter) { + return getter.get(); + } + } + } + """); + + Files.createDirectories(classes); + + { + new JavacTask(tb) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + } + } + + @Test + public void testCompilationArray(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeJavaFiles(src, + """ + package test; + public class Test { + static abstract class Getters { + abstract class Getter { + abstract X get(); + } + } + + static class Usage1> { + public T test(G.Getter[] getter) { + return getter[0].get(); + } + } + + static class Usage2, G extends U> { + public T test(G.Getter[] getter) { + return getter[0].get(); + } + } + + static class Usage3> { + public T test(G.Getter[] getter) { + return getter[0].get(); + } + } + + class G2 extends Getters {} + static class Usage4> { + M test(L.Getter[] getter) { + return getter[0].get(); + } + } + } + """); + + Files.createDirectories(classes); + + { + new JavacTask(tb) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + } + } + + @Test + public void testErasureViaJavap(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeJavaFiles(src, + """ + package test; + public class Test { + static abstract class Getters { + abstract class Getter { + abstract X get(); + } + } + + static class Usage1> { + public T test(G.Getter getter) { + return getter.get(); + } + } + } + """); + + Files.createDirectories(classes); + + { + new JavacTask(tb) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + String javapOut = new JavapTask(tb) + .options("-v") + .classpath(classes.toString()) + .classes("test.Test$Usage1") + .run() + .getOutput(Task.OutputKind.DIRECT); + + if (!javapOut.contains("Signature: #21 // ;>Ljava/lang/Object;")) + throw new AssertionError("Wrongly erased generated signature:\n" + javapOut); + } + } + + @Test + public void testGenericsViaReflection(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeJavaFiles(src, + """ + package test; + import java.lang.reflect.*; + import java.util.Arrays; + + public class Test { + public static void main(String[] args) throws Throwable { + new test.Test().test(); + } + + public void test() throws Throwable { + var m = getClass().getDeclaredMethod("getOwner", Test.Base.Handler.class); + System.out.println(m); + System.out.println(Arrays.toString(m.getGenericParameterTypes())); + System.out.println(m.getGenericReturnType()); + } + + > S getOwner(S.Handler handler) { + return handler.owner(); + } + + abstract class Base> { + class Handler { + @SuppressWarnings("unchecked") + S owner() { + return (S) Base.this; + } + } + } + } + """); + + Files.createDirectories(classes); + + { + new JavacTask(tb) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + var out = new JavaTask(tb) + .classpath(classes.toString()) + .className("test.Test") + .run() + .writeAll() + .getOutput(Task.OutputKind.STDOUT); + + var expectedOut = """ + test.Test$Base test.Test.getOwner(test.Test$Base$Handler) + [test.Test$Base$Handler] + S + """; + + containsOrdered(out, expectedOut, "Wrongly erased generated signature:\n"); + } + } + + private static void containsOrdered(String expected, String actual, String message) { + List expectedLines = expected.lines().map(s -> s.strip()).toList(); + Stream actualLines = actual.lines().map(s -> s.strip()); + + if (!actualLines.gather(Gatherers.windowSliding(expectedLines.size())).anyMatch(window -> window.equals(expectedLines))) + throw new AssertionError(message + actual); + } +} diff --git a/test/langtools/tools/javac/T8357653b.java b/test/langtools/tools/javac/T8357653b.java new file mode 100644 index 00000000000..527c09ca0bd --- /dev/null +++ b/test/langtools/tools/javac/T8357653b.java @@ -0,0 +1,49 @@ +/* + * 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 8357653 + * @summary Inner classes of type parameters emitted as raw types in signatures + * @compile T8357653b.java + */ + +class T8357653b { + class A { + class B { + public T rett() { return null; } + } + } + + class C extends A { + static class D { + { + B b = null; + String s = b.rett(); + + B[] b2 = new B[1]; + String s2 = b2[0].rett(); + } + } + } +}