1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ahat;
18 
19 import com.android.ahat.dominators.Dominators;
20 import com.android.ahat.dominators.DominatorsComputation;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import org.junit.Test;
28 import static org.junit.Assert.assertEquals;
29 
30 public class DominatorsTest {
31 
32   private static class Graph implements Dominators.Graph<String> {
33     private Map<String, Object> states = new HashMap<>();
34     private Map<String, Collection<String>> depends = new HashMap<>();
35     private Map<String, String> dominators = new HashMap<>();
36 
37     @Override
setDominatorsComputationState(String node, Object state)38     public void setDominatorsComputationState(String node, Object state) {
39       states.put(node, state);
40     }
41 
getDominatorsComputationState(String node)42     @Override public Object getDominatorsComputationState(String node) {
43       return states.get(node);
44     }
45 
46     @Override
getReferencesForDominators(String node)47     public Collection<String> getReferencesForDominators(String node) {
48       return depends.get(node);
49     }
50 
51     @Override
setDominator(String node, String dominator)52     public void setDominator(String node, String dominator) {
53       dominators.put(node, dominator);
54     }
55 
56     /**
57      * Define a node in the graph, including all its outgoing edges.
58      */
node(String src, String... dsts)59     public void node(String src, String... dsts) {
60       depends.put(src, Arrays.asList(dsts));
61     }
62 
63     /**
64      * Get the computed dominator for a given node.
65      */
dom(String node)66     public String dom(String node) {
67       return dominators.get(node);
68     }
69   }
70 
71   @Test
singleNode()72   public void singleNode() {
73     // --> n
74     // Trivial case.
75     Graph graph = new Graph();
76     graph.node("n");
77     new Dominators(graph).computeDominators("n");
78   }
79 
80   @Test
parentWithChild()81   public void parentWithChild() {
82     // --> parent --> child
83     // The child node is dominated by the parent.
84     Graph graph = new Graph();
85     graph.node("parent", "child");
86     graph.node("child");
87     new Dominators(graph).computeDominators("parent");
88 
89     assertEquals("parent", graph.dom("child"));
90   }
91 
92   @Test
reachableTwoWays()93   public void reachableTwoWays() {
94     //            /-> right -->\
95     // --> parent               child
96     //            \-> left --->/
97     // The child node can be reached either by right or by left.
98     Graph graph = new Graph();
99     graph.node("parent", "left", "right");
100     graph.node("right", "child");
101     graph.node("left", "child");
102     graph.node("child");
103     new Dominators(graph).computeDominators("parent");
104 
105     assertEquals("parent", graph.dom("left"));
106     assertEquals("parent", graph.dom("right"));
107     assertEquals("parent", graph.dom("child"));
108   }
109 
110   @Test
reachableDirectAndIndirect()111   public void reachableDirectAndIndirect() {
112     //            /-> right -->\
113     // --> parent  -----------> child
114     // The child node can be reached either by right or parent.
115     Graph graph = new Graph();
116     graph.node("parent", "right", "child");
117     graph.node("right", "child");
118     graph.node("child");
119     new Dominators(graph).computeDominators("parent");
120 
121     assertEquals("parent", graph.dom("child"));
122     assertEquals("parent", graph.dom("right"));
123   }
124 
125   @Test
subDominator()126   public void subDominator() {
127     // --> parent --> middle --> child
128     // The child is dominated by an internal node.
129     Graph graph = new Graph();
130     graph.node("parent", "middle");
131     graph.node("middle", "child");
132     graph.node("child");
133     new Dominators(graph).computeDominators("parent");
134 
135     assertEquals("parent", graph.dom("middle"));
136     assertEquals("middle", graph.dom("child"));
137   }
138 
139   @Test
childSelfLoop()140   public void childSelfLoop() {
141     // --> parent --> child -\
142     //                  \<---/
143     // The child points back to itself.
144     Graph graph = new Graph();
145     graph.node("parent", "child");
146     graph.node("child", "child");
147     new Dominators(graph).computeDominators("parent");
148 
149     assertEquals("parent", graph.dom("child"));
150   }
151 
152   @Test
singleEntryLoop()153   public void singleEntryLoop() {
154     // --> parent --> a --> b --> c -\
155     //                 \<------------/
156     // There is a loop in the graph, with only one way into the loop.
157     Graph graph = new Graph();
158     graph.node("parent", "a");
159     graph.node("a", "b");
160     graph.node("b", "c");
161     graph.node("c", "a");
162     new Dominators(graph).computeDominators("parent");
163 
164     assertEquals("parent", graph.dom("a"));
165     assertEquals("a", graph.dom("b"));
166     assertEquals("b", graph.dom("c"));
167   }
168 
169   @Test
multiEntryLoop()170   public void multiEntryLoop() {
171     // --> parent --> right --> a --> b ----\
172     //        \                  \<-- c <---/
173     //         \--> left --->--------/
174     // There is a loop in the graph, with two different ways to enter the
175     // loop.
176     Graph graph = new Graph();
177     graph.node("parent", "left", "right");
178     graph.node("left", "c");
179     graph.node("right", "a");
180     graph.node("a", "b");
181     graph.node("b", "c");
182     graph.node("c", "a");
183     new Dominators(graph).computeDominators("parent");
184 
185     assertEquals("parent", graph.dom("right"));
186     assertEquals("parent", graph.dom("left"));
187     assertEquals("parent", graph.dom("a"));
188     assertEquals("parent", graph.dom("c"));
189     assertEquals("a", graph.dom("b"));
190   }
191 
192   @Test
dominatorOverwrite()193   public void dominatorOverwrite() {
194     //            /---------> right <--\
195     // --> parent  --> child <--/      /
196     //            \---> left ---------/
197     // Test a strange case where we have had trouble in the past with a
198     // dominator getting improperly overwritten. The relevant features of this
199     // case are: 'child' is visited after 'right', 'child' is dominated by
200     // 'parent', and 'parent' revisits 'right' after visiting 'child'.
201     Graph graph = new Graph();
202     graph.node("parent", "left", "child", "right");
203     graph.node("right", "child");
204     graph.node("left", "right");
205     graph.node("child");
206     new Dominators(graph).computeDominators("parent");
207 
208     assertEquals("parent", graph.dom("left"));
209     assertEquals("parent", graph.dom("child"));
210     assertEquals("parent", graph.dom("right"));
211   }
212 
213   @Test
stackOverflow()214   public void stackOverflow() {
215     // --> a --> b --> ... --> N
216     // Verify we don't smash the stack for deep chains.
217     Graph graph = new Graph();
218     String root = "end";
219     graph.node(root);
220 
221     for (int i = 0; i < 10000; ++i) {
222       String child = root;
223       root = "n" + i;
224       graph.node(root, child);
225     }
226 
227     new Dominators(graph).computeDominators(root);
228   }
229 
230   @Test
hiddenRevisit()231   public void hiddenRevisit() {
232     //           /-> left ---->---------\
233     // --> parent      \---> a --> b --> c
234     //           \-> right -/
235     // Test a case we have had trouble in the past.
236     // When a's dominator is updated from left to parent, that should trigger
237     // all reachable children's dominators to be updated too. In particular,
238     // c's dominator should be updated, even though b's dominator is
239     // unchanged.
240     Graph graph = new Graph();
241     graph.node("parent", "right", "left");
242     graph.node("right", "a");
243     graph.node("left", "a", "c");
244     graph.node("a", "b");
245     graph.node("b", "c");
246     graph.node("c");
247     new Dominators(graph).computeDominators("parent");
248 
249     assertEquals("parent", graph.dom("left"));
250     assertEquals("parent", graph.dom("right"));
251     assertEquals("parent", graph.dom("a"));
252     assertEquals("parent", graph.dom("c"));
253     assertEquals("a", graph.dom("b"));
254   }
255 
256   @Test
preUndominatedUpdate()257   public void preUndominatedUpdate() {
258     //       /--------->--------\
259     //      /          /---->----\
260     // --> p -> a --> b --> c --> d --> e
261     //           \---------->----------/
262     // Test a case we have had trouble in the past.
263     // The candidate dominator for e is revised from d to a, then d is shown
264     // to be reachable from p. Make sure that causes e's dominator to be
265     // refined again from a to p. The extra nodes are there to ensure the
266     // necessary scheduling to expose the bug we had.
267     Graph graph = new Graph();
268     graph.node("p", "d", "a");
269     graph.node("a", "e", "b");
270     graph.node("b", "d", "c");
271     graph.node("c", "d");
272     graph.node("d", "e");
273     graph.node("e");
274     new Dominators(graph).computeDominators("p");
275 
276     assertEquals("p", graph.dom("a"));
277     assertEquals("a", graph.dom("b"));
278     assertEquals("b", graph.dom("c"));
279     assertEquals("p", graph.dom("d"));
280     assertEquals("p", graph.dom("e"));
281   }
282 
283   @Test
twiceRevisit()284   public void twiceRevisit() {
285     //       /---->---\
286     //      /     /--> f -->-\
287     // --> a --> b -->--x---> c --> d
288     //            \----------->----/
289     // A regression test for a bug where we failed to ever revisit a node more
290     // than once. The node c is revisited a first time to bring its dominator
291     // up to b. c needs to be revisited again after the dominator for f is
292     // pulled up to a, and that revisit of c is necessary to ensure the
293     // dominator for d is pulled up to a.
294     Graph graph = new Graph();
295     graph.node("a", "f", "b");
296     graph.node("b", "f", "d", "x");
297     graph.node("x", "c");
298     graph.node("c", "d");
299     graph.node("d");
300     graph.node("f", "c");
301     new Dominators(graph).computeDominators("a");
302 
303     assertEquals("a", graph.dom("b"));
304     assertEquals("b", graph.dom("x"));
305     assertEquals("a", graph.dom("c"));
306     assertEquals("a", graph.dom("d"));
307     assertEquals("a", graph.dom("f"));
308   }
309 
310   // Test the old dominators API.
311   private static class Node implements DominatorsComputation.Node {
312     public String name;
313     public List<Node> depends = new ArrayList<Node>();
314     public Node dominator;
315     private Object dominatorsComputationState;
316 
Node(String name)317     public Node(String name) {
318       this.name = name;
319     }
320 
computeDominators()321     public void computeDominators() {
322       DominatorsComputation.computeDominators(this);
323     }
324 
toString()325     public String toString() {
326       return name;
327     }
328 
329     @Override
setDominatorsComputationState(Object state)330     public void setDominatorsComputationState(Object state) {
331       dominatorsComputationState = state;
332     }
333 
334     @Override
getDominatorsComputationState()335     public Object getDominatorsComputationState() {
336       return dominatorsComputationState;
337     }
338 
339     @Override
getReferencesForDominators()340     public Collection<Node> getReferencesForDominators() {
341       return depends;
342     }
343 
344     @Override
setDominator(DominatorsComputation.Node dominator)345     public void setDominator(DominatorsComputation.Node dominator) {
346       this.dominator = (Node)dominator;
347     }
348   }
349 
350   @Test
twiceRevisitOldApi()351   public void twiceRevisitOldApi() {
352     //       /---->---\
353     //      /     /--> f -->-\
354     // --> a --> b -->--x---> c --> d
355     //            \----------->----/
356     // Run the twiceRevisit test using the user api version of computing
357     // dominators.
358     Node a = new Node("a");
359     Node b = new Node("b");
360     Node x = new Node("x");
361     Node c = new Node("c");
362     Node d = new Node("d");
363     Node f = new Node("f");
364     a.depends = Arrays.asList(f, b);
365     b.depends = Arrays.asList(f, d, x);
366     x.depends = Arrays.asList(c);
367     c.depends = Arrays.asList(d);
368     f.depends = Arrays.asList(c);
369 
370     a.computeDominators();
371     assertEquals(a, b.dominator);
372     assertEquals(b, x.dominator);
373     assertEquals(a, c.dominator);
374     assertEquals(a, d.dominator);
375     assertEquals(a, f.dominator);
376   }
377 }
378