blob: edb01fd09806dd7f41fae1d185c56b6bcb5c62c7 [file] [log] [blame]
/*
* Copyright (C) 2012 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.backend.dex.annotations;
import com.google.common.collect.Ordering;
import com.android.jack.Jack;
import com.android.jack.backend.dex.DexAnnotations;
import com.android.jack.backend.dex.annotations.tag.ReflectAnnotations;
import com.android.jack.dx.rop.code.AccessFlags;
import com.android.jack.ir.ast.Annotable;
import com.android.jack.ir.ast.JAnnotation;
import com.android.jack.ir.ast.JAnnotationType;
import com.android.jack.ir.ast.JArrayLiteral;
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.JDefinedClass;
import com.android.jack.ir.ast.JDefinedClassOrInterface;
import com.android.jack.ir.ast.JField;
import com.android.jack.ir.ast.JIntLiteral;
import com.android.jack.ir.ast.JLiteral;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JMethodIdWide;
import com.android.jack.ir.ast.JMethodLiteral;
import com.android.jack.ir.ast.JModifier;
import com.android.jack.ir.ast.JNameValuePair;
import com.android.jack.ir.ast.JNullLiteral;
import com.android.jack.ir.ast.JRetentionPolicy;
import com.android.jack.ir.ast.JSession;
import com.android.jack.ir.ast.JStringLiteral;
import com.android.jack.ir.ast.JType;
import com.android.jack.ir.ast.JVisitor;
import com.android.jack.ir.ast.MethodKind;
import com.android.jack.ir.ast.marker.GenericSignature;
import com.android.jack.ir.ast.marker.SimpleName;
import com.android.jack.ir.ast.marker.ThrownExceptionMarker;
import com.android.jack.ir.formatter.InternalFormatter;
import com.android.jack.ir.formatter.TypeFormatter;
import com.android.jack.ir.sourceinfo.SourceInfo;
import com.android.jack.lookup.JPhantomLookup;
import com.android.jack.scheduling.feature.SourceVersion8;
import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter;
import com.android.jack.shrob.obfuscation.FinalNames;
import com.android.jack.transformations.request.AddAnnotation;
import com.android.jack.transformations.request.PutNameValuePair;
import com.android.jack.transformations.request.TransformationRequest;
import com.android.sched.item.Description;
import com.android.sched.item.Synchronized;
import com.android.sched.schedulable.Access;
import com.android.sched.schedulable.Constraint;
import com.android.sched.schedulable.Filter;
import com.android.sched.schedulable.Optional;
import com.android.sched.schedulable.Protect;
import com.android.sched.schedulable.RunnableSchedulable;
import com.android.sched.schedulable.ToSupport;
import com.android.sched.schedulable.Transform;
import com.android.sched.schedulable.With;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.annotation.Nonnull;
/**
* Add annotations used by reflection.
*/
@Description("Add annotations used by reflection")
@Synchronized
@Transform(add = {ReflectAnnotations.class, JAnnotation.class, JNameValuePair.class,
JClassLiteral.class, JStringLiteral.class, JMethodLiteral.class, JArrayLiteral.class,
JNullLiteral.class, JIntLiteral.class})
@Constraint(need = {GenericSignature.class, SimpleName.class, FinalNames.class})
@Protect(add = {GenericSignature.class, SimpleName.class},
unprotect = @With(remove = ReflectAnnotations.class))
@Optional(@ToSupport(feature = SourceVersion8.class,
add = @Constraint(no = JAnnotation.RepeatedAnnotation.class)))
@Filter(TypeWithoutPrebuiltFilter.class)
// Access getSimpleName which may depend on TypeName that is accessing enclosing type name.
@Access(JSession.class)
public class ReflectAnnotationsAdder implements RunnableSchedulable<JDefinedClassOrInterface> {
private static class Visitor extends JVisitor {
@Nonnull
private final TransformationRequest request;
@Nonnull
private static final String ELT_VALUE = "value";
@Nonnull
private static final String ELT_NAME = "name";
@Nonnull
private static final String ELT_ACCESS_FLAGS = "accessFlags";
@Nonnull
private final JAnnotationType defaultAnnotationType;
@Nonnull
private final JAnnotationType signatureAnnotationType;
@Nonnull
private final JAnnotationType enclosingMethodAnnotationType;
@Nonnull
private final JAnnotationType enclosingClassAnnotationType;
@Nonnull
private final JAnnotationType throwsAnnotationType;
@Nonnull
private final JAnnotationType innerAnnotationType;
@Nonnull
private final JAnnotationType memberClassAnnotationType;
@Nonnull
TypeFormatter orderingFormatter = InternalFormatter.getFormatter();
@Nonnull
private final Ordering<JClassOrInterface> typeOrdering =
Ordering.from(new Comparator<JClassOrInterface>() {
@Override
public int compare(@Nonnull JClassOrInterface t1, @Nonnull JClassOrInterface t2) {
return orderingFormatter.getName(t1).compareTo(orderingFormatter.getName(t2));
}
});
public Visitor(@Nonnull TransformationRequest request, @Nonnull JPhantomLookup lookup) {
this.request = request;
defaultAnnotationType =
lookup.getAnnotationType(DexAnnotations.ANNOTATION_ANNOTATION_DEFAULT);
signatureAnnotationType = lookup.getAnnotationType(DexAnnotations.ANNOTATION_SIGNATURE);
enclosingMethodAnnotationType =
lookup.getAnnotationType(DexAnnotations.ANNOTATION_ENCLOSING_METHOD);
enclosingClassAnnotationType =
lookup.getAnnotationType(DexAnnotations.ANNOTATION_ENCLOSING_CLASS);
throwsAnnotationType = lookup.getAnnotationType(DexAnnotations.ANNOTATION_THROWS);
innerAnnotationType = lookup.getAnnotationType(DexAnnotations.ANNOTATION_INNER);
memberClassAnnotationType =
lookup.getAnnotationType(DexAnnotations.ANNOTATION_MEMBER_CLASSES);
}
@Nonnull
private JMethodIdWide getOrCreateMethodId(@Nonnull JAnnotationType type, @Nonnull String name) {
return type.getOrCreateMethodIdWide(
name, Collections.<JType>emptyList(), MethodKind.INSTANCE_VIRTUAL);
}
@Override
public boolean visit(@Nonnull JMethod x) {
return false;
}
@Override
public void endVisit(@Nonnull JDefinedClassOrInterface x) {
addMemberClasses(x);
JClassOrInterface enclosingType = x.getEnclosingType();
if (enclosingType != null) {
addInnerClass(x);
if ((x instanceof JDefinedClass) && ((JDefinedClass) x).getEnclosingMethod() != null) {
addEnclosingMethod(x);
} else {
addEnclosingClass(x);
}
}
GenericSignature marker = x.getMarker(GenericSignature.class);
if (marker != null) {
addSignature(x, marker.getGenericSignature(), x.getSourceInfo());
}
}
@Override
public void endVisit(@Nonnull JField x) {
GenericSignature marker = x.getMarker(GenericSignature.class);
if (marker != null) {
addSignature(x, marker.getGenericSignature(), x.getSourceInfo());
}
}
@Override
public void endVisit(@Nonnull JMethod x) {
addThrows(x);
GenericSignature marker = x.getMarker(GenericSignature.class);
if (marker != null) {
String genericSignature = marker.getGenericSignature();
if (genericSignature != null) {
addSignature(x, genericSignature, x.getSourceInfo());
}
}
}
private void addSignature(@Nonnull Annotable annotable, @Nonnull String signature,
@Nonnull SourceInfo info) {
JAnnotation annotation =
createAnnotation(annotable, signatureAnnotationType, info);
JArrayLiteral literal = buildSignatureAnnotationValue(signature, info);
JMethodIdWide methodId = getOrCreateMethodId(signatureAnnotationType, ELT_VALUE);
JNameValuePair valuePair = new JNameValuePair(info, methodId, literal);
assert annotation.getNameValuePair(methodId) == null
: "Type can not have more than one generic signature";
request.append(new PutNameValuePair(annotation, valuePair));
}
private void addEnclosingMethod(@Nonnull JDefinedClassOrInterface type) {
if (type instanceof JDefinedClass) {
JDefinedClass classType = (JDefinedClass) type;
JMethod method = classType.getEnclosingMethod();
if (method != null) {
SourceInfo info = type.getSourceInfo();
JAnnotation annotation =
createAnnotation(type, enclosingMethodAnnotationType, info);
JMethodLiteral newLiteral = new JMethodLiteral(method, info);
JMethodIdWide methodId = getOrCreateMethodId(enclosingMethodAnnotationType, ELT_VALUE);
JNameValuePair valuePair = new JNameValuePair(info, methodId, newLiteral);
assert annotation.getNameValuePair(methodId) == null
: "Type can not have more than one enclosing method";
request.append(new PutNameValuePair(annotation, valuePair));
}
}
}
private void addThrows(@Nonnull JMethod method) {
ThrownExceptionMarker marker = method.getMarker(ThrownExceptionMarker.class);
if (marker != null) {
List<JClass> throwns = marker.getThrownExceptions();
SourceInfo info = method.getSourceInfo();
JAnnotation annotation = createAnnotation(method, throwsAnnotationType, info);
List<JLiteral> literals = new ArrayList<JLiteral>();
for (JClass thrown : throwns) {
literals.add(new JClassLiteral(info, thrown));
}
JMethodIdWide methodId = getOrCreateMethodId(throwsAnnotationType, ELT_VALUE);
JArrayLiteral array = new JArrayLiteral(info, literals);
JNameValuePair valuePair = new JNameValuePair(info, methodId, array);
request.append(new PutNameValuePair(annotation, valuePair));
}
}
private void addMemberClasses(@Nonnull JDefinedClassOrInterface type) {
List<JLiteral> literals = new ArrayList<JLiteral>();
SourceInfo info = type.getSourceInfo();
// sort member classes to be deterministic even with synthetic member classes
List<JClassOrInterface> sortedMemberTypes =
typeOrdering.immutableSortedCopy(type.getMemberTypes());
for (JClassOrInterface member : sortedMemberTypes) {
// The method getMemberTypes returns all classes contained directly by another class
// without taking into account if the class is declared locally to a method or not.
// The built annotation requires to contain only classes declared outside a method.
if (member instanceof JDefinedClass
&& ((JDefinedClass) member).getEnclosingMethod() != null) {
continue;
}
literals.add(new JClassLiteral(info, member));
}
if (!literals.isEmpty()) {
JMethodIdWide methodId = getOrCreateMethodId(memberClassAnnotationType, ELT_VALUE);
JAnnotation annotation = getAnnotation(type, memberClassAnnotationType, info);
JArrayLiteral array = new JArrayLiteral(info, literals);
JNameValuePair valuePair = new JNameValuePair(info, methodId, array);
request.append(new PutNameValuePair(annotation, valuePair));
}
}
private void addEnclosingClass(@Nonnull JDefinedClassOrInterface innerType) {
SourceInfo info = innerType.getSourceInfo();
JAnnotation annotation =
createAnnotation(innerType, enclosingClassAnnotationType, info);
JLiteral newValue = new JClassLiteral(info, innerType.getEnclosingType());
JMethodIdWide methodId = getOrCreateMethodId(enclosingClassAnnotationType, ELT_VALUE);
JNameValuePair valuePair = new JNameValuePair(info, methodId, newValue);
request.append(new PutNameValuePair(annotation, valuePair));
}
private void addInnerClass(@Nonnull JDefinedClassOrInterface innerType) {
SourceInfo info = innerType.getSourceInfo();
JAnnotation annotation = createAnnotation(innerType, innerAnnotationType, info);
SimpleName marker = innerType.getMarker(SimpleName.class);
assert marker != null;
String innerShortName = marker.getSimpleName();
JLiteral newValue;
if (!innerShortName.isEmpty()) {
newValue = new JStringLiteral(info, innerShortName);
} else {
newValue = new JNullLiteral(info);
}
JMethodIdWide nameMethodId = getOrCreateMethodId(innerAnnotationType, ELT_NAME);
JNameValuePair nameValuePair = new JNameValuePair(info, nameMethodId, newValue);
request.append(new PutNameValuePair(annotation, nameValuePair));
int accessFlags = innerType.getModifier();
// An anonymous class should not be flagged as final
if (innerType.isAnonymous()) {
accessFlags &= ~JModifier.FINAL;
}
// Add static flag on inner interfaces
if (JModifier.isInterface(accessFlags)) {
accessFlags |= JModifier.STATIC;
}
accessFlags &= AccessFlags.INNER_CLASS_FLAGS;
JMethodIdWide flagsMethodId = getOrCreateMethodId(innerAnnotationType, ELT_ACCESS_FLAGS);
JNameValuePair flagsValuePair =
new JNameValuePair(info, flagsMethodId, new JIntLiteral(info, accessFlags));
request.append(new PutNameValuePair(annotation, flagsValuePair));
}
@Nonnull
private JAnnotation createAnnotation(@Nonnull Annotable annotable,
@Nonnull JAnnotationType annotationType, @Nonnull SourceInfo info) {
JAnnotation annotation =
new JAnnotation(info, JRetentionPolicy.SYSTEM, annotationType);
request.append(new AddAnnotation(annotation, annotable));
return annotation;
}
private boolean isSystemAnnotation(@Nonnull JAnnotationType annotationType) {
if (annotationType.isSameType(defaultAnnotationType)
|| annotationType.isSameType(enclosingClassAnnotationType)
|| annotationType.isSameType(enclosingMethodAnnotationType)
|| annotationType.isSameType(innerAnnotationType)
|| annotationType.isSameType(memberClassAnnotationType)
|| annotationType.isSameType(signatureAnnotationType)
|| annotationType.isSameType(throwsAnnotationType)) {
return true;
}
return false;
}
@Nonnull
private JAnnotation getAnnotation(@Nonnull Annotable annotable,
@Nonnull JAnnotationType annotationType, @Nonnull SourceInfo info) {
assert isSystemAnnotation(annotationType);
JAnnotation annotation = null;
Collection<JAnnotation> annotations = annotable.getAnnotations(annotationType);
if (annotations.isEmpty()) {
annotation = createAnnotation(annotable, annotationType, info);
} else {
assert annotations.size() == 1;
annotation = annotations.iterator().next();
}
return annotation;
}
@Nonnull
private JArrayLiteral buildSignatureAnnotationValue (@Nonnull String signature,
@Nonnull SourceInfo info) {
int sigLength = signature.length();
List<JLiteral> pieces = new ArrayList<JLiteral>();
for (int at = 0; at < sigLength; /*at*/) {
char c = signature.charAt(at);
int endAt = at + 1;
if (c == 'L') {
// Scan to ';' or '<'. Consume ';' but not '<'.
while (endAt < sigLength) {
c = signature.charAt(endAt);
if (c == ';') {
endAt++;
break;
} else if (c == '<') {
break;
}
endAt++;
}
} else {
// Scan to 'L' without consuming it.
while (endAt < sigLength) {
c = signature.charAt(endAt);
if (c == 'L') {
break;
}
endAt++;
}
}
pieces.add(new JStringLiteral(info, signature.substring(at, endAt)));
at = endAt;
}
return new JArrayLiteral(info, pieces);
}
}
@Override
public synchronized void run(@Nonnull JDefinedClassOrInterface declaredType) {
TransformationRequest tr = new TransformationRequest(declaredType);
Visitor visitor = new Visitor(tr, Jack.getSession().getPhantomLookup());
visitor.accept(declaredType);
tr.commit();
}
}