# HG changeset patch # User mcimadamore # Date 1306148155 -3600 # Node ID 6bb526ccf5ffa26453eda3973594767f1a7c3995 # Parent 8987de9a4ab8de1256c1bd3ab3a763e77da41a5f 7046348: Regression: javac complains of missing classfile for a seemingly unrelated interface Summary: Types.implementation forces unnecessary symbol completion on superinterfaces of a given type Reviewed-by: jjg diff -r 8987de9a4ab8 -r 6bb526ccf5ff src/share/classes/com/sun/tools/javac/code/Symbol.java --- a/src/share/classes/com/sun/tools/javac/code/Symbol.java Fri May 20 16:04:23 2011 -0700 +++ b/src/share/classes/com/sun/tools/javac/code/Symbol.java Mon May 23 11:55:55 2011 +0100 @@ -729,10 +729,6 @@ */ public Pool pool; - /** members closure cache (set by Types.membersClosure) - */ - Scope.CompoundScope membersClosure; - public ClassSymbol(long flags, Name name, Type type, Symbol owner) { super(flags, name, type, owner); this.members_field = null; diff -r 8987de9a4ab8 -r 6bb526ccf5ff src/share/classes/com/sun/tools/javac/code/Types.java --- a/src/share/classes/com/sun/tools/javac/code/Types.java Fri May 20 16:04:23 2011 -0700 +++ b/src/share/classes/com/sun/tools/javac/code/Types.java Mon May 23 11:55:55 2011 +0100 @@ -2061,7 +2061,7 @@ _map.put(ms, new SoftReference>(cache)); } Entry e = cache.get(origin); - CompoundScope members = membersClosure(origin.type); + CompoundScope members = membersClosure(origin.type, true); if (e == null || !e.matches(implFilter, checkResult, members.getMark())) { MethodSymbol impl = implementationInternal(ms, origin, checkResult, implFilter); @@ -2098,36 +2098,61 @@ // // - public CompoundScope membersClosure(Type site) { - return membersClosure.visit(site); - } - - UnaryVisitor membersClosure = new UnaryVisitor() { - - public CompoundScope visitType(Type t, Void s) { + class MembersClosureCache extends SimpleVisitor { + + private WeakHashMap _map = + new WeakHashMap(); + + class Entry { + final boolean skipInterfaces; + final CompoundScope compoundScope; + + public Entry(boolean skipInterfaces, CompoundScope compoundScope) { + this.skipInterfaces = skipInterfaces; + this.compoundScope = compoundScope; + } + + boolean matches(boolean skipInterfaces) { + return this.skipInterfaces == skipInterfaces; + } + } + + /** members closure visitor methods **/ + + public CompoundScope visitType(Type t, Boolean skipInterface) { return null; } @Override - public CompoundScope visitClassType(ClassType t, Void s) { + public CompoundScope visitClassType(ClassType t, Boolean skipInterface) { ClassSymbol csym = (ClassSymbol)t.tsym; - if (csym.membersClosure == null) { + Entry e = _map.get(csym); + if (e == null || !e.matches(skipInterface)) { CompoundScope membersClosure = new CompoundScope(csym); - for (Type i : interfaces(t)) { - membersClosure.addSubScope(visit(i)); + if (!skipInterface) { + for (Type i : interfaces(t)) { + membersClosure.addSubScope(visit(i, skipInterface)); + } } - membersClosure.addSubScope(visit(supertype(t))); + membersClosure.addSubScope(visit(supertype(t), skipInterface)); membersClosure.addSubScope(csym.members()); - csym.membersClosure = membersClosure; + e = new Entry(skipInterface, membersClosure); + _map.put(csym, e); } - return csym.membersClosure; + return e.compoundScope; } @Override - public CompoundScope visitTypeVar(TypeVar t, Void s) { - return visit(t.getUpperBound()); + public CompoundScope visitTypeVar(TypeVar t, Boolean skipInterface) { + return visit(t.getUpperBound(), skipInterface); } - }; + } + + private MembersClosureCache membersCache = new MembersClosureCache(); + + public CompoundScope membersClosure(Type site, boolean skipInterface) { + return membersCache.visit(site, skipInterface); + } // /** diff -r 8987de9a4ab8 -r 6bb526ccf5ff src/share/classes/com/sun/tools/javac/comp/Check.java --- a/src/share/classes/com/sun/tools/javac/comp/Check.java Fri May 20 16:04:23 2011 -0700 +++ b/src/share/classes/com/sun/tools/javac/comp/Check.java Mon May 23 11:55:55 2011 +0100 @@ -2098,10 +2098,10 @@ void checkOverrideClashes(DiagnosticPosition pos, Type site, MethodSymbol sym) { ClashFilter cf = new ClashFilter(site); //for each method m1 that is a member of 'site'... - for (Symbol s1 : types.membersClosure(site).getElementsByName(sym.name, cf)) { + for (Symbol s1 : types.membersClosure(site, false).getElementsByName(sym.name, cf)) { //...find another method m2 that is overridden (directly or indirectly) //by method 'sym' in 'site' - for (Symbol s2 : types.membersClosure(site).getElementsByName(sym.name, cf)) { + for (Symbol s2 : types.membersClosure(site, false).getElementsByName(sym.name, cf)) { if (s1 == s2 || !sym.overrides(s2, site.tsym, types, false)) continue; //if (i) the signature of 'sym' is not a subsignature of m1 (seen as //a member of 'site') and (ii) m1 has the same erasure as m2, issue an error @@ -2134,7 +2134,7 @@ void checkHideClashes(DiagnosticPosition pos, Type site, MethodSymbol sym) { ClashFilter cf = new ClashFilter(site); //for each method m1 that is a member of 'site'... - for (Symbol s : types.membersClosure(site).getElementsByName(sym.name, cf)) { + for (Symbol s : types.membersClosure(site, true).getElementsByName(sym.name, cf)) { //if (i) the signature of 'sym' is not a subsignature of m1 (seen as //a member of 'site') and (ii) 'sym' has the same erasure as m1, issue an error if (!types.isSubSignature(sym.type, types.memberType(site, s), false) && diff -r 8987de9a4ab8 -r 6bb526ccf5ff test/tools/javac/scope/7046348/EagerInterfaceCompletionTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/scope/7046348/EagerInterfaceCompletionTest.java Mon May 23 11:55:55 2011 +0100 @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2011, 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 7046348 + * @summary Regression: javac complains of missing classfile for a seemingly unrelated interface + */ + +import java.io.File; +import java.net.URI; +import java.util.Arrays; + +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +public class EagerInterfaceCompletionTest { + + JavaCompiler javacTool; + File testDir; + HierarchyKind hierarchyKind; + TestKind testKind; + ActionKind actionKind; + + EagerInterfaceCompletionTest(JavaCompiler javacTool, File testDir, + HierarchyKind hierarchyKind, TestKind testKind, ActionKind actionKind) { + this.javacTool = javacTool; + this.hierarchyKind = hierarchyKind; + this.testDir = testDir; + this.testKind = testKind; + this.actionKind = actionKind; + } + + void test() { + testDir.mkdirs(); + compile(null, hierarchyKind.source); + actionKind.doAction(this); + DiagnosticChecker dc = new DiagnosticChecker(); + compile(dc, testKind.source); + if (testKind.completionFailure(actionKind, hierarchyKind) != dc.errorFound) { + if (dc.errorFound) { + error("Unexpected completion failure" + + "\nhierarhcyKind " + hierarchyKind + + "\ntestKind " + testKind + + "\nactionKind " + actionKind); + } else { + error("Missing completion failure " + + "\nhierarhcyKind " + hierarchyKind + + "\ntestKind " + testKind + + "\nactionKind " + actionKind); + } + } + } + + void compile(DiagnosticChecker dc, JavaSource... sources) { + try { + CompilationTask ct = javacTool.getTask(null, null, dc, + Arrays.asList("-d", testDir.getAbsolutePath(), "-cp", testDir.getAbsolutePath()), + null, Arrays.asList(sources)); + ct.call(); + } + catch (Exception e) { + e.printStackTrace(); + error("Internal compilation error"); + } + } + + void removeClass(String classToRemoveStr) { + File classToRemove = new File(testDir, classToRemoveStr); + if (!classToRemove.exists()) { + error("Expected file " + classToRemove + " does not exists in folder " + testDir); + } + classToRemove.delete(); + }; + + void error(String msg) { + System.err.println(msg); + nerrors++; + } + + class DiagnosticChecker implements DiagnosticListener { + + boolean errorFound = false; + + public void report(Diagnostic diagnostic) { + errorFound = true; + } + } + + //global declarations + + enum HierarchyKind { + INTERFACE("interface A { boolean f = false; void m(); }\n" + + "class B implements A { public void m() {} }"), + CLASS("class A { boolean f; void m() {} }\n" + + "class B extends A { void m() {} }"), + ABSTRACT_CLASS("abstract class A { boolean f; abstract void m(); }\n" + + "class B extends A { void m() {} }"); + + JavaSource source; + + private HierarchyKind(String code) { + this.source = new JavaSource("Test1.java", code); + } + } + + enum ActionKind { + REMOVE_A("A.class"), + REMOVE_B("B.class"); + + String classFile; + + private ActionKind(String classFile) { + this.classFile = classFile; + } + + void doAction(EagerInterfaceCompletionTest test) { + test.removeClass(classFile); + }; + } + + enum TestKind { + ACCESS_ONLY("class C { B b; }"), + SUPER("class C extends B {}"), + METHOD("class C { void test(B b) { b.m(); } }"), + FIELD("class C { void test(B b) { boolean b2 = b.f; } }"), + CONSTR("class C { void test() { new B(); } }"); + + JavaSource source; + + private TestKind(final String code) { + this.source = new JavaSource("Test2.java", code); + } + + boolean completionFailure(ActionKind ak, HierarchyKind hk) { + switch (this) { + case ACCESS_ONLY: + case CONSTR: return ak == ActionKind.REMOVE_B; + case FIELD: + case SUPER: return true; + case METHOD: return hk != HierarchyKind.INTERFACE || ak == ActionKind.REMOVE_B; + default: throw new AssertionError("Unexpected test kind " + this); + } + } + } + + public static void main(String[] args) throws Exception { + String SCRATCH_DIR = System.getProperty("user.dir"); + JavaCompiler javacTool = ToolProvider.getSystemJavaCompiler(); + int n = 0; + for (HierarchyKind hierarchyKind : HierarchyKind.values()) { + for (TestKind testKind : TestKind.values()) { + for (ActionKind actionKind : ActionKind.values()) { + File testDir = new File(SCRATCH_DIR, "test"+n); + new EagerInterfaceCompletionTest(javacTool, testDir, hierarchyKind, testKind, actionKind).test(); + n++; + } + } + } + if (nerrors > 0) { + throw new AssertionError("Some errors have been detected"); + } + } + + static class JavaSource extends SimpleJavaFileObject { + + String source; + + public JavaSource(String filename, String source) { + super(URI.create("myfo:/" + filename), JavaFileObject.Kind.SOURCE); + this.source = source; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return source; + } + } + + static int nerrors = 0; +}