1 /*
2  * Copyright (c) 2012, 2013, 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 7192246 8006694
27  * @summary Automatic test for checking correctness of default super/this resolution
28  *  temporarily workaround combo tests are causing time out in several platforms
29  * @library ../../lib
30  * @build JavacTestingAbstractThreadedTest
31  * @run main/othervm TestDefaultSuperCall
32  */
33 
34 // use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047)
35 // see JDK-8006746
36 
37 import java.net.URI;
38 import java.util.Arrays;
39 import java.util.ArrayList;
40 import java.util.List;
41 import javax.tools.Diagnostic;
42 import javax.tools.JavaFileObject;
43 import javax.tools.SimpleJavaFileObject;
44 
45 import com.sun.source.util.JavacTask;
46 
47 public class TestDefaultSuperCall
48     extends JavacTestingAbstractThreadedTest
49     implements Runnable {
50 
51     enum InterfaceKind {
52         DEFAULT("interface A extends B { default void m() { } }"),
53         ABSTRACT("interface A extends B { void m(); }"),
54         NONE("interface A extends B { }");
55 
56         String interfaceStr;
57 
InterfaceKind(String interfaceStr)58         InterfaceKind(String interfaceStr) {
59             this.interfaceStr = interfaceStr;
60         }
61 
methodDefined()62         boolean methodDefined() {
63             return this == DEFAULT;
64         }
65     }
66 
67     enum PruneKind {
68         NO_PRUNE("interface C { }"),
69         PRUNE("interface C extends A { }");
70 
methodDefined(InterfaceKind ik)71         boolean methodDefined(InterfaceKind ik) {
72             return this == PRUNE &&
73                     ik.methodDefined();
74         }
75 
76         String interfaceStr;
77 
PruneKind(String interfaceStr)78         PruneKind(String interfaceStr) {
79             this.interfaceStr = interfaceStr;
80         }
81     }
82 
83     enum QualifierKind {
84         DIRECT_1("C"),
85         DIRECT_2("A"),
86         INDIRECT("B"),
87         UNRELATED("E"),
88         ENCLOSING_1(null),
89         ENCLOSING_2(null);
90 
91         String qualifierStr;
92 
QualifierKind(String qualifierStr)93         QualifierKind(String qualifierStr) {
94             this.qualifierStr = qualifierStr;
95         }
96 
getQualifier(Shape sh)97         String getQualifier(Shape sh) {
98             switch (this) {
99                 case ENCLOSING_1: return sh.enclosingAt(0);
100                 case ENCLOSING_2: return sh.enclosingAt(1);
101                 default:
102                     return qualifierStr;
103             }
104         }
105 
isEnclosing()106         boolean isEnclosing() {
107             return this == ENCLOSING_1 ||
108                     this == ENCLOSING_2;
109         }
110 
allowSuperCall(InterfaceKind ik, PruneKind pk)111         boolean allowSuperCall(InterfaceKind ik, PruneKind pk) {
112             switch (this) {
113                 case DIRECT_1:
114                     return pk.methodDefined(ik);
115                 case DIRECT_2:
116                     return ik.methodDefined() && pk == PruneKind.NO_PRUNE;
117                 default:
118                     return false;
119             }
120         }
121     }
122 
123     enum ExprKind {
124         THIS("this"),
125         SUPER("super");
126 
127         String exprStr;
128 
ExprKind(String exprStr)129         ExprKind(String exprStr) {
130             this.exprStr = exprStr;
131         }
132     }
133 
134     enum ElementKind {
135         INTERFACE("interface #N { #B }", true),
136         INTERFACE_EXTENDS("interface #N extends A, C { #B }", true),
137         CLASS("class #N { #B }", false),
138         CLASS_EXTENDS("abstract class #N implements A, C { #B }", false),
139         STATIC_CLASS("static class #N { #B }", true),
140         STATIC_CLASS_EXTENDS("abstract static class #N implements A, C { #B }", true),
141         ANON_CLASS("new Object() { #B };", false),
142         METHOD("void test() { #B }", false),
143         STATIC_METHOD("static void test() { #B }", true),
144         DEFAULT_METHOD("default void test() { #B }", false);
145 
146         String templateDecl;
147         boolean isStatic;
148 
ElementKind(String templateDecl, boolean isStatic)149         ElementKind(String templateDecl, boolean isStatic) {
150             this.templateDecl = templateDecl;
151             this.isStatic = isStatic;
152         }
153 
isClassDecl()154         boolean isClassDecl() {
155             switch(this) {
156                 case METHOD:
157                 case STATIC_METHOD:
158                 case DEFAULT_METHOD:
159                     return false;
160                 default:
161                     return true;
162             }
163         }
164 
isAllowedEnclosing(ElementKind ek, boolean isTop)165         boolean isAllowedEnclosing(ElementKind ek, boolean isTop) {
166             switch (this) {
167                 case CLASS:
168                 case CLASS_EXTENDS:
169                     //class is implicitly static inside interface, so skip this combo
170                     return ek.isClassDecl() &&
171                             ek != INTERFACE && ek != INTERFACE_EXTENDS;
172                 case ANON_CLASS:
173                     return !ek.isClassDecl();
174                 case METHOD:
175                     return ek == CLASS || ek == CLASS_EXTENDS ||
176                             ek == STATIC_CLASS || ek == STATIC_CLASS_EXTENDS ||
177                             ek == ANON_CLASS;
178                 case INTERFACE:
179                 case INTERFACE_EXTENDS:
180                 case STATIC_CLASS:
181                 case STATIC_CLASS_EXTENDS:
182                 case STATIC_METHOD:
183                     return (isTop && (ek == CLASS || ek == CLASS_EXTENDS)) ||
184                             ek == STATIC_CLASS || ek == STATIC_CLASS_EXTENDS;
185                 case DEFAULT_METHOD:
186                     return ek == INTERFACE || ek == INTERFACE_EXTENDS;
187                 default:
188                     throw new AssertionError("Bad enclosing element kind" + this);
189             }
190         }
191 
isAllowedTop()192         boolean isAllowedTop() {
193             switch (this) {
194                 case CLASS:
195                 case CLASS_EXTENDS:
196                 case INTERFACE:
197                 case INTERFACE_EXTENDS:
198                     return true;
199                 default:
200                     return false;
201             }
202         }
203 
hasSuper()204         boolean hasSuper() {
205             return this == INTERFACE_EXTENDS ||
206                     this == STATIC_CLASS_EXTENDS ||
207                     this == CLASS_EXTENDS;
208         }
209     }
210 
211     static class Shape {
212 
213         String shapeStr;
214         List<ElementKind> enclosingElements;
215         List<String> enclosingNames;
216         List<String> elementsWithMethod;
217 
Shape(ElementKind... elements)218         Shape(ElementKind... elements) {
219             enclosingElements = new ArrayList<>();
220             enclosingNames = new ArrayList<>();
221             elementsWithMethod = new ArrayList<>();
222             int count = 0;
223             String prevName = null;
224             for (ElementKind ek : elements) {
225                 String name = "name"+count++;
226                 if (ek.isStatic) {
227                     enclosingElements = new ArrayList<>();
228                     enclosingNames = new ArrayList<>();
229                 }
230                 if (ek.isClassDecl()) {
231                     enclosingElements.add(ek);
232                     enclosingNames.add(name);
233                 } else {
234                     elementsWithMethod.add(prevName);
235                 }
236                 String element = ek.templateDecl.replaceAll("#N", name);
237                 shapeStr = shapeStr ==
238                         null ? element : shapeStr.replaceAll("#B", element);
239                 prevName = name;
240             }
241         }
242 
getShape(QualifierKind qk, ExprKind ek)243         String getShape(QualifierKind qk, ExprKind ek) {
244             String methName = ek == ExprKind.THIS ? "test" : "m";
245             String call = qk.getQualifier(this) + "." +
246                     ek.exprStr + "." + methName + "();";
247             return shapeStr.replaceAll("#B", call);
248         }
249 
enclosingAt(int index)250         String enclosingAt(int index) {
251             return index < enclosingNames.size() ?
252                     enclosingNames.get(index) : "BAD";
253         }
254     }
255 
main(String... args)256     public static void main(String... args) throws Exception {
257         for (InterfaceKind ik : InterfaceKind.values()) {
258             for (PruneKind pk : PruneKind.values()) {
259                 for (ElementKind ek1 : ElementKind.values()) {
260                     if (!ek1.isAllowedTop()) continue;
261                     for (ElementKind ek2 : ElementKind.values()) {
262                         if (!ek2.isAllowedEnclosing(ek1, true)) continue;
263                         for (ElementKind ek3 : ElementKind.values()) {
264                             if (!ek3.isAllowedEnclosing(ek2, false)) continue;
265                             for (ElementKind ek4 : ElementKind.values()) {
266                                 if (!ek4.isAllowedEnclosing(ek3, false)) continue;
267                                 for (ElementKind ek5 : ElementKind.values()) {
268                                     if (!ek5.isAllowedEnclosing(ek4, false) ||
269                                             ek5.isClassDecl()) continue;
270                                     for (QualifierKind qk : QualifierKind.values()) {
271                                         for (ExprKind ek : ExprKind.values()) {
272                                             pool.execute(
273                                                     new TestDefaultSuperCall(ik, pk,
274                                                     new Shape(ek1, ek2, ek3,
275                                                     ek4, ek5), qk, ek));
276                                         }
277                                     }
278                                 }
279                             }
280                         }
281                     }
282                 }
283             }
284         }
285 
286         checkAfterExec();
287     }
288 
289     InterfaceKind ik;
290     PruneKind pk;
291     Shape sh;
292     QualifierKind qk;
293     ExprKind ek;
294     JavaSource source;
295     DiagnosticChecker diagChecker;
296 
TestDefaultSuperCall(InterfaceKind ik, PruneKind pk, Shape sh, QualifierKind qk, ExprKind ek)297     TestDefaultSuperCall(InterfaceKind ik, PruneKind pk, Shape sh,
298             QualifierKind qk, ExprKind ek) {
299         this.ik = ik;
300         this.pk = pk;
301         this.sh = sh;
302         this.qk = qk;
303         this.ek = ek;
304         this.source = new JavaSource();
305         this.diagChecker = new DiagnosticChecker();
306     }
307 
308     class JavaSource extends SimpleJavaFileObject {
309 
310         String template = "interface E {}\n" +
311                           "interface B { }\n" +
312                           "#I\n" +
313                           "#P\n" +
314                           "#C";
315 
316         String source;
317 
JavaSource()318         public JavaSource() {
319             super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
320             source = template.replaceAll("#I", ik.interfaceStr)
321                     .replaceAll("#P", pk.interfaceStr)
322                     .replaceAll("#C", sh.getShape(qk, ek));
323         }
324 
325         @Override
getCharContent(boolean ignoreEncodingErrors)326         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
327             return source;
328         }
329     }
330 
run()331     public void run() {
332         JavacTask ct = (JavacTask)comp.getTask(null, fm.get(), diagChecker,
333                 null, null, Arrays.asList(source));
334         try {
335             ct.analyze();
336         } catch (Throwable ex) {
337             processException(ex);
338             return;
339         }
340         check();
341     }
342 
check()343     void check() {
344         boolean errorExpected = false;
345 
346         boolean badEnclosing = false;
347         boolean badThis = false;
348         boolean badSuper = false;
349 
350         if (qk == QualifierKind.ENCLOSING_1 &&
351                 sh.enclosingNames.size() < 1) {
352             errorExpected |= true;
353             badEnclosing = true;
354         }
355 
356         if (qk == QualifierKind.ENCLOSING_2 &&
357                 sh.enclosingNames.size() < 2) {
358             errorExpected |= true;
359             badEnclosing = true;
360         }
361 
362         if (ek == ExprKind.THIS) {
363             boolean found = false;
364             for (int i = 0; i < sh.enclosingElements.size(); i++) {
365                 if (sh.enclosingElements.get(i) == ElementKind.ANON_CLASS) continue;
366                 if (sh.enclosingNames.get(i).equals(qk.getQualifier(sh))) {
367                     found = sh.elementsWithMethod.contains(sh.enclosingNames.get(i));
368                     break;
369                 }
370             }
371             errorExpected |= !found;
372             if (!found) {
373                 badThis = true;
374             }
375         }
376 
377         if (ek == ExprKind.SUPER) {
378 
379             int lastIdx = sh.enclosingElements.size() - 1;
380             boolean found = lastIdx == -1 ? false :
381                     sh.enclosingElements.get(lastIdx).hasSuper() &&
382                     qk.allowSuperCall(ik, pk);
383 
384             errorExpected |= !found;
385             if (!found) {
386                 badSuper = true;
387             }
388         }
389 
390         checkCount.incrementAndGet();
391         if (diagChecker.errorFound != errorExpected) {
392             throw new AssertionError("Problem when compiling source:\n" +
393                     source.getCharContent(true) +
394                     "\nenclosingElems: " + sh.enclosingElements +
395                     "\nenclosingNames: " + sh.enclosingNames +
396                     "\nelementsWithMethod: " + sh.elementsWithMethod +
397                     "\nbad encl: " + badEnclosing +
398                     "\nbad this: " + badThis +
399                     "\nbad super: " + badSuper +
400                     "\nqual kind: " + qk +
401                     "\nfound error: " + diagChecker.errorFound);
402         }
403     }
404 
405     static class DiagnosticChecker
406         implements javax.tools.DiagnosticListener<JavaFileObject> {
407 
408         boolean errorFound;
409 
report(Diagnostic<? extends JavaFileObject> diagnostic)410         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
411             if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
412                 errorFound = true;
413             }
414         }
415     }
416 
417 }
418