Skip to content

Commit b269493

Browse files
author
Olexandr Rotan
committed
8338981: Access to private classes should be permitted inside the permits clause of the enclosing top-level class
Reviewed-by: vromero, mcimadamore
1 parent 3ccd2f7 commit b269493

File tree

5 files changed

+210
-5
lines changed

5 files changed

+210
-5
lines changed

src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java

+1
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ public enum Feature {
262262
PRIMITIVE_PATTERNS(JDK23, Fragments.FeaturePrimitivePatterns, DiagKind.PLURAL),
263263
FLEXIBLE_CONSTRUCTORS(JDK22, Fragments.FeatureFlexibleConstructors, DiagKind.NORMAL),
264264
MODULE_IMPORTS(JDK23, Fragments.FeatureModuleImports, DiagKind.PLURAL),
265+
PRIVATE_MEMBERS_IN_PERMITS_CLAUSE(JDK19),
265266
;
266267

267268
enum DiagKind {

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java

+5
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ public class AttrContext {
9292
*/
9393
boolean allowProtectedAccess = false;
9494

95+
/** Are we attributing a permits clause?
96+
*/
97+
boolean isPermitsClause = false;
98+
9599
/** Are arguments to current function applications boxed into an array for varargs?
96100
*/
97101
Resolve.MethodResolutionPhase pendingResolutionPhase = null;
@@ -149,6 +153,7 @@ AttrContext dup(WriteableScope scope) {
149153
info.preferredTreeForDiagnostics = preferredTreeForDiagnostics;
150154
info.visitingServiceImplementation = visitingServiceImplementation;
151155
info.allowProtectedAccess = allowProtectedAccess;
156+
info.isPermitsClause = isPermitsClause;
152157
return info;
153158
}
154159

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public class Resolve {
111111
private final boolean compactMethodDiags;
112112
private final boolean allowLocalVariableTypeInference;
113113
private final boolean allowYieldStatement;
114+
private final boolean allowPrivateMembersInPermitsClause;
114115
final EnumSet<VerboseResolutionMode> verboseResolutionMode;
115116
final boolean dumpMethodReferenceSearchResults;
116117
final boolean dumpStacktraceOnError;
@@ -147,6 +148,7 @@ protected Resolve(Context context) {
147148
Target target = Target.instance(context);
148149
allowLocalVariableTypeInference = Feature.LOCAL_VARIABLE_TYPE_INFERENCE.allowedInSource(source);
149150
allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source);
151+
allowPrivateMembersInPermitsClause = Feature.PRIVATE_MEMBERS_IN_PERMITS_CLAUSE.allowedInSource(source);
150152
polymorphicSignatureScope = WriteableScope.create(syms.noSymbol);
151153
allowModules = Feature.MODULES.allowedInSource(source);
152154
allowRecords = Feature.RECORDS.allowedInSource(source);
@@ -425,7 +427,9 @@ public boolean isAccessible(Env<AttrContext> env, Type site, Symbol sym, boolean
425427
(env.enclClass.sym == sym.owner // fast special case
426428
||
427429
env.enclClass.sym.outermostClass() ==
428-
sym.owner.outermostClass())
430+
sym.owner.outermostClass()
431+
||
432+
privateMemberInPermitsClauseIfAllowed(env, sym))
429433
&&
430434
sym.isInheritedIn(site.tsym, types);
431435
case 0:
@@ -458,6 +462,13 @@ public boolean isAccessible(Env<AttrContext> env, Type site, Symbol sym, boolean
458462
return isAccessible(env, site, checkInner) && notOverriddenIn(site, sym);
459463
}
460464
}
465+
466+
private boolean privateMemberInPermitsClauseIfAllowed(Env<AttrContext> env, Symbol sym) {
467+
return allowPrivateMembersInPermitsClause &&
468+
env.info.isPermitsClause &&
469+
((JCClassDecl) env.tree).sym.outermostClass() == sym.owner.outermostClass();
470+
}
471+
461472
//where
462473
/* `sym' is accessible only if not overridden by
463474
* another symbol which is a member of `site'

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -979,11 +979,17 @@ private void fillPermits(JCClassDecl tree, Env<AttrContext> baseEnv) {
979979
if (sym.isPermittedExplicit) {
980980
ListBuffer<Symbol> permittedSubtypeSymbols = new ListBuffer<>();
981981
List<JCExpression> permittedTrees = tree.permitting;
982-
for (JCExpression permitted : permittedTrees) {
983-
Type pt = attr.attribBase(permitted, baseEnv, false, false, false);
984-
permittedSubtypeSymbols.append(pt.tsym);
982+
var isPermitsClause = baseEnv.info.isPermitsClause;
983+
try {
984+
baseEnv.info.isPermitsClause = true;
985+
for (JCExpression permitted : permittedTrees) {
986+
Type pt = attr.attribBase(permitted, baseEnv, false, false, false);
987+
permittedSubtypeSymbols.append(pt.tsym);
988+
}
989+
sym.setPermittedSubclasses(permittedSubtypeSymbols.toList());
990+
} finally {
991+
baseEnv.info.isPermitsClause = isPermitsClause;
985992
}
986-
sym.setPermittedSubclasses(permittedSubtypeSymbols.toList());
987993
}
988994
}
989995
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/**
25+
* @test
26+
* @bug 8338981
27+
* @summary Access to private classes should be permitted inside the permits clause of the enclosing top-level class.
28+
* @library /tools/lib
29+
* @modules jdk.compiler/com.sun.tools.javac.api
30+
* jdk.compiler/com.sun.tools.javac.main
31+
* jdk.compiler/com.sun.tools.javac.util
32+
* @build toolbox.ToolBox toolbox.JavacTask toolbox.Task
33+
* @run main PrivateMembersInPermitClause -source 19+
34+
*/
35+
import java.nio.file.Path;
36+
import java.util.Objects;
37+
import toolbox.Task;
38+
import java.util.List;
39+
40+
public class PrivateMembersInPermitClause extends toolbox.TestRunner {
41+
42+
private final toolbox.ToolBox tb;
43+
44+
public PrivateMembersInPermitClause() {
45+
super(System.err);
46+
tb = new toolbox.ToolBox();
47+
}
48+
49+
public static void main(String... args) throws Exception {
50+
new PrivateMembersInPermitClause().runTests();
51+
}
52+
53+
public void runTests() throws Exception {
54+
runTests(_ -> new Object[] {});
55+
}
56+
57+
/**
58+
* Tests that a private class in the permits clause compiles successfully.
59+
*/
60+
@Test
61+
public void privateClassPermitted() throws Exception {
62+
var root = Path.of("src");
63+
tb.writeJavaFiles(root,
64+
"""
65+
sealed class S permits S.A {
66+
private static final class A extends S {}
67+
}
68+
"""
69+
);
70+
71+
new toolbox.JavacTask(tb)
72+
.files(root.resolve("S.java"))
73+
.run(toolbox.Task.Expect.SUCCESS);
74+
}
75+
76+
/**
77+
* Tests that a private class from another top-level class in the permits clause fails to compile.
78+
*/
79+
@Test
80+
public void otherTopLevelPrivateClassFails() throws Exception {
81+
var root = Path.of("src");
82+
tb.writeJavaFiles(root,
83+
"""
84+
public class S {
85+
private static final class A extends S {}
86+
}
87+
sealed class T permits S.A {
88+
}
89+
"""
90+
);
91+
var expectedErrors = List.of(
92+
"S.java:4:25: compiler.err.report.access: S.A, private, S",
93+
"1 error"
94+
);
95+
96+
var compileErrors = new toolbox.JavacTask(tb)
97+
.files(root.resolve("S.java"))
98+
.options("-XDrawDiagnostics")
99+
.run(toolbox.Task.Expect.FAIL)
100+
.getOutputLines(Task.OutputKind.DIRECT);
101+
102+
if (!Objects.equals(compileErrors, expectedErrors)) {
103+
throw new AssertionError("Expected errors: " + expectedErrors + ", but got: " + compileErrors);
104+
}
105+
}
106+
107+
/**
108+
* Tests that a private class in the permits clause of an inner class compiles successfully.
109+
*/
110+
@Test
111+
public void privateClassInInnerPermitted() throws Exception {
112+
var root = Path.of("src");
113+
tb.writeJavaFiles(root,
114+
"""
115+
public sealed class S permits S.T.A {
116+
static class T {
117+
private static final class A extends S {}
118+
}
119+
}
120+
"""
121+
);
122+
123+
new toolbox.JavacTask(tb)
124+
.files(root.resolve("S.java"))
125+
.run(toolbox.Task.Expect.SUCCESS);
126+
}
127+
128+
/**
129+
* Tests that a private class in the permits clause contained in a sibling private inner class compiles successfully.
130+
*/
131+
@Test
132+
public void siblingPrivateClassesPermitted() throws Exception {
133+
var root = Path.of("src");
134+
tb.writeJavaFiles(root,
135+
"""
136+
public class S {
137+
private static class A {
138+
private static class B extends C.D {}
139+
}
140+
private static class C {
141+
private static class D {}
142+
}
143+
}
144+
"""
145+
);
146+
147+
new toolbox.JavacTask(tb)
148+
.files(root.resolve("S.java"))
149+
.run(toolbox.Task.Expect.SUCCESS);
150+
}
151+
152+
/**
153+
* Tests that a private class in the permits clause of a sealed class does not compile when the release is lower than 19.
154+
*/
155+
@Test
156+
public void testSourceLowerThan19() throws Exception {
157+
var root = Path.of("src");
158+
tb.writeJavaFiles(root,
159+
"""
160+
sealed class S permits S.A {
161+
private static final class A extends S {}
162+
}
163+
"""
164+
);
165+
166+
var expectedErrors = List.of(
167+
"S.java:1:25: compiler.err.report.access: S.A, private, S",
168+
"S.java:2:26: compiler.err.cant.inherit.from.sealed: S",
169+
"2 errors"
170+
);
171+
172+
var actualOutput = new toolbox.JavacTask(tb)
173+
.files(root.resolve("S.java"))
174+
.options("--release", "18", "-XDrawDiagnostics")
175+
.run(toolbox.Task.Expect.FAIL)
176+
.getOutputLines(Task.OutputKind.DIRECT);
177+
178+
if (!Objects.equals(actualOutput, expectedErrors)) {
179+
throw new AssertionError("Expected errors: " + expectedErrors + ", but got: " + actualOutput);
180+
}
181+
}
182+
}

0 commit comments

Comments
 (0)