changeset 903:351027202f60

7017664: Add listeners infrastracture to javac scopes Summary: Add listeners to javac scopes, added CompoundScope and correct invalidation logic for ImplementationCache Reviewed-by: jjg
author mcimadamore
date Tue, 15 Feb 2011 11:49:46 +0000
parents ef6c66215a93
children fa0e4e1916f4
files src/share/classes/com/sun/tools/javac/code/Scope.java src/share/classes/com/sun/tools/javac/code/Symbol.java src/share/classes/com/sun/tools/javac/code/Types.java src/share/classes/com/sun/tools/javac/comp/Check.java test/tools/javac/scope/7017664/CompoundScopeTest.java test/tools/javac/scope/7017664/ImplementationCacheTest.java
diffstat 6 files changed, 576 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/tools/javac/code/Scope.java	Mon Feb 14 14:27:47 2011 -0800
+++ b/src/share/classes/com/sun/tools/javac/code/Scope.java	Tue Feb 15 11:49:46 2011 +0000
@@ -74,7 +74,7 @@
 
     /** A list of scopes to be notified if items are to be removed from this scope.
      */
-    List<Scope> listeners = List.nil();
+    List<ScopeListener> listeners = List.nil();
 
     /** Use as a "not-found" result for lookup.
      * Also used to mark deleted entries in the table.
@@ -219,12 +219,27 @@
         Entry e = makeEntry(sym, old, elems, s, origin);
         table[hash] = e;
         elems = e;
+
+        //notify listeners
+        for (List<ScopeListener> l = listeners; l.nonEmpty(); l = l.tail) {
+            l.head.symbolAdded(sym, this);
+        }
     }
 
     Entry makeEntry(Symbol sym, Entry shadowed, Entry sibling, Scope scope, Scope origin) {
         return new Entry(sym, shadowed, sibling, scope);
     }
 
+
+    public interface ScopeListener {
+        public void symbolAdded(Symbol sym, Scope s);
+        public void symbolRemoved(Symbol sym, Scope s);
+    }
+
+    public void addScopeListener(ScopeListener sl) {
+        listeners = listeners.prepend(sl);
+    }
+
     /** Remove symbol from this scope.  Used when an inner class
      *  attribute tells us that the class isn't a package member.
      */
@@ -258,9 +273,9 @@
             te = te.sibling;
         }
 
-        // remove items from scopes that have done importAll
-        for (List<Scope> l = listeners; l.nonEmpty(); l = l.tail) {
-            l.head.remove(sym);
+        //notify listeners
+        for (List<ScopeListener> l = listeners; l.nonEmpty(); l = l.tail) {
+            l.head.symbolRemoved(sym, this);
         }
     }
 
@@ -393,7 +408,32 @@
                 };
             }
         };
+    }
 
