From 5e4a2ead714814cb4eb90ca88debc226f9c75864 Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Wed, 16 Jul 2025 10:52:26 +0000 Subject: [PATCH] 8357653: Inner classes of type parameters emitted as raw types in signatures 8357472: NPE in Types.containsType for type variable used as a qualifier Co-authored-by: Maurizio Cimadamore Reviewed-by: mcimadamore, vromero, liach --- .../com/sun/tools/javac/code/Types.java | 94 +++--- .../com/sun/tools/javac/comp/Attr.java | 11 +- test/langtools/tools/javac/T8357472.java | 42 +++ test/langtools/tools/javac/T8357653.java | 281 ++++++++++++++++++ test/langtools/tools/javac/T8357653b.java | 49 +++ 5 files changed, 424 insertions(+), 53 deletions(-) create mode 100644 test/langtools/tools/javac/T8357472.java create mode 100644 test/langtools/tools/javac/T8357653.java create mode 100644 test/langtools/tools/javac/T8357653b.java 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..0c155caa56d 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 javax.tools.JavaFileObject; 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.*; @@ -2236,60 +2234,64 @@ public class Types { }; /** - * Return the base type of t or any of its outer types that starts - * with the given symbol. If none exists, return null. + * This method returns the first type in a sequence (starting at `t`) that is + * a subclass of `sym`. The next type in the sequence is obtained by calling + * `getEnclosingType()` on the previous type in the sequence. Note, this is + * typically used to compute the implicit qualifier in a method/field access + * expression. Example: * - * @param t a type - * @param sym a symbol + * static class Sup { public F f; } + * class Outer { + * static class Sub extends Sup { + * class I { + * void test() { + * String f2 = f; // Sup::f + * } + * } + * } + * } + * + * @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; + Type t1 = t; + while (!t1.hasTag(NONE)) { + Type s = asSuper(t1, sym); + if (s != null) return s; + t1 = t1.getEnclosingType(); } + 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. + * This method returns the first type in a sequence (starting at `t`) that is + * a subclass of `sym`. The next type in the sequence is obtained by obtaining + * innermost lexically enclosing class type of the previous type in the sequence. + * Note, this is typically used to compute the implicit qualifier in + * a type expression. Example: + * + * class A { class B { } } + * + * class C extends A { + * static class D { + * B b; // A.B + * } + * } * * @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; } // @@ -4514,7 +4516,7 @@ public class Types { 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 +4574,13 @@ public class Types { 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..ea3b08e0338 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 @@ public class Attr extends JCTree.Visitor { // // 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) @@ -5063,8 +5060,8 @@ public class Attr extends JCTree.Visitor { 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(); + } + } + } +}