From 96eed7fa6c025374bc10039bca2949a76d78f890 Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Fri, 8 Nov 2024 12:27:31 +0000 Subject: [PATCH] 8343306: javac is failing to determine if a class and a sealed interface are disjoint Reviewed-by: jlahoda, mcimadamore --- .../com/sun/tools/javac/code/Types.java | 58 +++++++++++-------- .../javac/sealed/SealedCompilationTests.java | 35 ++++++++++- 2 files changed, 69 insertions(+), 24 deletions(-) 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 e35ecbdc956..3545ebcc278 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 @@ -1672,6 +1672,12 @@ public class Types { // where class DisjointChecker { Set> pairsSeen = new HashSet<>(); + /* there are three cases for ts and ss: + * - one is a class and the other one is an interface (case I) + * - both are classes (case II) + * - both are interfaces (case III) + * all those cases are covered in JLS 23, section: "5.1.6.1 Allowed Narrowing Reference Conversion" + */ private boolean areDisjoint(ClassSymbol ts, ClassSymbol ss) { Pair newPair = new Pair<>(ts, ss); /* if we are seeing the same pair again then there is an issue with the sealed hierarchy @@ -1679,32 +1685,38 @@ public class Types { */ if (!pairsSeen.add(newPair)) return false; - if (isSubtype(erasure(ts.type), erasure(ss.type))) { - return false; - } - // if both are classes or both are interfaces, shortcut - if (ts.isInterface() == ss.isInterface() && isSubtype(erasure(ss.type), erasure(ts.type))) { - return false; - } - if (ts.isInterface() && !ss.isInterface()) { - /* so ts is interface but ss is a class - * an interface is disjoint from a class if the class is disjoint form the interface - */ - return areDisjoint(ss, ts); - } - // a final class that is not subtype of ss is disjoint - if (!ts.isInterface() && ts.isFinal()) { - return true; - } - // if at least one is sealed - if (ts.isSealed() || ss.isSealed()) { - // permitted subtypes have to be disjoint with the other symbol - ClassSymbol sealedOne = ts.isSealed() ? ts : ss; - ClassSymbol other = sealedOne == ts ? ss : ts; - return sealedOne.getPermittedSubclasses().stream().allMatch(type -> areDisjoint((ClassSymbol)type.tsym, other)); + + if (ts.isInterface() != ss.isInterface()) { // case I: one is a class and the other one is an interface + ClassSymbol isym = ts.isInterface() ? ts : ss; // isym is the interface and csym the class + ClassSymbol csym = isym == ts ? ss : ts; + if (!isSubtype(erasure(csym.type), erasure(isym.type))) { + if (csym.isFinal()) { + return true; + } else if (csym.isSealed()) { + return areDisjoint(isym, csym.getPermittedSubclasses()); + } else if (isym.isSealed()) { + // if the class is not final and not sealed then it has to be freely extensible + return areDisjoint(csym, isym.getPermittedSubclasses()); + } + } // now both are classes or both are interfaces + } else if (!ts.isInterface()) { // case II: both are classes + return !isSubtype(erasure(ss.type), erasure(ts.type)) && !isSubtype(erasure(ts.type), erasure(ss.type)); + } else { // case III: both are interfaces + if (!isSubtype(erasure(ts.type), erasure(ss.type)) && !isSubtype(erasure(ss.type), erasure(ts.type))) { + if (ts.isSealed()) { + return areDisjoint(ss, ts.getPermittedSubclasses()); + } else if (ss.isSealed()) { + return areDisjoint(ts, ss.getPermittedSubclasses()); + } + } } + // at this point we haven't been able to statically prove that the classes or interfaces are disjoint return false; } + + boolean areDisjoint(ClassSymbol csym, List permittedSubtypes) { + return permittedSubtypes.stream().allMatch(psubtype -> areDisjoint(csym, (ClassSymbol) psubtype.tsym)); + } } private TypeRelation isCastable = new TypeRelation() { diff --git a/test/langtools/tools/javac/sealed/SealedCompilationTests.java b/test/langtools/tools/javac/sealed/SealedCompilationTests.java index 5ad70d102d6..dee64fd0865 100644 --- a/test/langtools/tools/javac/sealed/SealedCompilationTests.java +++ b/test/langtools/tools/javac/sealed/SealedCompilationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -1178,6 +1178,39 @@ class SealedCompilationTests extends CompilationTestCase { I[] i = (I[]) c; } } + """, + """ + class Test { + sealed interface I permits C1 {} + non-sealed class C1 implements I {} + class C2 extends C1 {} + class C3 {} + I m(int s, C3 c3) { + I i = (I)c3; + } + } + """, + """ + class Test { + sealed interface I permits C1 {} + non-sealed class C1 implements I {} + class C2 extends C1 {} + class C3 {} + I m(int s, C3 c3) { + I i = (C1)c3; + } + } + """, + """ + class Test { + sealed interface I permits C1 {} + non-sealed class C1 implements I {} + class C2 extends C1 {} + class C3 {} + I m(int s, C3 c3) { + I i = (C2)c3; + } + } """ )) { assertFail("compiler.err.prob.found.req", s);