DupUnsharedTest.java revision 2717:532a67fe69ec
1/*
2 * Copyright (c) 2014, 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 * @summary WriteableScope.dupUnshared not working properly for shared Scopes.
27 */
28
29import com.sun.tools.javac.util.*;
30import com.sun.tools.javac.code.*;
31import com.sun.tools.javac.code.Scope.*;
32import com.sun.tools.javac.code.Symbol.*;
33import com.sun.tools.javac.file.JavacFileManager;
34import java.lang.reflect.Field;
35import java.lang.reflect.Method;
36import java.util.Collections;
37import java.util.IdentityHashMap;
38import java.util.Objects;
39import java.util.Set;
40
41public class DupUnsharedTest {
42    public static void main(String... args) throws Exception {
43        new DupUnsharedTest().run();
44    }
45
46    Context context;
47    Names names;
48    Symtab symtab;
49    Name a;
50    Name b;
51    int errors;
52
53    public DupUnsharedTest() {
54        context = new Context();
55        JavacFileManager.preRegister(context); // required by ClassReader which is required by Symtab
56        names = Names.instance(context);
57        symtab = Symtab.instance(context);
58        a = names.fromString("a");
59        b = names.fromString("b");
60    }
61
62    void run() throws Exception {
63        runScopeContentTest();
64        runClashTest();
65
66        if (errors > 0)
67            throw new AssertionError("Errors detected (" + errors + ").");
68    }
69
70    void runScopeContentTest() throws Exception {
71        Set<Symbol> expected = Collections.newSetFromMap(new IdentityHashMap<>());
72        Set<Symbol> notExpected = Collections.newSetFromMap(new IdentityHashMap<>());
73        WriteableScope s1 = WriteableScope.create(symtab.rootPackage);
74        ClassSymbol acceptSym = symtab.arrayClass;
75        s1.enter(acceptSym);
76        expected.add(acceptSym);
77        WriteableScope s2 = s1.dup();
78        fillScope(s2, notExpected, a);
79        WriteableScope s3 = s2.dup();
80        fillScope(s3, notExpected, b);
81        WriteableScope s4 = s1.dupUnshared();
82        assertEquals(toSet(s4.getSymbols()), expected);
83        assertEquals(toSet(s4.getSymbolsByName(a)), Collections.emptySet());
84        assertEquals(toSet(s4.getSymbolsByName(b)), Collections.emptySet());
85        assertEquals(toSet(s4.getSymbolsByName(acceptSym.name)), expected);
86        for (Symbol sym : notExpected) {
87            try {
88                s4.remove(sym);
89            } catch (Exception ex) {
90                System.err.println("s4.remove(" + sym + "); crashes with exception:");
91                ex.printStackTrace();
92                errors++;
93            }
94        }
95    }
96
97    void fillScope(WriteableScope scope, Set<Symbol> notExpected, Name name) {
98        VarSymbol var1 = new VarSymbol(0, name, Type.noType, symtab.arrayClass);
99        VarSymbol var2 = new VarSymbol(0, name, Type.noType, symtab.autoCloseableClose.owner);
100        scope.enter(var1);
101        scope.enter(var2);
102        scope.remove(var1);
103        notExpected.add(var1);
104        notExpected.add(var2);
105    }
106
107    Set<Symbol> toSet(Iterable<Symbol> it) {
108        Set<Symbol> result = Collections.newSetFromMap(new IdentityHashMap<>());
109
110        for (Symbol sym : it) {
111            result.add(sym);
112        }
113
114        return result;
115    }
116
117    void assertEquals(Set<Symbol> set1, Set<Symbol> set2) {
118        if (!Objects.equals(set1, set2)) {
119            System.err.println("Sets are not equals: s1=" + set1 + "; s2=" + set2);
120            errors++;
121        }
122    }
123
124    /**
125     * This tests tests the following situation.
126     * - consider empty Scope S1
127     * - a Symbol with name 'A' is added into S1
128     * - S1 is dupped into S2
129     * - a Symbol with name 'B', clashing with 'A', is added into S2
130     * - so the table now looks like: [..., A, ..., B, ...]
131     * - S2 is doubled. As a consequence, the table is re-hashed, and looks like:
132     *   [..., B, ..., A, ...] (note that re-hashing goes from the end, hence the original order).
133     * - B has been chosen so that it clashes in the doubled scope as well. So when looking up 'A',
134     *   B is found (and rejected) first, and only then the A's bucket is tested.
135     * - S2 is dupUshared - the resulting table needs to look like: [..., /sentinel/, ..., A, ...], not
136     *   [..., null, ..., A, ...], as in the latter case lookups would see 'null' while looking for
137     *   'A' and would stop the search prematurely.
138     */
139    void runClashTest() throws Exception {
140        WriteableScope emptyScope = WriteableScope.create(symtab.unnamedPackage);
141        Field tableField = emptyScope.getClass().getDeclaredField("table");
142        tableField.setAccessible(true);
143        Method dble = emptyScope.getClass().getDeclaredMethod("dble");
144        dble.setAccessible(true);
145        Method getIndex = emptyScope.getClass().getDeclaredMethod("getIndex", Name.class);
146        getIndex.setAccessible(true);
147
148        int tries = 0;
149
150        //find a name that will be in the first bucket in table (so that a conflicting name
151        //will be in placed in a bucket after this one).
152        Name first = names.fromString("a");
153        while ((Integer) getIndex.invoke(emptyScope, first) != 0) {
154            if (tries++ > MAX_TRIES) {
155                System.err.println("could not find a name that would be placed in the first bucket");
156                errors++;
157                return ;
158            }
159            first = names.fromString("a" + first.toString());
160        }
161
162        System.out.println("first name: " + first);
163
164        //now, find another name, that will clash with the first one both in the empty and a doubled scope:
165        Scope doubledEmptyScope = WriteableScope.create(symtab.unnamedPackage);
166        dble.invoke(doubledEmptyScope);
167        Integer firstNameTestScopeIndex = (Integer) getIndex.invoke(emptyScope, first);
168        Integer firstNameDoubleScopeIndex = (Integer) getIndex.invoke(doubledEmptyScope, first);
169        Name other = names.fromString("b");
170        while (!Objects.equals(firstNameTestScopeIndex, getIndex.invoke(emptyScope, other)) ||
171               !Objects.equals(firstNameDoubleScopeIndex, getIndex.invoke(doubledEmptyScope, other))) {
172            if (tries++ > MAX_TRIES) {
173                System.err.println("could not find a name that would properly clash with the first chosen name");
174                errors++;
175                return ;
176            }
177            other = names.fromString("b" + other);
178        }
179
180        System.out.println("other name: " + other);
181
182        Symbol firstSymbol = new VarSymbol(0, first, Type.noType, null);
183        Symbol otherSymbol = new VarSymbol(0, other, Type.noType, null);
184
185        //test the situation described above:
186        WriteableScope testScope1 = WriteableScope.create(symtab.unnamedPackage);
187        testScope1.enter(firstSymbol);
188
189        WriteableScope dupped1 = testScope1.dup();
190
191        dupped1.enter(otherSymbol);
192        dble.invoke(dupped1);
193
194        if (testScope1.dupUnshared().findFirst(first) != firstSymbol) {
195            System.err.println("cannot find the Symbol in the dupUnshared scope (1)");
196            errors++;
197        }
198
199        //also check a situation where the clashing Symbol is removed from the dupped scope:
200        WriteableScope testScope2 = WriteableScope.create(symtab.unnamedPackage);
201        testScope2.enter(firstSymbol);
202
203        WriteableScope dupped2 = testScope2.dup();
204
205        dupped2.enter(otherSymbol);
206        dble.invoke(dupped2);
207        dupped2.remove(otherSymbol);
208
209        if (testScope2.dupUnshared().findFirst(first) != firstSymbol) {
210            System.err.println("cannot find the Symbol in the dupUnshared scope (2)");
211            errors++;
212        }
213    }
214
215    int MAX_TRIES = 100; // max tries to find a hash clash before giving up.
216
217}
218