8338981: Access to private classes should be permitted inside the permits clause of the enclosing top-level class

Reviewed-by: vromero, mcimadamore
This commit is contained in:
Olexandr Rotan 2024-10-31 15:25:42 +00:00
parent 3ccd2f757d
commit b2694934b5
5 changed files with 210 additions and 5 deletions

View File

@ -262,6 +262,7 @@ public enum Source {
PRIMITIVE_PATTERNS(JDK23, Fragments.FeaturePrimitivePatterns, DiagKind.PLURAL),
FLEXIBLE_CONSTRUCTORS(JDK22, Fragments.FeatureFlexibleConstructors, DiagKind.NORMAL),
MODULE_IMPORTS(JDK23, Fragments.FeatureModuleImports, DiagKind.PLURAL),
PRIVATE_MEMBERS_IN_PERMITS_CLAUSE(JDK19),
;
enum DiagKind {

View File

@ -92,6 +92,10 @@ public class AttrContext {
*/
boolean allowProtectedAccess = false;
/** Are we attributing a permits clause?
*/
boolean isPermitsClause = false;
/** Are arguments to current function applications boxed into an array for varargs?
*/
Resolve.MethodResolutionPhase pendingResolutionPhase = null;
@ -149,6 +153,7 @@ public class AttrContext {
info.preferredTreeForDiagnostics = preferredTreeForDiagnostics;
info.visitingServiceImplementation = visitingServiceImplementation;
info.allowProtectedAccess = allowProtectedAccess;
info.isPermitsClause = isPermitsClause;
return info;
}

View File

@ -111,6 +111,7 @@ public class Resolve {
private final boolean compactMethodDiags;
private final boolean allowLocalVariableTypeInference;
private final boolean allowYieldStatement;
private final boolean allowPrivateMembersInPermitsClause;
final EnumSet<VerboseResolutionMode> verboseResolutionMode;
final boolean dumpMethodReferenceSearchResults;
final boolean dumpStacktraceOnError;
@ -147,6 +148,7 @@ public class Resolve {
Target target = Target.instance(context);
allowLocalVariableTypeInference = Feature.LOCAL_VARIABLE_TYPE_INFERENCE.allowedInSource(source);
allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source);
allowPrivateMembersInPermitsClause = Feature.PRIVATE_MEMBERS_IN_PERMITS_CLAUSE.allowedInSource(source);
polymorphicSignatureScope = WriteableScope.create(syms.noSymbol);
allowModules = Feature.MODULES.allowedInSource(source);
allowRecords = Feature.RECORDS.allowedInSource(source);
@ -425,7 +427,9 @@ public class Resolve {
(env.enclClass.sym == sym.owner // fast special case
||
env.enclClass.sym.outermostClass() ==
sym.owner.outermostClass())
sym.owner.outermostClass()
||
privateMemberInPermitsClauseIfAllowed(env, sym))
&&
sym.isInheritedIn(site.tsym, types);
case 0:
@ -458,6 +462,13 @@ public class Resolve {
return isAccessible(env, site, checkInner) && notOverriddenIn(site, sym);
}
}
private boolean privateMemberInPermitsClauseIfAllowed(Env<AttrContext> env, Symbol sym) {
return allowPrivateMembersInPermitsClause &&
env.info.isPermitsClause &&
((JCClassDecl) env.tree).sym.outermostClass() == sym.owner.outermostClass();
}
//where
/* `sym' is accessible only if not overridden by
* another symbol which is a member of `site'

View File

@ -979,11 +979,17 @@ public class TypeEnter implements Completer {
if (sym.isPermittedExplicit) {
ListBuffer<Symbol> permittedSubtypeSymbols = new ListBuffer<>();
List<JCExpression> permittedTrees = tree.permitting;
var isPermitsClause = baseEnv.info.isPermitsClause;
try {
baseEnv.info.isPermitsClause = true;
for (JCExpression permitted : permittedTrees) {
Type pt = attr.attribBase(permitted, baseEnv, false, false, false);
permittedSubtypeSymbols.append(pt.tsym);
}
sym.setPermittedSubclasses(permittedSubtypeSymbols.toList());
} finally {
baseEnv.info.isPermitsClause = isPermitsClause;
}
}
}
}

View File

@ -0,0 +1,182 @@
/*
* Copyright (c) 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
* 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 8338981
* @summary Access to private classes should be permitted inside the permits clause of the enclosing top-level class.
* @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 toolbox.Task
* @run main PrivateMembersInPermitClause -source 19+
*/
import java.nio.file.Path;
import java.util.Objects;
import toolbox.Task;
import java.util.List;
public class PrivateMembersInPermitClause extends toolbox.TestRunner {
private final toolbox.ToolBox tb;
public PrivateMembersInPermitClause() {
super(System.err);
tb = new toolbox.ToolBox();
}
public static void main(String... args) throws Exception {
new PrivateMembersInPermitClause().runTests();
}
public void runTests() throws Exception {
runTests(_ -> new Object[] {});
}
/**
* Tests that a private class in the permits clause compiles successfully.
*/
@Test
public void privateClassPermitted() throws Exception {
var root = Path.of("src");
tb.writeJavaFiles(root,
"""
sealed class S permits S.A {
private static final class A extends S {}
}
"""
);
new toolbox.JavacTask(tb)
.files(root.resolve("S.java"))
.run(toolbox.Task.Expect.SUCCESS);
}
/**
* Tests that a private class from another top-level class in the permits clause fails to compile.
*/
@Test
public void otherTopLevelPrivateClassFails() throws Exception {
var root = Path.of("src");
tb.writeJavaFiles(root,
"""
public class S {
private static final class A extends S {}
}
sealed class T permits S.A {
}
"""
);
var expectedErrors = List.of(
"S.java:4:25: compiler.err.report.access: S.A, private, S",
"1 error"
);
var compileErrors = new toolbox.JavacTask(tb)
.files(root.resolve("S.java"))
.options("-XDrawDiagnostics")
.run(toolbox.Task.Expect.FAIL)
.getOutputLines(Task.OutputKind.DIRECT);
if (!Objects.equals(compileErrors, expectedErrors)) {
throw new AssertionError("Expected errors: " + expectedErrors + ", but got: " + compileErrors);
}
}
/**
* Tests that a private class in the permits clause of an inner class compiles successfully.
*/
@Test
public void privateClassInInnerPermitted() throws Exception {
var root = Path.of("src");
tb.writeJavaFiles(root,
"""
public sealed class S permits S.T.A {
static class T {
private static final class A extends S {}
}
}
"""
);
new toolbox.JavacTask(tb)
.files(root.resolve("S.java"))
.run(toolbox.Task.Expect.SUCCESS);
}
/**
* Tests that a private class in the permits clause contained in a sibling private inner class compiles successfully.
*/
@Test
public void siblingPrivateClassesPermitted() throws Exception {
var root = Path.of("src");
tb.writeJavaFiles(root,
"""
public class S {
private static class A {
private static class B extends C.D {}
}
private static class C {
private static class D {}
}
}
"""
);
new toolbox.JavacTask(tb)
.files(root.resolve("S.java"))
.run(toolbox.Task.Expect.SUCCESS);
}
/**
* Tests that a private class in the permits clause of a sealed class does not compile when the release is lower than 19.
*/
@Test
public void testSourceLowerThan19() throws Exception {
var root = Path.of("src");
tb.writeJavaFiles(root,
"""
sealed class S permits S.A {
private static final class A extends S {}
}
"""
);
var expectedErrors = List.of(
"S.java:1:25: compiler.err.report.access: S.A, private, S",
"S.java:2:26: compiler.err.cant.inherit.from.sealed: S",
"2 errors"
);
var actualOutput = new toolbox.JavacTask(tb)
.files(root.resolve("S.java"))
.options("--release", "18", "-XDrawDiagnostics")
.run(toolbox.Task.Expect.FAIL)
.getOutputLines(Task.OutputKind.DIRECT);
if (!Objects.equals(actualOutput, expectedErrors)) {
throw new AssertionError("Expected errors: " + expectedErrors + ", but got: " + actualOutput);
}
}
}