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