1 /* 2 * Copyright (C) 2018 The Dagger Authors. 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 dagger.internal.codegen.validation; 18 19 import static androidx.room.compiler.processing.XElementKt.isTypeElement; 20 import static com.google.common.base.Preconditions.checkState; 21 import static com.google.common.base.Predicates.equalTo; 22 import static com.google.common.base.Verify.verify; 23 import static com.google.common.collect.Iterables.filter; 24 import static com.google.common.collect.Iterables.getLast; 25 import static com.google.common.collect.Iterables.indexOf; 26 import static com.google.common.collect.Iterables.transform; 27 import static dagger.internal.codegen.base.ElementFormatter.elementToString; 28 import static dagger.internal.codegen.extension.DaggerGraphs.shortestPath; 29 import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; 30 import static dagger.internal.codegen.extension.DaggerStreams.presentValues; 31 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 32 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 33 import static dagger.internal.codegen.xprocessing.XElements.asExecutable; 34 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; 35 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; 36 import static dagger.internal.codegen.xprocessing.XElements.isExecutable; 37 import static java.util.Collections.min; 38 import static java.util.Comparator.comparing; 39 import static java.util.Comparator.comparingInt; 40 41 import androidx.room.compiler.processing.XElement; 42 import androidx.room.compiler.processing.XType; 43 import androidx.room.compiler.processing.XTypeElement; 44 import com.google.common.cache.CacheBuilder; 45 import com.google.common.cache.CacheLoader; 46 import com.google.common.cache.LoadingCache; 47 import com.google.common.collect.HashBasedTable; 48 import com.google.common.collect.ImmutableList; 49 import com.google.common.collect.ImmutableSet; 50 import com.google.common.collect.Iterables; 51 import com.google.common.collect.Table; 52 import dagger.internal.codegen.base.ElementFormatter; 53 import dagger.internal.codegen.base.Formatter; 54 import dagger.internal.codegen.binding.DependencyRequestFormatter; 55 import dagger.internal.codegen.model.Binding; 56 import dagger.internal.codegen.model.BindingGraph; 57 import dagger.internal.codegen.model.BindingGraph.DependencyEdge; 58 import dagger.internal.codegen.model.BindingGraph.Edge; 59 import dagger.internal.codegen.model.BindingGraph.MaybeBinding; 60 import dagger.internal.codegen.model.BindingGraph.Node; 61 import dagger.internal.codegen.model.ComponentPath; 62 import dagger.internal.codegen.model.DaggerElement; 63 import java.util.Comparator; 64 import java.util.List; 65 import java.util.Set; 66 import java.util.function.Function; 67 import javax.inject.Inject; 68 69 /** Helper class for generating diagnostic messages. */ 70 public final class DiagnosticMessageGenerator { 71 72 /** Injectable factory for {@code DiagnosticMessageGenerator}. */ 73 public static final class Factory { 74 private final DependencyRequestFormatter dependencyRequestFormatter; 75 private final ElementFormatter elementFormatter; 76 77 @Inject Factory( DependencyRequestFormatter dependencyRequestFormatter, ElementFormatter elementFormatter)78 Factory( 79 DependencyRequestFormatter dependencyRequestFormatter, 80 ElementFormatter elementFormatter) { 81 this.dependencyRequestFormatter = dependencyRequestFormatter; 82 this.elementFormatter = elementFormatter; 83 } 84 85 /** Creates a {@code DiagnosticMessageGenerator} for the given binding graph. */ create(BindingGraph graph)86 public DiagnosticMessageGenerator create(BindingGraph graph) { 87 return new DiagnosticMessageGenerator(graph, dependencyRequestFormatter, elementFormatter); 88 } 89 } 90 91 private final BindingGraph graph; 92 private final DependencyRequestFormatter dependencyRequestFormatter; 93 private final ElementFormatter elementFormatter; 94 95 /** A cached function from type to all of its supertypes in breadth-first order. */ 96 private final Function<XTypeElement, Iterable<XTypeElement>> supertypes; 97 98 /** The shortest path (value) from an entry point (column) to a binding (row). */ 99 private final Table<MaybeBinding, DependencyEdge, ImmutableList<Node>> shortestPaths = 100 HashBasedTable.create(); 101 memoize(Function<K, V> uncached)102 private static <K, V> Function<K, V> memoize(Function<K, V> uncached) { 103 // If Android Guava is on the processor path, then c.g.c.b.Function (which LoadingCache 104 // implements) does not extend j.u.f.Function. 105 // TODO(erichang): Fix current breakages and try to remove this to enforce not having this on 106 // processor path. 107 108 // First, explicitly convert uncached to c.g.c.b.Function because CacheLoader.from() expects 109 // one. 110 com.google.common.base.Function<K, V> uncachedAsBaseFunction = uncached::apply; 111 112 LoadingCache<K, V> cache = 113 CacheBuilder.newBuilder().build(CacheLoader.from(uncachedAsBaseFunction)); 114 115 // Second, explicitly convert LoadingCache to j.u.f.Function. 116 @SuppressWarnings("deprecation") // uncachedAsBaseFunction throws only unchecked exceptions 117 Function<K, V> memoized = cache::apply; 118 119 return memoized; 120 } 121 DiagnosticMessageGenerator( BindingGraph graph, DependencyRequestFormatter dependencyRequestFormatter, ElementFormatter elementFormatter)122 private DiagnosticMessageGenerator( 123 BindingGraph graph, 124 DependencyRequestFormatter dependencyRequestFormatter, 125 ElementFormatter elementFormatter) { 126 this.graph = graph; 127 this.dependencyRequestFormatter = dependencyRequestFormatter; 128 this.elementFormatter = elementFormatter; 129 supertypes = 130 memoize(component -> transform(component.getType().getSuperTypes(), XType::getTypeElement)); 131 } 132 getMessage(MaybeBinding binding)133 public String getMessage(MaybeBinding binding) { 134 ImmutableSet<DependencyEdge> entryPoints = graph.entryPointEdgesDependingOnBinding(binding); 135 ImmutableSet<DependencyEdge> requests = requests(binding); 136 ImmutableList<DependencyEdge> dependencyTrace = dependencyTrace(binding, entryPoints); 137 138 return getMessageInternal(dependencyTrace, requests, entryPoints); 139 } 140 getMessage(DependencyEdge dependencyEdge)141 public String getMessage(DependencyEdge dependencyEdge) { 142 ImmutableSet<DependencyEdge> requests = ImmutableSet.of(dependencyEdge); 143 144 ImmutableSet<DependencyEdge> entryPoints; 145 ImmutableList<DependencyEdge> dependencyTrace; 146 if (dependencyEdge.isEntryPoint()) { 147 entryPoints = ImmutableSet.of(dependencyEdge); 148 dependencyTrace = ImmutableList.of(dependencyEdge); 149 } else { 150 // It's not an entry point, so it's part of a binding 151 Binding binding = (Binding) source(dependencyEdge); 152 entryPoints = graph.entryPointEdgesDependingOnBinding(binding); 153 dependencyTrace = 154 ImmutableList.<DependencyEdge>builder() 155 .add(dependencyEdge) 156 .addAll(dependencyTrace(binding, entryPoints)) 157 .build(); 158 } 159 160 return getMessageInternal(dependencyTrace, requests, entryPoints); 161 } 162 getMessageInternal( ImmutableList<DependencyEdge> dependencyTrace, ImmutableSet<DependencyEdge> requests, ImmutableSet<DependencyEdge> entryPoints)163 private String getMessageInternal( 164 ImmutableList<DependencyEdge> dependencyTrace, 165 ImmutableSet<DependencyEdge> requests, 166 ImmutableSet<DependencyEdge> entryPoints) { 167 StringBuilder message = new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */); 168 dependencyTrace.forEach( 169 edge -> dependencyRequestFormatter.appendFormatLine(message, edge.dependencyRequest())); 170 if (!dependencyTrace.isEmpty()) { 171 appendComponentPathUnlessAtRoot(message, source(getLast(dependencyTrace))); 172 } 173 message.append(getRequestsNotInTrace(dependencyTrace, requests, entryPoints)); 174 return message.toString(); 175 } 176 getRequestsNotInTrace( ImmutableList<DependencyEdge> dependencyTrace, ImmutableSet<DependencyEdge> requests, ImmutableSet<DependencyEdge> entryPoints)177 public String getRequestsNotInTrace( 178 ImmutableList<DependencyEdge> dependencyTrace, 179 ImmutableSet<DependencyEdge> requests, 180 ImmutableSet<DependencyEdge> entryPoints) { 181 StringBuilder message = new StringBuilder(); 182 // Print any dependency requests that aren't shown as part of the dependency trace. 183 ImmutableSet<XElement> requestsToPrint = 184 requests.stream() 185 // if printing entry points, skip entry points and the traced request 186 .filter(request -> !request.isEntryPoint()) 187 .filter(request -> !isTracedRequest(dependencyTrace, request)) 188 .map(request -> request.dependencyRequest().requestElement()) 189 .flatMap(presentValues()) 190 .map(DaggerElement::xprocessing) 191 .collect(toImmutableSet()); 192 if (!requestsToPrint.isEmpty()) { 193 message.append("\nIt is also requested at:"); 194 elementFormatter.formatIndentedList(message, requestsToPrint, 1); 195 } 196 197 // Print the remaining entry points, showing which component they're in 198 if (entryPoints.size() > 1) { 199 message.append("\nThe following other entry points also depend on it:"); 200 entryPointFormatter.formatIndentedList( 201 message, 202 entryPoints.stream() 203 .filter(entryPoint -> !entryPoint.equals(getLast(dependencyTrace))) 204 .sorted( 205 // 1. List entry points in components closest to the root first. 206 // 2. List entry points declared in a component before those in a supertype. 207 // 3. List entry points in declaration order in their declaring type. 208 rootComponentFirst() 209 .thenComparing(nearestComponentSupertypeFirst()) 210 .thenComparing(requestElementDeclarationOrder())) 211 .collect(toImmutableList()), 212 1); 213 } 214 return message.toString(); 215 } 216 appendComponentPathUnlessAtRoot(StringBuilder message, Node node)217 public void appendComponentPathUnlessAtRoot(StringBuilder message, Node node) { 218 if (!node.componentPath().equals(graph.rootComponentNode().componentPath())) { 219 message.append(String.format(" [%s]", node.componentPath())); 220 } 221 } 222 223 private final Formatter<DependencyEdge> entryPointFormatter = 224 new Formatter<DependencyEdge>() { 225 @Override 226 public String format(DependencyEdge object) { 227 XElement requestElement = object.dependencyRequest().requestElement().get().xprocessing(); 228 StringBuilder builder = new StringBuilder(elementToString(requestElement)); 229 230 // For entry points declared in subcomponents or supertypes of the root component, 231 // append the component path to make clear to the user which component it's in. 232 ComponentPath componentPath = source(object).componentPath(); 233 if (!componentPath.atRoot() 234 || !requestElement 235 .getEnclosingElement() 236 .equals(componentPath.rootComponent().xprocessing())) { 237 builder.append(String.format(" [%s]", componentPath)); 238 } 239 return builder.toString(); 240 } 241 }; 242 isTracedRequest( ImmutableList<DependencyEdge> dependencyTrace, DependencyEdge request)243 private boolean isTracedRequest( 244 ImmutableList<DependencyEdge> dependencyTrace, DependencyEdge request) { 245 return !dependencyTrace.isEmpty() 246 && request.dependencyRequest().equals(dependencyTrace.get(0).dependencyRequest()) 247 // Comparing the dependency request is not enough since the request is just the key. 248 // Instead, we check that the target incident node is the same. 249 && graph.network().incidentNodes(request).target() 250 .equals(graph.network().incidentNodes(dependencyTrace.get(0)).target()); 251 } 252 253 /** 254 * Returns the dependency trace from one of the {@code entryPoints} to {@code binding} to {@code 255 * message} as a list <i>ending with</i> the entry point. 256 */ 257 // TODO(ronshapiro): Adding a DependencyPath type to dagger.internal.codegen.model could be 258 // useful, i.e. 259 // bindingGraph.shortestPathFromEntryPoint(DependencyEdge, MaybeBindingNode) dependencyTrace( MaybeBinding binding, ImmutableSet<DependencyEdge> entryPoints)260 public ImmutableList<DependencyEdge> dependencyTrace( 261 MaybeBinding binding, ImmutableSet<DependencyEdge> entryPoints) { 262 // Module binding graphs may have bindings unreachable from any entry points. If there are 263 // no entry points for this DiagnosticInfo, don't try to print a dependency trace. 264 if (entryPoints.isEmpty()) { 265 return ImmutableList.of(); 266 } 267 // Show the full dependency trace for one entry point. 268 DependencyEdge entryPointForTrace = 269 min( 270 entryPoints, 271 // prefer entry points in components closest to the root 272 rootComponentFirst() 273 // then prefer entry points with a short dependency path to the error 274 .thenComparing(shortestDependencyPathFirst(binding)) 275 // then prefer entry points declared in the component to those declared in a 276 // supertype 277 .thenComparing(nearestComponentSupertypeFirst()) 278 // finally prefer entry points declared first in their enclosing type 279 .thenComparing(requestElementDeclarationOrder())); 280 281 ImmutableList<Node> shortestBindingPath = 282 shortestPathFromEntryPoint(entryPointForTrace, binding); 283 verify( 284 !shortestBindingPath.isEmpty(), 285 "no dependency path from %s to %s in %s", 286 entryPointForTrace, 287 binding, 288 graph); 289 290 ImmutableList.Builder<DependencyEdge> dependencyTrace = ImmutableList.builder(); 291 dependencyTrace.add(entryPointForTrace); 292 for (int i = 0; i < shortestBindingPath.size() - 1; i++) { 293 Set<Edge> dependenciesBetween = 294 graph 295 .network() 296 .edgesConnecting(shortestBindingPath.get(i), shortestBindingPath.get(i + 1)); 297 // If a binding requests a key more than once, any of them should be fine to get to the 298 // shortest path 299 dependencyTrace.add((DependencyEdge) Iterables.get(dependenciesBetween, 0)); 300 } 301 return dependencyTrace.build().reverse(); 302 } 303 304 /** Returns all the nonsynthetic dependency requests for a binding. */ requests(MaybeBinding binding)305 public ImmutableSet<DependencyEdge> requests(MaybeBinding binding) { 306 return graph.network().inEdges(binding).stream() 307 .flatMap(instancesOf(DependencyEdge.class)) 308 .filter(edge -> edge.dependencyRequest().requestElement().isPresent()) 309 .sorted(requestEnclosingTypeName().thenComparing(requestElementDeclarationOrder())) 310 .collect(toImmutableSet()); 311 } 312 313 /** 314 * Returns a comparator that sorts entry points in components whose paths from the root are 315 * shorter first. 316 */ rootComponentFirst()317 private Comparator<DependencyEdge> rootComponentFirst() { 318 return comparingInt(entryPoint -> source(entryPoint).componentPath().components().size()); 319 } 320 321 /** 322 * Returns a comparator that puts entry points whose shortest dependency path to {@code binding} 323 * is shortest first. 324 */ shortestDependencyPathFirst(MaybeBinding binding)325 private Comparator<DependencyEdge> shortestDependencyPathFirst(MaybeBinding binding) { 326 return comparing(entryPoint -> shortestPathFromEntryPoint(entryPoint, binding).size()); 327 } 328 shortestPathFromEntryPoint( DependencyEdge entryPoint, MaybeBinding binding)329 private ImmutableList<Node> shortestPathFromEntryPoint( 330 DependencyEdge entryPoint, MaybeBinding binding) { 331 return shortestPaths 332 .row(binding) 333 .computeIfAbsent( 334 entryPoint, 335 ep -> 336 shortestPath( 337 node -> 338 filter(graph.network().successors(node), MaybeBinding.class::isInstance), 339 graph.network().incidentNodes(ep).target(), 340 binding)); 341 } 342 343 /** 344 * Returns a comparator that sorts entry points in by the distance of the type that declares them 345 * from the type of the component that contains them. 346 * 347 * <p>For instance, an entry point declared directly in the component type would sort before one 348 * declared in a direct supertype, which would sort before one declared in a supertype of a 349 * supertype. 350 */ nearestComponentSupertypeFirst()351 private Comparator<DependencyEdge> nearestComponentSupertypeFirst() { 352 return comparingInt( 353 entryPoint -> 354 indexOf( 355 supertypes.apply(componentContainingEntryPoint(entryPoint)), 356 equalTo(typeDeclaringEntryPoint(entryPoint)))); 357 } 358 componentContainingEntryPoint(DependencyEdge entryPoint)359 private XTypeElement componentContainingEntryPoint(DependencyEdge entryPoint) { 360 return source(entryPoint).componentPath().currentComponent().xprocessing(); 361 } 362 typeDeclaringEntryPoint(DependencyEdge entryPoint)363 private XTypeElement typeDeclaringEntryPoint(DependencyEdge entryPoint) { 364 return asTypeElement( 365 entryPoint.dependencyRequest().requestElement().get().xprocessing().getEnclosingElement()); 366 } 367 368 /** 369 * Returns a comparator that sorts dependency edges lexicographically by the qualified name of the 370 * type that contains them. Only appropriate for edges with request elements. 371 */ requestEnclosingTypeName()372 private Comparator<DependencyEdge> requestEnclosingTypeName() { 373 return comparing( 374 edge -> 375 closestEnclosingTypeElement( 376 edge.dependencyRequest().requestElement().get().xprocessing()) 377 .getQualifiedName()); 378 } 379 380 /** 381 * Returns a comparator that sorts edges in the order in which their request elements were 382 * declared in their declaring type. 383 * 384 * <p>Only useful to compare edges whose request elements were declared in the same type. 385 */ requestElementDeclarationOrder()386 private Comparator<DependencyEdge> requestElementDeclarationOrder() { 387 return comparing( 388 edge -> edge.dependencyRequest().requestElement().get().xprocessing(), 389 // TODO(bcorso): This is inefficient as it requires each element to iterate through all of 390 // its siblings to find its order. Ideally, the order of all elements would be calculated in 391 // a single pass and cached, but the organization of the current code makes that a bit 392 // difficult. I'm leaving this for now since this is only called on failures. 393 comparing( 394 element -> { 395 XElement enclosingElement = element.getEnclosingElement(); 396 checkState(isTypeElement(enclosingElement) || isExecutable(enclosingElement)); 397 List<? extends XElement> siblings = 398 isTypeElement(enclosingElement) 399 ? asTypeElement(enclosingElement).getEnclosedElements() 400 // For parameter elements, element.getEnclosingElement().getEnclosedElements() 401 // is empty, so instead look at the parameter list of the enclosing executable 402 : asExecutable(enclosingElement).getParameters(); 403 return siblings.indexOf(element); 404 })); 405 } 406 source(Edge edge)407 private Node source(Edge edge) { 408 return graph.network().incidentNodes(edge).source(); 409 } 410 } 411