/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devtools.cyclefinder;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.devtools.j2objc.ast.ClassInstanceCreation;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.CreationReference;
import com.google.devtools.j2objc.ast.ExpressionMethodReference;
import com.google.devtools.j2objc.ast.LambdaExpression;
import com.google.devtools.j2objc.ast.MethodInvocation;
import com.google.devtools.j2objc.ast.MethodReference;
import com.google.devtools.j2objc.ast.SuperMethodReference;
import com.google.devtools.j2objc.ast.TreeNode;
import com.google.devtools.j2objc.ast.TypeDeclaration;
import com.google.devtools.j2objc.ast.TypeMethodReference;
import com.google.devtools.j2objc.ast.UnitTreeVisitor;
import com.google.devtools.j2objc.util.CaptureInfo;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.TypeUtil;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.SimpleTypeVisitor8;
/**
* Builds the graph of possible references between types.
*
* @author Keith Stanger
*/
public class GraphBuilder {
private final Map<String, TypeNode> allTypes = new HashMap<>();
private final NameList whitelist;
private final ReferenceGraph graph = new ReferenceGraph();
private final Map<TypeNode, TypeNode> superclasses = new HashMap<>();
private final SetMultimap<TypeNode, TypeNode> subtypes = HashMultimap.create();
private final SetMultimap<TypeNode, Edge> possibleOuterEdges = HashMultimap.create();
private final Set<TypeNode> hasOuterRef = new HashSet<>();
public GraphBuilder(NameList whitelist) {
this.whitelist = whitelist;
}
public GraphBuilder constructGraph() {
addOuterEdges();
addSubtypeEdges();
addSuperclassEdges();
return this;
}
public ReferenceGraph getGraph() {
return graph;
}
private void addEdge(Edge e) {
if (!e.getOrigin().equals(e.getTarget())) {
graph.addEdge(e);
}
}
private static TypeMirror getElementType(TypeMirror t) {
while (TypeUtil.isArray(t)) {
t = ((ArrayType) t).getComponentType();
}
return t;
}
private void addOuterEdges() {
for (TypeNode type : hasOuterRef) {
for (Edge e : possibleOuterEdges.get(type)) {
addEdge(e);
}
}
}
private void addSubtypeEdges() {
for (TypeNode type : allTypes.values()) {
for (Edge e : ImmutableList.copyOf(graph.getEdges(type))) {
Set<TypeNode> targetSubtypes = subtypes.get(e.getTarget());
Set<TypeNode> whitelisted = new HashSet<>();
String fieldName = e.getFieldQualifiedName();
if (fieldName == null) {
continue; // Outer or capture field.
}
for (TypeNode subtype : targetSubtypes) {
if (whitelist.isWhitelistedTypeForField(fieldName, subtype)
|| whitelist.containsType(subtype)) {
whitelisted.add(subtype);
whitelisted.addAll(subtypes.get(subtype));
}
}
for (TypeNode subtype : Sets.difference(targetSubtypes, whitelisted)) {
addEdge(Edge.newSubtypeEdge(e, subtype));
}
}
}
}
private void addSuperclassEdges() {
for (TypeNode type : allTypes.values()) {
TypeNode superclassNode = superclasses.get(type);
while (superclassNode != null) {
for (Edge e : graph.getEdges(superclassNode)) {
addEdge(Edge.newSuperclassEdge(e, type, superclassNode));
}
superclassNode = superclasses.get(superclassNode);
}
}
}
private static final TypeVisitor<Integer, Void> TYPE_DEPTH_COUNTER =
new SimpleTypeVisitor8<Integer, Void>(0) {
private int tryVisit(TypeMirror t) {
return t == null ? 0 : visit(t);
}
private int visitList(List<? extends TypeMirror> types) {
int max = 0;
for (TypeMirror t : types) {
max = Math.max(max, visit(t));
}
return max;
}
@Override
public Integer visitArray(ArrayType t, Void p) {
return visit(t.getComponentType()) + 1;
}
@Override
public Integer visitDeclared(DeclaredType t, Void p) {
// Visit enclosing types but don't penalize them by adding +1.
return Math.max(visit(t.getEnclosingType()), visitList(t.getTypeArguments()) + 1);
}
@Override
public Integer visitTypeVariable(TypeVariable t, Void p) {
return 1;
}
@Override
public Integer visitWildcard(WildcardType t, Void p) {
return Math.max(tryVisit(t.getExtendsBound()), tryVisit(t.getSuperBound())) + 1;
}
};
private static boolean isRawType(TypeMirror type) {
return TypeUtil.isDeclaredType(type)
&& !TypeUtil.asTypeElement(type).getTypeParameters().isEmpty()
&& ((DeclaredType) type).getTypeArguments().isEmpty();
}
public void visitAST(CompilationUnit unit) {
new Visitor(unit).run();
}
private class Visitor extends UnitTreeVisitor {
private final CaptureInfo captureInfo;
private final NameUtil nameUtil;
private Visitor(CompilationUnit unit) {
super(unit);
captureInfo = unit.getEnv().captureInfo();
nameUtil = new NameUtil(typeUtil);
}
private TypeNode createNode(TypeMirror type, String signature, String name) {
TypeNode node = new TypeNode(signature, name, NameUtil.getQualifiedName(type));
allTypes.put(signature, node);
followType(type, node);
return node;
}
private TypeNode getOrCreateNode(TypeMirror type) {
type = getElementType(type);
String signature = nameUtil.getSignature(type);
TypeNode node = allTypes.get(signature);
if (node != null) {
return node;
}
if (!TypeUtil.isReferenceType(type) || isRawType(type)) {
return null;
}
if (TYPE_DEPTH_COUNTER.visit(type) > 5) {
// Avoid infinite recursion caused by type argument cycles.
return null;
}
return createNode(type, signature, NameUtil.getName(type));
}
private void visitType(TypeMirror type) {
if (type == null) {
return;
} else if (TypeUtil.isIntersection(type)) {
for (TypeMirror bound : ((IntersectionType) type).getBounds()) {
getOrCreateNode(bound);
}
} else {
getOrCreateNode(type);
}
}
private void followType(TypeMirror type, TypeNode node) {
List<? extends TypeMirror> supertypes = TypeUtil.isDeclaredType(type)
? typeUtil.directSupertypes(type) : typeUtil.getUpperBounds(type);
for (TypeMirror supertype : supertypes) {
TypeNode supertypeNode = getOrCreateNode(supertype);
if (supertypeNode != null) {
subtypes.put(supertypeNode, node);
if (TypeUtil.isDeclaredType(supertype)
&& TypeUtil.getDeclaredTypeKind(supertype).isClass()) {
superclasses.put(node, supertypeNode);
}
}
}
if (TypeUtil.isDeclaredType(type)) {
followDeclaredType((DeclaredType) type, node);
}
}
private void followDeclaredType(DeclaredType type, TypeNode node) {
followEnclosingType((DeclaredType) type, node);
followFields((DeclaredType) type, node);
for (TypeMirror typeArg : type.getTypeArguments()) {
visitType(typeArg);
}
}
private void followFields(DeclaredType type, TypeNode node) {
TypeElement element = (TypeElement) type.asElement();
for (VariableElement field : ElementUtil.getDeclaredFields(element)) {
TypeMirror fieldType = getElementType(typeUtil.asMemberOf(type, field));
TypeNode target = getOrCreateNode(fieldType);
String fieldName = ElementUtil.getName(field);
if (target != null
&& !whitelist.containsField(node, fieldName)
&& !whitelist.containsType(target)
&& !ElementUtil.isStatic(field)
// Exclude self-referential fields. (likely linked DS or delegate pattern)
&& !typeUtil.isAssignable(type, fieldType)
&& !ElementUtil.isWeakReference(field)
&& !ElementUtil.isRetainedWithField(field)) {
addEdge(Edge.newFieldEdge(node, target, fieldName));
}
}
}
private void followEnclosingType(DeclaredType type, TypeNode typeNode) {
TypeMirror enclosingType = type.getEnclosingType();
if (TypeUtil.isNone(enclosingType)) {
return;
}
TypeNode enclosingTypeNode = getOrCreateNode(enclosingType);
TypeElement element = (TypeElement) type.asElement();
TypeNode declarationType = getOrCreateNode(element.asType());
if (declarationType != null && enclosingTypeNode != null
&& ElementUtil.hasOuterContext(element)
&& !elementUtil.isWeakOuterType(element)
&& !whitelist.containsType(enclosingTypeNode)
&& !whitelist.hasOuterForType(typeNode)) {
possibleOuterEdges.put(
declarationType, Edge.newOuterClassEdge(typeNode, enclosingTypeNode));
}
}
private void followCaptureFields(TypeElement type, TypeNode typeNode) {
assert ElementUtil.isAnonymous(type);
for (VariableElement capturedVarElement : captureInfo.getLocalCaptureFields(type)) {
TypeNode targetNode = getOrCreateNode(capturedVarElement.asType());
if (targetNode != null && !whitelist.containsType(targetNode)
&& !ElementUtil.isWeakReference(capturedVarElement)) {
addEdge(Edge.newCaptureEdge(
typeNode, targetNode, ElementUtil.getName(capturedVarElement)));
}
}
}
private String getTypeDeclarationName(TreeNode node, TypeElement typeElem) {
if (node instanceof MethodReference) {
return "methodref:" + node.getLineNumber();
} else if (ElementUtil.isLambda(typeElem)) {
return "lambda:" + node.getLineNumber();
} else if (ElementUtil.isAnonymous(typeElem)) {
return "anonymous:" + node.getLineNumber();
} else {
return NameUtil.getName(typeElem.asType());
}
}
private void handleTypeDeclaration(TreeNode node, TypeElement typeElem) {
TypeMirror type = typeElem.asType();
TypeNode typeNode = createNode(
type, nameUtil.getSignature(type), getTypeDeclarationName(node, typeElem));
if (captureInfo.needsOuterReference(typeElem)) {
hasOuterRef.add(typeNode);
}
VariableElement receiverField = captureInfo.getReceiverField(typeElem);
if (receiverField != null) {
TypeNode receiverNode = getOrCreateNode(receiverField.asType());
if (receiverNode != null) {
addEdge(Edge.newReceiverClassEdge(typeNode, receiverNode));
}
}
if (ElementUtil.isAnonymous(typeElem)) {
followCaptureFields(typeElem, typeNode);
}
}
@Override
public boolean visit(TypeDeclaration node) {
handleTypeDeclaration(node, node.getTypeElement());
return true;
}
@Override
public void endVisit(LambdaExpression node) {
handleTypeDeclaration(node, node.getTypeElement());
}
@Override
public void endVisit(CreationReference node) {
handleTypeDeclaration(node, node.getTypeElement());
}
@Override
public void endVisit(ExpressionMethodReference node) {
handleTypeDeclaration(node, node.getTypeElement());
}
@Override
public void endVisit(SuperMethodReference node) {
handleTypeDeclaration(node, node.getTypeElement());
}
@Override
public void endVisit(TypeMethodReference node) {
handleTypeDeclaration(node, node.getTypeElement());
}
@Override
public boolean visit(ClassInstanceCreation node) {
visitType(node.getTypeMirror());
return true;
}
@Override
public boolean visit(MethodInvocation node) {
visitType(node.getTypeMirror());
return true;
}
}
}