1 /*
2  * Copyright (c) 2015, 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 8139829
27  * @summary Test access to members of user defined class.
28  * @build KullaTesting TestingInputStream ExpectedDiagnostic
29  * @run testng/timeout=600 ClassMembersTest
30  */
31 
32 import java.lang.annotation.RetentionPolicy;
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 import javax.tools.Diagnostic;
37 
38 import jdk.jshell.SourceCodeAnalysis;
39 import org.testng.annotations.DataProvider;
40 import org.testng.annotations.Test;
41 import jdk.jshell.TypeDeclSnippet;
42 import static jdk.jshell.Snippet.Status.OVERWRITTEN;
43 import static jdk.jshell.Snippet.Status.VALID;
44 
45 public class ClassMembersTest extends KullaTesting {
46 
47     @Test(dataProvider = "memberTestCase")
memberTest(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference)48     public void memberTest(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) {
49         MemberTestCase testCase = new MemberTestCase(accessModifier, codeChunk, isStaticMember, isStaticReference);
50         assertEval(testCase.generateSource());
51         String expectedMessage = testCase.expectedMessage;
52         if (testCase.codeChunk != CodeChunk.CONSTRUCTOR || testCase.isAccessible()) {
53             assertEval("A a = new A();");
54         }
55         if (expectedMessage == null) {
56             assertEval(testCase.useCodeChunk());
57         } else {
58             assertDeclareFail(testCase.useCodeChunk(), expectedMessage);
59         }
60     }
61 
parseCode(String input)62     private List<String> parseCode(String input) {
63         List<String> list = new ArrayList<>();
64         SourceCodeAnalysis codeAnalysis = getAnalysis();
65         String source = input;
66         while (!source.trim().isEmpty()) {
67             SourceCodeAnalysis.CompletionInfo info = codeAnalysis.analyzeCompletion(source);
68             list.add(info.source());
69             source = info.remaining();
70         }
71         return list;
72     }
73 
74     @Test(dataProvider = "memberTestCase")
extendsMemberTest(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference)75     public void extendsMemberTest(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) {
76         MemberTestCase testCase = new ExtendsMemberTestCase(accessModifier, codeChunk, isStaticMember, isStaticReference);
77         String input = testCase.generateSource();
78         List<String> ss = parseCode(input);
79         assertEval(ss.get(0));
80         if (testCase.codeChunk != CodeChunk.CONSTRUCTOR || testCase.isAccessible()) {
81             assertEval(ss.get(1));
82             assertEval("B b = new B();");
83         }
84         String expectedMessage = testCase.expectedMessage;
85         if (expectedMessage == null) {
86             assertEval(testCase.useCodeChunk());
87         } else {
88             assertDeclareFail(testCase.useCodeChunk(), expectedMessage);
89         }
90     }
91 
92     @Test
interfaceTest()93     public void interfaceTest() {
94         String interfaceSource =
95                 "interface A {\n" +
96                 "   default int defaultMethod() { return 1; }\n" +
97                 "   static int staticMethod() { return 2; }\n" +
98                 "   int method();\n" +
99                 "   class Inner1 {}\n" +
100                 "   static class Inner2 {}\n" +
101                 "}";
102         assertEval(interfaceSource);
103         assertEval("A.staticMethod();", "2");
104         String classSource =
105                 "class B implements A {\n" +
106                 "   public int method() { return 3; }\n" +
107                 "}";
108         assertEval(classSource);
109         assertEval("B b = new B();");
110         assertEval("b.defaultMethod();", "1");
111         assertDeclareFail("B.staticMethod();",
112                 new ExpectedDiagnostic("compiler.err.cant.resolve.location.args", 0, 14, 1, -1, -1, Diagnostic.Kind.ERROR));
113         assertEval("b.method();", "3");
114         assertEval("new A.Inner1();");
115         assertEval("new A.Inner2();");
116         assertEval("new B.Inner1();");
117         assertEval("new B.Inner2();");
118     }
119 
120     @Test
enumTest()121     public void enumTest() {
122         String enumSource =
123                 "enum E {A(\"s\");\n" +
124                 "   private final String s;\n" +
125                 "   private E(String s) { this.s = s; }\n" +
126                 "   public String method() { return s; }\n" +
127                 "   private String privateMethod() { return s; }\n" +
128                 "   public static String staticMethod() { return staticPrivateMethod(); }\n" +
129                 "   private static String staticPrivateMethod() { return \"a\"; }\n" +
130                 "}";
131         assertEval(enumSource);
132         assertEval("E a = E.A;", "A");
133         assertDeclareFail("a.s;",
134                 new ExpectedDiagnostic("compiler.err.report.access", 0, 3, 1, -1, -1, Diagnostic.Kind.ERROR));
135         assertDeclareFail("new E(\"q\");",
136                 new ExpectedDiagnostic("compiler.err.enum.cant.be.instantiated", 0, 10, 0, -1, -1, Diagnostic.Kind.ERROR));
137         assertEval("a.method();", "\"s\"");
138         assertDeclareFail("a.privateMethod();",
139                 new ExpectedDiagnostic("compiler.err.report.access", 0, 15, 1, -1, -1, Diagnostic.Kind.ERROR));
140         assertEval("E.staticMethod();", "\"a\"");
141         assertDeclareFail("a.staticPrivateMethod();",
142                 new ExpectedDiagnostic("compiler.err.report.access", 0, 21, 1, -1, -1, Diagnostic.Kind.ERROR));
143         assertDeclareFail("E.method();",
144                 new ExpectedDiagnostic("compiler.err.non-static.cant.be.ref", 0, 8, 1, -1, -1, Diagnostic.Kind.ERROR));
145     }
146 
147     @Test(dataProvider = "retentionPolicyTestCase")
annotationTest(RetentionPolicy policy)148     public void annotationTest(RetentionPolicy policy) {
149         assertEval("import java.lang.annotation.*;");
150         String annotationSource =
151                 "@Retention(RetentionPolicy." + policy.toString() + ")\n" +
152                 "@interface A {}";
153         assertEval(annotationSource);
154         String classSource =
155                 "@A class C {\n" +
156                 "   @A C() {}\n" +
157                 "   @A void f() {}\n" +
158                 "   @A int f;\n" +
159                 "   @A class Inner {}\n" +
160                 "}";
161         assertEval(classSource);
162         String isRuntimeVisible = policy == RetentionPolicy.RUNTIME ? "true" : "false";
163         assertEval("C.class.getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
164         assertEval("C.class.getDeclaredConstructor().getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
165         assertEval("C.class.getDeclaredMethod(\"f\").getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
166         assertEval("C.class.getDeclaredField(\"f\").getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
167         assertEval("C.Inner.class.getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
168     }
169 
170     @DataProvider(name = "retentionPolicyTestCase")
retentionPolicyTestCaseGenerator()171     public Object[][] retentionPolicyTestCaseGenerator() {
172         List<Object[]> list = new ArrayList<>();
173         for (RetentionPolicy policy : RetentionPolicy.values()) {
174             list.add(new Object[]{policy});
175         }
176         return list.toArray(new Object[list.size()][]);
177     }
178 
179     @DataProvider(name = "memberTestCase")
memberTestCaseGenerator()180     public Object[][] memberTestCaseGenerator() {
181         List<Object[]> list = new ArrayList<>();
182         for (AccessModifier accessModifier : AccessModifier.values()) {
183             for (Static isStaticMember : Static.values()) {
184                 for (Static isStaticReference : Static.values()) {
185                     for (CodeChunk codeChunk : CodeChunk.values()) {
186                         if (codeChunk == CodeChunk.CONSTRUCTOR && isStaticMember == Static.STATIC) {
187                             continue;
188                         }
189                         list.add(new Object[]{ accessModifier, codeChunk, isStaticMember, isStaticReference });
190                     }
191                 }
192             }
193         }
194         return list.toArray(new Object[list.size()][]);
195     }
196 
197     public static class ExtendsMemberTestCase extends MemberTestCase {
198 
ExtendsMemberTestCase(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference)199         public ExtendsMemberTestCase(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) {
200             super(accessModifier, codeChunk, isStaticMember, isStaticReference);
201         }
202 
203         @Override
getSourceTemplate()204         public String getSourceTemplate() {
205             return super.getSourceTemplate() + "\n"
206                     + "class B extends A {}";
207         }
208 
209         @Override
errorMessage()210         public String errorMessage() {
211             if (!isAccessible()) {
212                 if (codeChunk == CodeChunk.METHOD) {
213                     return "compiler.err.cant.resolve.location.args";
214                 }
215                 if (codeChunk == CodeChunk.CONSTRUCTOR) {
216                     return "compiler.err.cant.resolve.location";
217                 }
218             }
219             return super.errorMessage();
220         }
221 
222         @Override
useCodeChunk()223         public String useCodeChunk() {
224             return useCodeChunk("B");
225         }
226     }
227 
228     public static class MemberTestCase {
229         public final AccessModifier accessModifier;
230         public final CodeChunk codeChunk;
231         public final Static isStaticMember;
232         public final Static isStaticReference;
233         public final String expectedMessage;
234 
MemberTestCase(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference)235         public MemberTestCase(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember,
236                               Static isStaticReference) {
237             this.accessModifier = accessModifier;
238             this.codeChunk = codeChunk;
239             this.isStaticMember = isStaticMember;
240             this.isStaticReference = isStaticReference;
241             this.expectedMessage = errorMessage();
242         }
243 
getSourceTemplate()244         public String getSourceTemplate() {
245             return  "class A {\n" +
246                     "    #MEMBER#\n" +
247                     "}";
248         }
249 
isAccessible()250         public boolean isAccessible() {
251             return accessModifier != AccessModifier.PRIVATE;
252         }
253 
errorMessage()254         public String errorMessage() {
255             if (!isAccessible()) {
256                 return "compiler.err.report.access";
257             }
258             if (codeChunk == CodeChunk.INNER_INTERFACE) {
259                 return "compiler.err.abstract.cant.be.instantiated";
260             }
261             if (isStaticMember == Static.STATIC) {
262                 if (isStaticReference == Static.NO && codeChunk == CodeChunk.INNER_CLASS) {
263                     return "compiler.err.qualified.new.of.static.class";
264                 }
265                 return null;
266             }
267             if (isStaticReference == Static.STATIC) {
268                 if (codeChunk == CodeChunk.CONSTRUCTOR) {
269                     return null;
270                 }
271                 if (codeChunk == CodeChunk.INNER_CLASS) {
272                     return "compiler.err.encl.class.required";
273                 }
274                 return "compiler.err.non-static.cant.be.ref";
275             }
276             return null;
277         }
278 
generateSource()279         public String generateSource() {
280             return getSourceTemplate().replace("#MEMBER#", codeChunk.generateSource(accessModifier, isStaticMember));
281         }
282 
useCodeChunk(String className)283         protected String useCodeChunk(String className) {
284             String name = className.toLowerCase();
285             switch (codeChunk) {
286                 case CONSTRUCTOR:
287                     return String.format("new %s();", className);
288                 case METHOD:
289                     if (isStaticReference == Static.STATIC) {
290                         return String.format("%s.method();", className);
291                     } else {
292                         return String.format("%s.method();", name);
293                     }
294                 case FIELD:
295                     if (isStaticReference == Static.STATIC) {
296                         return String.format("%s.field;", className);
297                     } else {
298                         return String.format("%s.field;", name);
299                     }
300                 case INNER_CLASS:
301                     if (isStaticReference == Static.STATIC) {
302                         return String.format("new %s.Inner();", className);
303                     } else {
304                         return String.format("%s.new Inner();", name);
305                     }
306                 case INNER_INTERFACE:
307                     return String.format("new %s.Inner();", className);
308                 default:
309                     throw new AssertionError("Unknown code chunk: " + this);
310             }
311         }
312 
useCodeChunk()313         public String useCodeChunk() {
314             return useCodeChunk("A");
315         }
316     }
317 
318     public enum AccessModifier {
319         PUBLIC("public"),
320         PROTECTED("protected"),
321         PACKAGE_PRIVATE(""),
322         PRIVATE("private");
323 
324         private final String modifier;
325 
AccessModifier(String modifier)326         AccessModifier(String modifier) {
327             this.modifier = modifier;
328         }
329 
getModifier()330         public String getModifier() {
331             return modifier;
332         }
333     }
334 
335     public enum Static {
336         STATIC("static"), NO("");
337 
338         private final String modifier;
339 
Static(String modifier)340         Static(String modifier) {
341             this.modifier = modifier;
342         }
343 
getModifier()344         public String getModifier() {
345             return modifier;
346         }
347     }
348 
349     public enum CodeChunk {
350         CONSTRUCTOR("#MODIFIER# A() {}"),
351         METHOD("#MODIFIER# int method() { return 10; }"),
352         FIELD("#MODIFIER# int field = 10;"),
353         INNER_CLASS("#MODIFIER# class Inner {}"),
354         INNER_INTERFACE("#MODIFIER# interface Inner {}");
355 
356         private final String code;
357 
CodeChunk(String code)358         CodeChunk(String code) {
359             this.code = code;
360         }
361 
generateSource(AccessModifier accessModifier, Static isStatic)362         public String generateSource(AccessModifier accessModifier, Static isStatic) {
363             return code.replace("#MODIFIER#", accessModifier.getModifier() + " " + isStatic.getModifier());
364         }
365     }
366 }
367