blob: a0d06aeb391c58cc08b946f6beda117b7a39e089 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.android.jack.analysis.tracer;
import com.android.jack.Jack;
import com.android.jack.debug.DebugVariableInfoMarker;
import com.android.jack.frontend.MethodIdDuplicateRemover.UniqMethodIds;
import com.android.jack.ir.ast.Annotable;
import com.android.jack.ir.ast.JAbstractMethodBody;
import com.android.jack.ir.ast.JAbstractStringLiteral;
import com.android.jack.ir.ast.JAlloc;
import com.android.jack.ir.ast.JAnnotation;
import com.android.jack.ir.ast.JAnnotationMethod;
import com.android.jack.ir.ast.JAnnotationType;
import com.android.jack.ir.ast.JArrayType;
import com.android.jack.ir.ast.JBinaryOperation;
import com.android.jack.ir.ast.JClass;
import com.android.jack.ir.ast.JClassLiteral;
import com.android.jack.ir.ast.JClassOrInterface;
import com.android.jack.ir.ast.JConstructor;
import com.android.jack.ir.ast.JDefinedClass;
import com.android.jack.ir.ast.JDefinedClassOrInterface;
import com.android.jack.ir.ast.JDefinedEnum;
import com.android.jack.ir.ast.JDefinedInterface;
import com.android.jack.ir.ast.JDynamicCastOperation;
import com.android.jack.ir.ast.JEnumLiteral;
import com.android.jack.ir.ast.JExpression;
import com.android.jack.ir.ast.JField;
import com.android.jack.ir.ast.JFieldId;
import com.android.jack.ir.ast.JFieldNameLiteral;
import com.android.jack.ir.ast.JFieldRef;
import com.android.jack.ir.ast.JInstanceOf;
import com.android.jack.ir.ast.JInterface;
import com.android.jack.ir.ast.JLambda;
import com.android.jack.ir.ast.JLiteral;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JMethodBody;
import com.android.jack.ir.ast.JMethodCall;
import com.android.jack.ir.ast.JMethodId;
import com.android.jack.ir.ast.JMethodIdRef;
import com.android.jack.ir.ast.JMethodIdWide;
import com.android.jack.ir.ast.JMethodNameLiteral;
import com.android.jack.ir.ast.JNameValuePair;
import com.android.jack.ir.ast.JNewArray;
import com.android.jack.ir.ast.JNewInstance;
import com.android.jack.ir.ast.JParameter;
import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
import com.android.jack.ir.ast.JReinterpretCastOperation;
import com.android.jack.ir.ast.JSession;
import com.android.jack.ir.ast.JType;
import com.android.jack.ir.ast.JTypeStringLiteral;
import com.android.jack.ir.ast.JVariable;
import com.android.jack.ir.ast.JVariableRef;
import com.android.jack.ir.ast.JVisitor;
import com.android.jack.ir.ast.marker.ThrownExceptionMarker;
import com.android.jack.lookup.JMethodLookupException;
import com.android.jack.reporting.Reporter.Severity;
import com.android.jack.shrob.obfuscation.SubClassOrInterfaceMarker;
import com.android.jack.shrob.shrink.PartialTypeHierarchy;
import com.android.sched.item.Description;
import com.android.sched.marker.LocalMarkerManager;
import com.android.sched.schedulable.Access;
import com.android.sched.schedulable.Constraint;
import com.android.sched.util.log.LoggerFactory;
import com.android.sched.util.log.TracerFactory;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* A visitor that traces dependencies
*/
@Description("traces dependencies")
@Constraint(need = {UniqMethodIds.class, SubClassOrInterfaceMarker.class, JMethodBody.class})
// Visit type hierarchy, access referenced types and depends on isAnonymous
@Access(JSession.class)
public class Tracer extends JVisitor {
@Nonnull
protected final com.android.sched.util.log.Tracer tracer = TracerFactory.getTracer();
@Nonnull
public Logger logger = LoggerFactory.getLogger();
@Nonnull
private final TracerBrush brush;
public Tracer(@Nonnull TracerBrush brush) {
this.brush = brush;
}
public void run(@Nonnull JDefinedClassOrInterface type) {
if (brush.startTraceSeed(type)) {
trace(type);
brush.endTraceSeed(type);
}
}
private void trace(@Nonnull JType t) {
if (t instanceof JDefinedClassOrInterface) {
trace((JDefinedClassOrInterface) t);
} else if (t instanceof JArrayType) {
trace(((JArrayType) t).getLeafType());
}
}
private void traceAnnotations(@Nonnull Annotable annotable) {
for (JAnnotation annotation : annotable.getAnnotations()) {
accept(annotation);
}
}
private boolean isNullaryConstructor(@Nonnull JMethod m) {
return m instanceof JConstructor && m.getParams().isEmpty();
}
private void traceImplementation(@Nonnull JDefinedClassOrInterface extendingOrImplementingClOrI,
@Nonnull JClassOrInterface superClOrI) {
if (superClOrI instanceof JDefinedClassOrInterface) {
JDefinedClassOrInterface definedSuperClOrI = (JDefinedClassOrInterface) superClOrI;
for (JMethod method : definedSuperClOrI.getMethods()) {
if (brush.startTraceOverridingMethod(method)) {
JMethodIdWide methodId = method.getMethodIdWide();
JType returnType = method.getType();
JMethod implementation =
findImplementation(methodId, returnType, extendingOrImplementingClOrI);
// method was already marked, if implementation is the same, no need to re-trace it, and
// no need to mark implementation in subtypes. It was already done when the method
// was marked the first time, and for further subtypes that will be marked later,
// the case will be managed by this method.
// Be careful, findImplementation does not return only implementation but also
// definition, if a found method belongs to an interface, keep only default method.
if (implementation != null && implementation != method
&& (implementation.getEnclosingType() instanceof JDefinedClass
|| isDefaultMethod(implementation))) {
trace(methodId, implementation.getEnclosingType(), returnType,
true /* mustTraceOverridingMethods */);
}
brush.endTraceOverridingMethod(method);
}
}
JClass superClass = definedSuperClOrI.getSuperClass();
if (superClass != null) {
traceImplementation(extendingOrImplementingClOrI, superClass);
}
for (JInterface i : definedSuperClOrI.getImplements()) {
traceImplementation(extendingOrImplementingClOrI, i);
}
}
}
private void trace(@Nonnull JDefinedClassOrInterface t) {
if (brush.startTrace(t)) {
traceAnnotations(t);
for (JInterface i : t.getImplements()) {
traceImplementation(t, i);
}
if (t instanceof JDefinedClass) {
JDefinedClass definedClass = (JDefinedClass) t;
JClass superClass = definedClass.getSuperClass();
if (superClass != null) {
traceImplementation(definedClass, superClass);
}
if (t.isAnonymous()) {
if (brush.startTraceEnclosingMethod()) {
JMethod enclosingMethod = ((JDefinedClass) t).getEnclosingMethod();
if (enclosingMethod != null) {
trace(t.getEnclosingType());
trace(enclosingMethod);
}
brush.endTraceEnclosingMethod();
}
}
if (t instanceof JDefinedEnum) {
// The 'values()' method must be traced if present (for the switches on enum support)
try {
JMethod values = definedClass.getMethod("values", definedClass.getArray());
trace(values);
} catch (JMethodLookupException e) {
// Ignored since we want to support missing 'values()' method.
}
}
}
for (JField field : t.getFields()) {
if (brush.startTraceSeed(field)) {
trace(field);
brush.endTraceSeed(field);
}
}
PartialTypeHierarchy pth = t.getMarker(PartialTypeHierarchy.class);
if (pth != null) {
Jack.getSession().getReporter().report(Severity.NON_FATAL, pth);
}
for (JMethod method : t.getMethods()) {
// Clinit and constructor without parameters must always be trace without taking into
// account seed.
if ((JMethod.isClinit(method) || isNullaryConstructor(method))) {
trace(method);
} else {
// To be safe, Jack is conservative when there is partial type hierarchy.
// It considers all methods of the type as seed.
if (brush.startTraceSeed(method) || pth != null) {
trace(method.getMethodIdWide(), method.getEnclosingType(), method.getType(),
true /* mustTraceOverridingMethods */);
brush.endTraceSeed(method);
}
}
}
brush.endTrace(t);
}
}
private void trace(@Nonnull JField f) {
if (brush.startTrace(f)) {
trace(f.getEnclosingType());
trace(f.getType());
traceAnnotations(f);
brush.endTrace(f);
}
}
private void trace(@Nonnull JFieldId fid, @Nonnull JClassOrInterface receiverType) {
trace(receiverType);
JField field = fid.getField();
if (field != null) {
trace(field);
}
}
@CheckForNull
private JMethod findMethod(@Nonnull JMethodIdWide methodIdWide,
@Nonnull JClassOrInterface enclosingType,
@Nonnull JType returnType) {
JMethodId id = methodIdWide.getMethodId(returnType);
if (id == null) {
return null;
}
for (JMethod m : id.getMethods()) {
if (m.getEnclosingType().isSameType(enclosingType)) {
return m;
}
}
return null;
}
/**
* Traces the methods corresponding to a method id whose enclosing type is a subclass of
* receiverType
* @param mid the methodId of the searched method
* @param receiverType the type with which the methodId was used
* @param returnType the return type of the searched method
* @param mustTraceOverridingMethods indicates if the overriding methods of the traced method
* should be traced as well
*/
private void trace(@Nonnull JMethodIdWide mid, @Nonnull JClassOrInterface receiverType,
@Nonnull JType returnType, boolean mustTraceOverridingMethods) {
JMethod foundMethod = findMethod(mid, receiverType, returnType);
if (foundMethod != null) {
trace(foundMethod);
if (mustTraceOverridingMethods) {
brush.setMustTraceOverridingMethods(foundMethod);
}
}
if (receiverType instanceof JDefinedClassOrInterface && mustTraceOverridingMethods) {
SubClassOrInterfaceMarker marker =
((LocalMarkerManager) receiverType).getMarker(SubClassOrInterfaceMarker.class);
if (marker != null) {
Iterator<JDefinedClassOrInterface> classOrInterfaceIterator = marker.iterator();
while (classOrInterfaceIterator.hasNext()) {
JDefinedClassOrInterface clOrI = classOrInterfaceIterator.next();
if (brush.traceMarked(clOrI)) {
JMethod implementation = findImplementation(mid, returnType, clOrI);
// Be careful, findImplementation does not return only implementation but also
// definition, if a found method belongs to an interface, keep only default method.
if (implementation != null
&& (implementation.getEnclosingType() instanceof JDefinedClass
|| isDefaultMethod(implementation))) {
trace(implementation);
brush.setMustTraceOverridingMethods(implementation);
}
brush.endTraceMarked(clOrI);
}
}
}
}
}
private boolean isDefaultMethod(@Nonnull JMethod jMethod) {
assert jMethod != null;
assert jMethod.getEnclosingType() instanceof JDefinedInterface;
return !jMethod.isAbstract() && !jMethod.isStatic();
}
private void trace(@Nonnull JMethod m) {
if (brush.startTrace(m)) {
trace(m.getEnclosingType());
traceAnnotations(m);
for (JParameter arg : m.getParams()) {
trace(arg.getType());
}
trace(m.getType());
ThrownExceptionMarker marker = m.getMarker(ThrownExceptionMarker.class);
if (marker != null) {
for (JClass throwException : marker.getThrownExceptions()) {
trace(throwException);
}
}
if (m.getEnclosingType().isToEmit()) {
JAbstractMethodBody body = m.getBody();
if (body != null) {
accept(body);
}
}
brush.endTrace(m);
}
}
private void trace(@Nonnull JAnnotation al) {
JAnnotationType type = al.getType();
trace(type);
for (JNameValuePair pair : al.getNameValuePairs()) {
for (JMethod method : pair.getMethodId().getMethods()) {
if (method.getEnclosingType().isSameType(type)) {
trace(method);
JLiteral defaultValue = ((JAnnotationMethod) method).getDefaultValue();
if (defaultValue != null) {
this.accept(defaultValue);
}
}
}
}
}
@Override
public void endVisit(@Nonnull JFieldRef fr) {
trace(fr.getFieldId(), fr.getReceiverType());
}
@Override
public void endVisit(@Nonnull JMethodCall mc) {
JType returnType = mc.getType();
trace(returnType);
JMethodIdWide methodId = mc.getMethodIdWide();
JClassOrInterface receiverType = mc.getReceiverType();
trace(receiverType);
JMethod implementationOrDefinition = null;
if (receiverType instanceof JDefinedClass) {
implementationOrDefinition = findImplementationOrDefinition(
methodId, returnType, (JDefinedClass) receiverType);
if (implementationOrDefinition == null && receiverType.isToEmit()) {
logger.log(Level.WARNING,
"No implementation or definition found for method {0} in {1} or its super types",
new Object[] {Jack.getUserFriendlyFormatter().getName(methodId.getName(),
methodId.getParamTypes(), returnType),
Jack.getUserFriendlyFormatter().getName(receiverType)});
}
} else if (receiverType instanceof JDefinedInterface) {
implementationOrDefinition =
findDefinition(methodId, returnType, (JDefinedClassOrInterface) receiverType);
if (implementationOrDefinition == null && receiverType.isToEmit()) {
logger.log(Level.WARNING,
"No implementation or definition found for method {0} in {1} or its super types",
new Object[] {Jack.getUserFriendlyFormatter().getName(methodId.getName(),
methodId.getParamTypes(), returnType),
Jack.getUserFriendlyFormatter().getName(receiverType)});
}
}
JClassOrInterface tracingStartingPoint = null;
if (implementationOrDefinition != null) {
tracingStartingPoint = implementationOrDefinition.getEnclosingType();
trace(tracingStartingPoint);
} else {
tracingStartingPoint = receiverType;
}
trace(methodId, tracingStartingPoint, returnType, true /* mustTraceOverridingMethods */);
}
@Override
public void endVisit(@Nonnull JNewInstance newInstance) {
JClass returnType = newInstance.getType();
trace(returnType);
JMethodIdWide methodId = newInstance.getMethodIdWide();
trace(methodId, returnType, JPrimitiveTypeEnum.VOID.getType(),
false /* mustTraceOverridingMethods */);
}
/**
* Look up the super interfaces of a type to find a method with a matching methodId and
* return type.
*
* @param methodId
* @param returnType
* @param receiverType
* @return the method was found
*/
@CheckForNull
private JMethod findDefinition(@Nonnull JMethodIdWide methodId,
@Nonnull JType returnType, @Nonnull JDefinedClassOrInterface receiverType) {
JMethod foundMethod = findMethod(methodId, receiverType, returnType);
if (foundMethod != null) {
return foundMethod;
}
for (JInterface i : receiverType.getImplements()) {
if (i instanceof JDefinedInterface) {
JMethod foundDefinition =
findDefinition(methodId, returnType, (JDefinedInterface) i);
if (foundDefinition != null) {
return foundDefinition;
}
}
}
return null;
}
@CheckForNull
private JMethod findImplementation(@Nonnull JMethodIdWide methodId, @Nonnull JType returnType,
@Nonnull JDefinedClassOrInterface receiverType) {
JMethod foundMethod = findMethod(methodId, receiverType, returnType);
if (foundMethod != null) {
return foundMethod;
}
JClass currentType = receiverType.getSuperClass();
while (currentType instanceof JDefinedClass) {
foundMethod = findMethod(methodId, currentType, returnType);
if (foundMethod != null) {
return foundMethod;
}
currentType = ((JDefinedClass) currentType).getSuperClass();
}
foundMethod = findImplementationInInterfaces(methodId, returnType, receiverType);
return foundMethod;
}
@CheckForNull
private JMethod findImplementationInInterfaces(@Nonnull JMethodIdWide methodId,
@Nonnull JType returnType, @Nonnull JDefinedClassOrInterface receiverType) {
for (JInterface interfaze : receiverType.getImplements()) {
JMethod foundMethod = findMethod(methodId, interfaze, returnType);
if (foundMethod != null) {
return foundMethod;
} else if (interfaze instanceof JDefinedClassOrInterface) {
findImplementationInInterfaces(methodId, returnType, (JDefinedClassOrInterface) interfaze);
}
}
return null;
}
/**
* Look up the super classes of a type to find a method with a matching methodId.
*
* @param methodId
* @param receiverType
* @return the type where the method was found
*/
@CheckForNull
private JMethod findImplementationOrDefinition(
@Nonnull JMethodIdWide methodId, @Nonnull JType returnType,
@Nonnull JDefinedClass receiverType) {
JMethod implementation =
findImplementation(methodId, returnType, receiverType);
if (implementation != null) {
return implementation;
}
JClass currentType = receiverType;
while (currentType instanceof JDefinedClass) {
JMethod definition =
findDefinition(methodId, returnType, (JDefinedClassOrInterface) currentType);
if (definition != null) {
return definition;
}
currentType = ((JDefinedClass) currentType).getSuperClass();
}
return null;
}
@Override
public void endVisit(@Nonnull JMethodNameLiteral mnl) {
trace(mnl.getMethod());
}
@Override
public void endVisit(@Nonnull JFieldNameLiteral fnl) {
trace(fnl.getField());
}
@Override
public void endVisit(@Nonnull JTypeStringLiteral tsl) {
trace(tsl.getReferencedType());
}
@Override
public void endVisit(@Nonnull JAlloc alloc) {
trace(alloc.getInstanceType());
}
@Override
public void endVisit(@Nonnull JAnnotation annotation) {
trace(annotation);
}
@Override
public void endVisit(@Nonnull JBinaryOperation x) {
trace(x.getType());
}
@Override
public void endVisit(@Nonnull JReinterpretCastOperation x) {
trace(x.getType());
}
@Override
public void endVisit(@Nonnull JDynamicCastOperation x) {
for (JType type : x.getTypes()) {
trace(type);
}
}
@Override
public void endVisit(@Nonnull JClassLiteral x) {
trace(x.getRefType());
}
@Override
public void endVisit(@Nonnull JEnumLiteral enumLit) {
// No need to trace field since JEnumLiteral will be replace by constant
JField field = enumLit.getFieldId().getField();
if (field != null) {
traceAnnotations(field);
}
}
@Override
public void endVisit(@Nonnull JInstanceOf x) {
trace(x.getTestType());
}
@Override
public void endVisit(@Nonnull JNewArray x) {
trace(x.getArrayType());
}
@Override
public void endVisit(@Nonnull JAbstractStringLiteral x) {
trace(x.getType());
}
@Override
public void endVisit(@Nonnull JVariableRef x) {
DebugVariableInfoMarker debugInfo = x.getMarker(DebugVariableInfoMarker.class);
if (debugInfo != null) {
JType debugInfoType = debugInfo.getType();
if (debugInfoType != null) {
trace(debugInfoType);
}
}
}
@Override
public void endVisit(@Nonnull JVariable x) {
trace(x.getType());
}
@Override
public void endVisit(@Nonnull JLambda lambdaExpr) {
trace(lambdaExpr.getType());
for (JInterface interfaze : lambdaExpr.getInterfaceBounds()) {
trace(interfaze);
}
for (JExpression capturedVar : lambdaExpr.getCapturedVariables()) {
accept(capturedVar);
}
JMethodIdRef methodIdRef = lambdaExpr.getMethodIdRef();
JMethodId methodId = methodIdRef.getMethodId();
JMethodIdWide methodIdWide = methodId.getMethodIdWide();
JClassOrInterface receiverType = methodIdRef.getEnclosingType();
trace(receiverType);
trace(methodIdWide, methodIdRef.getEnclosingType(), methodId.getType(),
/*mustTraceOverridingMethods=*/ false);
}
}