+    public Iterable<Symbol> getElementsByName(Name name) {
+        return getElementsByName(name, noFilter);
+    }
+
+    public Iterable<Symbol> getElementsByName(final Name name, final Filter<Symbol> sf) {
+        return new Iterable<Symbol>() {
+            public Iterator<Symbol> iterator() {
+                 return new Iterator<Symbol>() {
+                    Scope.Entry currentEntry = lookup(name, sf);
+
+                    public boolean hasNext() {
+                        return currentEntry.scope != null;
+                    }
+                    public Symbol next() {
+                        Scope.Entry prevEntry = currentEntry;
+                        currentEntry = currentEntry.next(sf);
+                        return prevEntry.sym;
+                    }
+                    public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+        };
     }
 
     public String toString() {
@@ -488,7 +528,7 @@
         }
     }
 
-    public static class StarImportScope extends ImportScope {
+    public static class StarImportScope extends ImportScope implements ScopeListener {
 
         public StarImportScope(Symbol owner) {
             super(owner);
@@ -500,8 +540,13 @@
                     enter(e.sym, fromScope);
             }
             // Register to be notified when imported items are removed
-            fromScope.listeners = fromScope.listeners.prepend(this);
+            fromScope.addScopeListener(this);
         }
+
+        public void symbolRemoved(Symbol sym, Scope s) {
+            remove(sym);
+        }
+        public void symbolAdded(Symbol sym, Scope s) { }
     }
 
     /** An empty scope, into which you can't place anything.  Used for
@@ -538,6 +583,151 @@
         }
     }
 
+    /** A class scope adds capabilities to keep track of changes in related
+     *  class scopes - this allows client to realize whether a class scope
+     *  has changed, either directly (because a new member has been added/removed
+     *  to this scope) or indirectly (i.e. because a new member has been
+     *  added/removed into a supertype scope)
+     */
+    public static class CompoundScope extends Scope implements ScopeListener {
+
+        public static final Entry[] emptyTable = new Entry[0];
+
+        private List<Scope> subScopes = List.nil();
+        private int mark = 0;
+
+        public CompoundScope(Symbol owner) {
+            super(null, owner, emptyTable);
+        }
+
+        public void addSubScope(Scope that) {
+           if (that != null) {
+                subScopes = subScopes.prepend(that);
+                that.addScopeListener(this);
+                mark++;
+                for (ScopeListener sl : listeners) {
+                    sl.symbolAdded(null, this); //propagate upwards in case of nested CompoundScopes
+                }
+           }
+         }
+
+        public void symbolAdded(Symbol sym, Scope s) {
+            mark++;
+            for (ScopeListener sl : listeners) {
+                sl.symbolAdded(sym, s);
+            }
+        }
+
+        public void symbolRemoved(Symbol sym, Scope s) {
+            mark++;
+            for (ScopeListener sl : listeners) {
+                sl.symbolRemoved(sym, s);
+            }
+        }
+
+        public int getMark() {
+            return mark;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder buf = new StringBuilder();
+            buf.append("CompoundScope{");
+            String sep = "";
+            for (Scope s : subScopes) {
+                buf.append(sep);
+                buf.append(s);
+                sep = ",";
+            }
+            buf.append("}");
+            return buf.toString();
+        }
+
+        @Override
+        public Iterable<Symbol> getElements(final Filter<Symbol> sf) {
+            return new Iterable<Symbol>() {
+                public Iterator<Symbol> iterator() {
+                    return new CompoundScopeIterator(subScopes) {
+                        Iterator<Symbol> nextIterator(Scope s) {
+                            return s.getElements().iterator();
+                        }
+                    };
+                }
+            };
+        }
+
+        @Override
+        public Iterable<Symbol> getElementsByName(final Name name, final Filter<Symbol> sf) {
+            return new Iterable<Symbol>() {
+                public Iterator<Symbol> iterator() {
+                    return new CompoundScopeIterator(subScopes) {
+                        Iterator<Symbol> nextIterator(Scope s) {
+                            return s.getElementsByName(name, sf).iterator();
+                        }
+                    };
+                }
+            };
+        }
+
+        abstract class CompoundScopeIterator implements Iterator<Symbol> {
+
+            private Iterator<Symbol> currentIterator;
+            private List<Scope> scopesToScan;
+
+            public CompoundScopeIterator(List<Scope> scopesToScan) {
+                this.scopesToScan = scopesToScan;
+                update();
+            }
+
+            abstract Iterator<Symbol> nextIterator(Scope s);
+
+            public boolean hasNext() {
+                return currentIterator != null;
+            }
+
+            public Symbol next() {
+                Symbol sym = currentIterator.next();
+                if (!currentIterator.hasNext()) {
+                    update();
+                }
+                return sym;
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+
+            private void update() {
+                while (scopesToScan.nonEmpty()) {
+                    currentIterator = nextIterator(scopesToScan.head);
+                    scopesToScan = scopesToScan.tail;
+                    if (currentIterator.hasNext()) return;
+                }
+                currentIterator = null;
+            }
+        }
+
+        @Override
+        public Entry lookup(Name name, Filter<Symbol> sf) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Scope dup(Symbol newOwner) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void enter(Symbol sym, Scope s, Scope origin) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void remove(Symbol sym) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
     /** An error scope, for which the owner should be an error symbol. */
     public static class ErrorScope extends Scope {
         ErrorScope(Scope next, Symbol errSymbol, Entry[] table) {
--- a/src/share/classes/com/sun/tools/javac/code/Symbol.java	Mon Feb 14 14:27:47 2011 -0800
+++ b/src/share/classes/com/sun/tools/javac/code/Symbol.java	Tue Feb 15 11:49:46 2011 +0000
@@ -731,7 +731,7 @@
 
         /** members closure cache (set by Types.membersClosure)
          */
-        Scope membersClosure;
+        Scope.CompoundScope membersClosure;
 
         public ClassSymbol(long flags, Name name, Type type, Symbol owner) {
             super(flags, name, type, owner);
--- a/src/share/classes/com/sun/tools/javac/code/Types.java	Mon Feb 14 14:27:47 2011 -0800
+++ b/src/share/classes/com/sun/tools/javac/code/Types.java	Tue Feb 15 11:49:46 2011 +0000
@@ -2023,18 +2023,22 @@
             final MethodSymbol cachedImpl;
             final Filter<Symbol> implFilter;
             final boolean checkResult;
+            final int prevMark;
 
             public Entry(MethodSymbol cachedImpl,
                     Filter<Symbol> scopeFilter,
-                    boolean checkResult) {
+                    boolean checkResult,
+                    int prevMark) {
                 this.cachedImpl = cachedImpl;
                 this.implFilter = scopeFilter;
                 this.checkResult = checkResult;
+                this.prevMark = prevMark;
             }
 
-            boolean matches(Filter<Symbol> scopeFilter, boolean checkResult) {
+            boolean matches(Filter<Symbol> scopeFilter, boolean checkResult, int mark) {
                 return this.implFilter == scopeFilter &&
-                        this.checkResult == checkResult;
+                        this.checkResult == checkResult &&
+                        this.prevMark == mark;
             }
         }
 
@@ -2046,10 +2050,11 @@
                 _map.put(ms, new SoftReference<Map<TypeSymbol, Entry>>(cache));
             }
             Entry e = cache.get(origin);
+            CompoundScope members = membersClosure(origin.type);
             if (e == null ||
-                    !e.matches(implFilter, checkResult)) {
-                MethodSymbol impl = implementationInternal(ms, origin, Types.this, checkResult, implFilter);
-                cache.put(origin, new Entry(impl, implFilter, checkResult));
+                    !e.matches(implFilter, checkResult, members.getMark())) {
+                MethodSymbol impl = implementationInternal(ms, origin, checkResult, implFilter);
+                cache.put(origin, new Entry(impl, implFilter, checkResult, members.getMark()));
                 return impl;
             }
             else {
@@ -2057,8 +2062,8 @@
             }
         }
 
-        private MethodSymbol implementationInternal(MethodSymbol ms, TypeSymbol origin, Types types, boolean checkResult, Filter<Symbol> implFilter) {
-            for (Type t = origin.type; t.tag == CLASS || t.tag == TYPEVAR; t = types.supertype(t)) {
+        private MethodSymbol implementationInternal(MethodSymbol ms, TypeSymbol origin, boolean checkResult, Filter<Symbol> implFilter) {
+            for (Type t = origin.type; t.tag == CLASS || t.tag == TYPEVAR; t = supertype(t)) {
                 while (t.tag == TYPEVAR)
                     t = t.getUpperBound();
                 TypeSymbol c = t.tsym;
@@ -2066,7 +2071,7 @@
                      e.scope != null;
                      e = e.next(implFilter)) {
                     if (e.sym != null &&
-                             e.sym.overrides(ms, origin, types, checkResult))
+                             e.sym.overrides(ms, origin, Types.this, checkResult))
                         return (MethodSymbol)e.sym;
                 }
             }
@@ -2082,46 +2087,35 @@
     // </editor-fold>
 
     // <editor-fold defaultstate="collapsed" desc="compute transitive closure of all members in given site">
-    public Scope membersClosure(Type site) {
+    public CompoundScope membersClosure(Type site) {
         return membersClosure.visit(site);
     }
 
-    UnaryVisitor<Scope> membersClosure = new UnaryVisitor<Scope>() {
-
-        public Scope visitType(Type t, Void s) {
+    UnaryVisitor<CompoundScope> membersClosure = new UnaryVisitor<CompoundScope>() {
+
+        public CompoundScope visitType(Type t, Void s) {
             return null;
         }
 
         @Override
-        public Scope visitClassType(ClassType t, Void s) {
+        public CompoundScope visitClassType(ClassType t, Void s) {
             ClassSymbol csym = (ClassSymbol)t.tsym;
             if (csym.membersClosure == null) {
-                Scope membersClosure = new Scope(csym);
+                CompoundScope membersClosure = new CompoundScope(csym);
                 for (Type i : interfaces(t)) {
-                    enterAll(visit(i), membersClosure);
+                    membersClosure.addSubScope(visit(i));
                 }
-                enterAll(visit(supertype(t)), membersClosure);
-                enterAll(csym.members(), membersClosure);
+                membersClosure.addSubScope(visit(supertype(t)));
+                membersClosure.addSubScope(csym.members());
                 csym.membersClosure = membersClosure;
             }
             return csym.membersClosure;
         }
 
         @Override
-        public Scope visitTypeVar(TypeVar t, Void s) {
+        public CompoundScope visitTypeVar(TypeVar t, Void s) {
             return visit(t.getUpperBound());
         }
-
-        public void enterAll(Scope s, Scope to) {
-            if (s == null) return;
-            List<Symbol> syms = List.nil();
-            for (Scope.Entry e = s.elems ; e != null ; e = e.sibling) {
-                syms = syms.prepend(e.sym);
-            }
-            for (Symbol sym : syms) {
-                to.enter(sym);
-            }
-        }
     };
     // </editor-fold>
 
--- a/src/share/classes/com/sun/tools/javac/comp/Check.java	Mon Feb 14 14:27:47 2011 -0800
+++ b/src/share/classes/com/sun/tools/javac/comp/Check.java	Tue Feb 15 11:49:46 2011 +0000
@@ -2106,32 +2106,32 @@
     void checkOverrideClashes(DiagnosticPosition pos, Type site, MethodSymbol sym) {
          ClashFilter cf = new ClashFilter(site);
          //for each method m1 that is a member of 'site'...
-         for (Scope.Entry e1 = types.membersClosure(site).lookup(sym.name, cf) ;
-                e1.scope != null ; e1 = e1.next(cf)) {
+         for (Symbol s1 : types.membersClosure(site).getElementsByName(sym.name, cf)) {
             //...find another method m2 that is overridden (directly or indirectly)
             //by method 'sym' in 'site'
-            for (Scope.Entry e2 = types.membersClosure(site).lookup(sym.name, cf) ;
-                    e2.scope != null ; e2 = e2.next(cf)) {
-                if (e1.sym == e2.sym || !sym.overrides(e2.sym, site.tsym, types, false)) continue;
+            for (Symbol s2 : types.membersClosure(site).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
-                if (!types.isSubSignature(sym.type, types.memberType(site, e1.sym)) &&
-                        types.hasSameArgs(e1.sym.erasure(types), e2.sym.erasure(types))) {
+                if (!types.isSubSignature(sym.type, types.memberType(site, s1)) &&
+                        types.hasSameArgs(s1.erasure(types), s2.erasure(types))) {
                     sym.flags_field |= CLASH;
-                    String key = e2.sym == sym ?
+                    String key = s2 == sym ?
                             "name.clash.same.erasure.no.override" :
                             "name.clash.same.erasure.no.override.1";
                     log.error(pos,
                             key,
                             sym, sym.location(),
-                            e1.sym, e1.sym.location(),
-                            e2.sym, e2.sym.location());
+                            s1, s1.location(),
+                            s2, s2.location());
                     return;
                 }
             }
         }
     }
 
+
+
     /** Check that all static methods accessible from 'site' are
      *  mutually compatible (JLS 8.4.8).
      *
@@ -2142,16 +2142,15 @@
     void checkHideClashes(DiagnosticPosition pos, Type site, MethodSymbol sym) {
         ClashFilter cf = new ClashFilter(site);
         //for each method m1 that is a member of 'site'...
-        for (Scope.Entry e = types.membersClosure(site).lookup(sym.name, cf) ;
-                e.scope != null ; e = e.next(cf)) {
+        for (Symbol s : types.membersClosure(site).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, e.sym)) &&
-                    types.hasSameArgs(e.sym.erasure(types), sym.erasure(types))) {
+            if (!types.isSubSignature(sym.type, types.memberType(site, s)) &&
+                    types.hasSameArgs(s.erasure(types), sym.erasure(types))) {
                 log.error(pos,
                         "name.clash.same.erasure.no.hide",
                         sym, sym.location(),
-                        e.sym, e.sym.location());
+                        s, s.location());
                 return;
              }
          }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/scope/7017664/CompoundScopeTest.java	Tue Feb 15 11:49:46 2011 +0000
@@ -0,0 +1,212 @@
+/*
+ * 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 7017664
+ * @summary Basher for CompoundScopes
+ */
+
+import java.util.Random;
+import java.util.Map;
+import java.util.HashMap;
+import com.sun.tools.javac.util.*;
+import com.sun.tools.javac.code.*;
+import com.sun.tools.javac.code.Scope.*;
+import com.sun.tools.javac.code.Symbol.*;
+import com.sun.tools.javac.file.JavacFileManager;
+
+public class CompoundScopeTest {
+    public static void main(String... args) throws Exception {
+        new CompoundScopeTest().run(args);
+    }
+
+    static final int MAX_SYMBOLS_COUNT = 20;
+    static final int PASSES = 10;
+
+    void run(String... args) throws Exception {
+        int count = PASSES;
+
+        for (int i = 0; i < args.length; i++) {
+            String arg = args[i];
+            if (arg.equals("-seed") && (i + 1 < args.length))
+                seed = Long.parseLong(args[++i]);
+            else if(arg.equals("-tests") && (i + 1 < args.length))
+                count = Integer.parseInt(args[++i]);
+            else
+                throw new Exception("unknown arg: " + arg);
+        }
+
+        rgen = new Random(seed);
+
+        for (int i = 0; i < count; i++) {
+            Test t = new Test();
+            t.run();
+        }
+
+        if (errors > 0)
+            throw new Exception(errors + " errors found");
+    }
+
+    /**
+     * Write a message to stderr.
+     */
+    void log(String msg) {
+        System.err.println(msg);
+    }
+
+    /**
+     * Write an error message to stderr.
+     */
+    void error(String msg) {
+        System.err.println("Error: " + msg);
+        errors++;
+    }
+
+    Random rgen;
+    long seed = 0;
+
+    int errors;
+
+    /** Class to encapsulate a test run. */
+    class Test {
+
+        List<Symbol> elems = List.nil();
+        Map<Name, List<Symbol>> shadowedMap = new HashMap<Name, List<Symbol>>();
+
+        /** Run the test. */
+        void run() throws Exception {
+            log ("starting test");
+            setup();
+            Scope[] scopes = { createScope(rgen.nextInt(MAX_SYMBOLS_COUNT)),
+                               createScope(rgen.nextInt(MAX_SYMBOLS_COUNT)),
+                               createScope(rgen.nextInt(MAX_SYMBOLS_COUNT)) };
+            boolean[][] scopeNesting = { {false, true, false, true},
+                                   {false, true, true, true},
+                                   {false, false, true, true} };
+            /**
+             * We want to generate (and check) the following compound scopes:
+             * C1 = C(S1, S2, S3)
+             * C2 = C((S1, S2), S3)
+             * C3 = C(S1, (S2, S3))
+             * C3 = C(C(S1, S2, S3))
+             */
+            for (int i = 0 ; i < 4 ; i ++) {
+                CompoundScope root = new CompoundScope(symtab.noSymbol);
+                CompoundScope sub = new CompoundScope(symtab.noSymbol);
+                boolean subAdded = false;
+                for (int sc = 0 ; sc < 3 ; sc ++) {
+                    if (scopeNesting[sc][i]) {
+                        sub.addSubScope(scopes[sc]);
+                        if (!subAdded) {
+                            root.addSubScope(sub);
+                            subAdded = true;
+                        }
+                    } else {
+                        root.addSubScope(scopes[sc]);
+                    }
+                }
+                log("testing scope: " + root);
+                checkElems(root);
+                checkShadowed(root);
+            }
+        }
+
+        /**
+         * Create a scope containing a given number of synthetic symbols
+         */
+        Scope createScope(int nelems) {
+            Scope s = new Scope(symtab.noSymbol);
+            for (int i = 0 ; i < nelems ; i++) {
+                Symbol sym = new TypeSymbol(0, names.fromString("s" + i), null, null);
+                s.enter(sym);
+                elems = elems.prepend(sym);
+                List<Symbol> shadowed = shadowedMap.get(sym.name);
+                if (shadowed == null) {
+                    shadowed = List.nil();
+                }
+                shadowedMap.put(sym.name, shadowed.prepend(sym));
+            }
+            return s;
+        }
+
+        /**
+         * Setup compiler context
+         */
+        void setup() {
+            log ("setup");
+            context = new Context();
+            JavacFileManager.preRegister(context); // required by ClassReader which is required by Symtab
+            names = Names.instance(context);       // Name.Table impls tied to an instance of Names
+            symtab = Symtab.instance(context);
+        }
+
+        /**
+         * Check that CompoundScope.getElements() correctly visits all symbols
+         * in all subscopes (in the correct order)
+         */
+        void checkElems(CompoundScope cs) {
+            List<Symbol> allSymbols = elems;
+            int count = 0;
+            for (Symbol s : cs.getElements()) {
+                checkSameSymbols(s, allSymbols.head);
+                allSymbols = allSymbols.tail;
+                count++;
+            }
+            if (count != elems.size()) {
+                error("CompoundScope.getElements() did not returned enough symbols");
+            }
+        }
+
+        /**
+         * Check that CompoundScope.getElements() correctly visits all symbols
+         * with a given name in all subscopes (in the correct order)
+         */
+        void checkShadowed(CompoundScope cs) {
+            for (Map.Entry<Name, List<Symbol>> shadowedEntry : shadowedMap.entrySet()) {
+                int count = 0;
+                List<Symbol> shadowed = shadowedEntry.getValue();
+                Name name = shadowedEntry.getKey();
+                for (Symbol s : cs.getElementsByName(name)) {
+                    checkSameSymbols(s, shadowed.head);
+                    shadowed = shadowed.tail;
+                    count++;
+                }
+                if (count != shadowedEntry.getValue().size()) {
+                    error("CompoundScope.lookup() did not returned enough symbols for name " + name);
+                }
+            }
+        }
+
+        void checkSameSymbols(Symbol found, Symbol req) {
+            if (found != req) {
+                error("Symbol mismatch - found    : " + found + ":" + found.hashCode() + "\n" +
+                      "                  required : " + req + ":" + req.hashCode());
+            }
+        }
+
+        Context context;
+        Symtab symtab;
+        Names names;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/scope/7017664/ImplementationCacheTest.java	Tue Feb 15 11:49:46 2011 +0000
@@ -0,0 +1,129 @@
+/*
+ * 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 7017664
+ * @summary Basher for CompoundScopes
+ */
+
+import com.sun.source.util.JavacTask;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Types;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.util.Context;
+
+import com.sun.tools.javac.code.Symbol.*;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import javax.lang.model.element.Element;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.ToolProvider;
+
+import static javax.tools.JavaFileObject.Kind;
+
+public class ImplementationCacheTest {
+
+    static class SourceFile extends SimpleJavaFileObject {
+
+        final String source = "interface I { void m(); }\n" +
+                              "class A implements I { public void m() {} }\n" +
+                              "class B extends A { }\n";
+
+        public SourceFile() {
+            super(URI.create("test.java"), Kind.SOURCE);
+        }
+
+        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+            return source;
+        }
+    }
+
+    public static void main(String[] args) throws IOException {
+        List<? extends JavaFileObject> files = Arrays.asList(new SourceFile());
+        JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
+        JavacTask ct = (JavacTask)tool.getTask(null, null, null, null, null, files);
+        Context ctx = new Context();
+        JavacFileManager.preRegister(ctx);
+        checkImplementationCache(ct.analyze(), Types.instance(ctx));
+    }
+
+    static void checkImplementationCache(Iterable<? extends Element> elements, Types types) {
+        if (types == null) {
+            throw new AssertionError("problems initializing Types");
+        }
+
+        Symbol a = null;
+        Symbol b = null;
+        Symbol i = null;
+
+        for (Element e : elements) {
+            if (e.getSimpleName().contentEquals("A")) {
+                a = (Symbol)e;
+            } else if (e.getSimpleName().contentEquals("B")) {
+                b = (Symbol)e;
+            } else if (e.getSimpleName().contentEquals("I")) {
+                i = (Symbol)e;
+            }
+        }
+
+        if (a == null || b == null || i == null) {
+            throw new AssertionError("missing class");
+        }
+
+        MethodSymbol I_m = null;
+
+        for (Symbol sym : i.members().getElements()) {
+            if (sym.name.contentEquals("m")) {
+                I_m = (MethodSymbol)sym;
+            }
+        }
+
+        if (I_m == null) {
+            throw new AssertionError("missing method m() in scope of interface I");
+        }
+
+        Symbol impl = I_m.implementation((TypeSymbol)b, types, true);
+
+        if (impl == null || impl.owner != a) {
+            throw new AssertionError("wrong implementation for m() in B");
+        }
+
+        b.members().enter(I_m.clone(b));
+
+        Symbol newImpl = I_m.implementation((TypeSymbol)b, types, true);
+
+        if (newImpl == impl) {
+            throw new AssertionError("stale implementation for m() in B");
+        }
+
+        if (newImpl == null || newImpl.owner != b) {
+            throw new AssertionError("wrong implementation for m() in B");
+        }
+    }
+}