blob: fadfdb793c9b0b8b91917975c57d48a8c6742f2c [file] [log] [blame]
/*
* Copyright (C) 2015 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.transformations.ast.inner;
import com.android.jack.Options;
import com.android.jack.ir.SideEffectOperation;
import com.android.jack.ir.ast.JAlloc;
import com.android.jack.ir.ast.JAsgOperation;
import com.android.jack.ir.ast.JBinaryOperation;
import com.android.jack.ir.ast.JClass;
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.JExpression;
import com.android.jack.ir.ast.JExpressionStatement;
import com.android.jack.ir.ast.JField;
import com.android.jack.ir.ast.JFieldRef;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JMethodCall;
import com.android.jack.ir.ast.JMethodCall.DispatchKind;
import com.android.jack.ir.ast.JMethodId;
import com.android.jack.ir.ast.JModifier;
import com.android.jack.ir.ast.JNewInstance;
import com.android.jack.ir.ast.JNode;
import com.android.jack.ir.ast.JNullLiteral;
import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
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.sourceinfo.SourceInfo;
import com.android.jack.lookup.JMethodWithReturnLookupException;
import com.android.jack.transformations.ast.NewInstanceRemoved;
import com.android.jack.transformations.request.Replace;
import com.android.jack.transformations.request.TransformationRequest;
import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeForm;
import com.android.jack.util.NamingTools;
import com.android.jack.util.filter.Filter;
import com.android.sched.item.Description;
import com.android.sched.item.Synchronized;
import com.android.sched.schedulable.Constraint;
import com.android.sched.schedulable.RunnableSchedulable;
import com.android.sched.schedulable.Transform;
import com.android.sched.util.config.ThreadConfig;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Generate accessors for outer fields and methods in an inner class
*/
@Description("Generate accessors for outer fields and methods in an inner class")
@Synchronized
@Transform(add = {GetterMarker.class,
SetterMarker.class,
WrapperMarker.class,
JMethodCall.class,
JNewInstance.class,
JNullLiteral.class,
JExpressionStatement.class,
InnerAccessorSchedulingSeparator.SeparatorTag.class},
remove = {ThreeAddressCodeForm.class, NewInstanceRemoved.class})
@Constraint(no = {SideEffectOperation.class, JAlloc.class})
public class InnerAccessorGenerator implements RunnableSchedulable<JMethod> {
@Nonnull
static final String THIS_PARAM_NAME = NamingTools.getNonSourceConflictingName("this");
@Nonnull
protected final Filter<JMethod> filter = ThreadConfig.get(Options.METHOD_FILTER);
class Visitor extends JVisitor {
@Nonnull
protected final TransformationRequest tr;
@Nonnull
private final JDefinedClassOrInterface currentType;
public Visitor(@Nonnull TransformationRequest tr,
@Nonnull JDefinedClassOrInterface currentType) {
this.tr = tr;
this.currentType = currentType;
}
/**
* Determines where the accessor must be located in case of super invocation
* @param declaringType where the member is declared
* @return the class where the accessor will be located
*/
@Nonnull
private JDefinedClassOrInterface getAccessorClassForSuperCall(
@Nonnull JDefinedClassOrInterface declaringType) {
JDefinedClassOrInterface enclosing = currentType;
// If declaringType is an interface, the accessor class is the first enclosing
// type implementing this interface.
// If declaringType is a class, the accessor is the first enclosing type
// extending this class.
while (!enclosing.canBeSafelyUpcast(declaringType)) {
enclosing = (JDefinedClassOrInterface) enclosing.getEnclosingType();
}
return enclosing;
}
/**
* Determines where the accessor must be located
* @param modifier the modifier of the member we try to access to
* @param declaringType where the member is declared
* @return the class where the accessor will be located
*/
@Nonnull
private JDefinedClassOrInterface getAccessorClass(int modifier,
@Nonnull JDefinedClassOrInterface declaringType) {
// Search the first class in which the member is accessible
// from the referencing type to its enclosing types
JDefinedClassOrInterface refType = currentType;
while (refType != null) {
if (isDirectlyVisibleFrom(modifier, declaringType, refType)) {
return refType;
}
refType = (JDefinedClassOrInterface) refType.getEnclosingType();
}
// If not found, the accessor must be in the declaring class
assert JModifier.isPrivate(modifier);
return declaringType;
}
/**
* Indicates that a field or method can be accessed without accessor
* @param modifier the modifier of the field or method
* @param declaringType the class where the field or method is declared
* @param type the type from where we want to know if the field or method is accessible
* @return true if the field or method is visible without accessor
*/
private boolean isDirectlyVisibleFrom(int modifier,
@Nonnull JDefinedClassOrInterface declaringType, @Nonnull JDefinedClassOrInterface type) {
if (JModifier.isPublic(modifier) || declaringType.isSameType(type)) {
return true;
}
if (JModifier.isPrivate(modifier)) {
// The case when type is the declaring type has already been treated
return false;
}
if (JModifier.isProtected(modifier) && type.canBeSafelyUpcast(declaringType)) {
return true;
}
// The field is protected (but not from a super class) or package
// We test if both classes are in same package
return declaringType.getEnclosingPackage() == type.getEnclosingPackage();
}
@Override
public boolean visit(@Nonnull JFieldRef x) {
JNode parent = x.getParent();
JField field = x.getFieldId().getField();
assert field != null;
JDefinedClassOrInterface accessorClass = getAccessorClass(field.getModifier(),
field.getEnclosingType());
if (!accessorClass.isSameType(currentType)) {
assert accessorClass.getSourceInfo().getFileSourceInfo()
.equals(currentType.getSourceInfo().getFileSourceInfo());
if (parent instanceof JAsgOperation
&& ((JAsgOperation) parent).getLhs() == x) {
// writing access
//
handleOuterFieldWrite(tr, x, accessorClass);
} else {
// reading access
//
handleOuterFieldRead(tr, x, accessorClass);
}
}
return super.visit(x);
}
@Override
public boolean visit(@Nonnull JMethodCall x) {
JClassOrInterface receiverType = x.getReceiverType();
// No need to generate an accessor if receiver type is an interface since method will be
// visible
if (receiverType instanceof JDefinedClass) {
JType returnType =
x instanceof JNewInstance ? JPrimitiveTypeEnum.VOID.getType() : x.getType();
JMethod method = getMethod((JDefinedClassOrInterface) receiverType,
(JDefinedClassOrInterface) receiverType, returnType, x.getMethodId());
// Method can be null when an interface method is implemented by a sub type of the receiver
// type, but in this case accessors are not needed
if (method != null) {
JDefinedClassOrInterface accessorClass;
boolean isSuper = x.getDispatchKind() == DispatchKind.DIRECT
&& method.getMethodId().getKind() == MethodKind.INSTANCE_VIRTUAL;
if (isSuper) {
accessorClass = getAccessorClassForSuperCall(method.getEnclosingType());
} else {
accessorClass = getAccessorClass(method.getModifier(), method.getEnclosingType());
}
if (!accessorClass.isSameType(currentType)) {
assert accessorClass.getSourceInfo().getFileSourceInfo()
.equals(currentType.getSourceInfo().getFileSourceInfo());
handleOuterMethodCall(tr, x, method, accessorClass, isSuper);
}
}
}
return super.visit(x);
}
@CheckForNull
private JMethod getMethod(@Nonnull JDefinedClassOrInterface receiverType,
@Nonnull JDefinedClassOrInterface typeToSearchMth,
@Nonnull JType returnType, @Nonnull JMethodId mthId) {
try {
JMethod methodFound =
typeToSearchMth.getMethod(mthId.getName(), returnType, mthId.getParamTypes());
if (isDirectlyVisibleFrom(methodFound.getModifier(), methodFound.getEnclosingType(),
receiverType)) {
return methodFound;
}
} catch (JMethodWithReturnLookupException e) {
// Continue to search into super class
}
JClass superClass = typeToSearchMth.getSuperClass();
JMethod methodFound;
if (superClass instanceof JDefinedClass) {
methodFound = getMethod(receiverType, (JDefinedClass) superClass, returnType, mthId);
if (methodFound != null) {
return methodFound;
}
}
return null;
}
}
protected void handleOuterFieldWrite(@Nonnull TransformationRequest tr,
@Nonnull JFieldRef fieldRef, @Nonnull JDefinedClassOrInterface accessorClass) {
JField field = fieldRef.getFieldId().getField();
assert(field != null);
SetterMarker marker = accessorClass.getMarker(SetterMarker.class);
if (marker == null) {
marker = new SetterMarker();
accessorClass.addMarker(marker);
}
JMethod setter = marker.getOrCreateSetter(field, (JDefinedClass) accessorClass);
// this.this$0.field = $value => $set<id>(this.this$0, $value)
JBinaryOperation binOp = (JBinaryOperation) fieldRef.getParent();
JMethodId setterId = setter.getMethodId();
JMethodCall setterCall =
new JMethodCall(binOp.getSourceInfo(), null, accessorClass, setterId,
setter.getType(), setterId.canBeVirtual());
if (!field.isStatic()) {
JExpression instance = fieldRef.getInstance();
assert instance != null;
setterCall.addArg(instance);
}
setterCall.addArg(binOp.getRhs());
assert setterCall.getArgs().size() == setter.getParams().size();
tr.append(new Replace(binOp, setterCall));
}
protected void handleOuterFieldRead(@Nonnull TransformationRequest tr,
@Nonnull JFieldRef fieldRef, @Nonnull JDefinedClassOrInterface accessorClass) {
JField field = fieldRef.getFieldId().getField();
assert(field != null);
GetterMarker marker = accessorClass.getMarker(GetterMarker.class);
if (marker == null) {
marker = new GetterMarker();
accessorClass.addMarker(marker);
}
JMethod getter = marker.getOrCreateGetter(field, (JDefinedClass) accessorClass);
// this.this$0.field => $get<id>(this.this$0)
JMethodId getterId = getter.getMethodId();
JMethodCall getterCall = new JMethodCall(fieldRef.getSourceInfo(), null, accessorClass,
getterId, getter.getType(), getterId.canBeVirtual());
if (!field.isStatic()) {
JExpression instance = fieldRef.getInstance();
assert instance != null;
getterCall.addArg(instance);
}
assert getterCall.getArgs().size() == getter.getParams().size();
tr.append(new Replace(fieldRef, getterCall));
}
protected void handleOuterMethodCall(@Nonnull TransformationRequest tr,
@Nonnull JMethodCall methodCall, @Nonnull JMethod method,
@Nonnull JDefinedClassOrInterface accessorClass, boolean isSuper) {
WrapperMarker marker = accessorClass.getMarker(WrapperMarker.class);
if (marker == null) {
marker = new WrapperMarker();
accessorClass.addMarker(marker);
}
JMethod wrapper = marker.getOrCreateWrapper(method, (JDefinedClass) accessorClass,
isSuper, methodCall.getReceiverType());
JMethodCall wrapperCall = null;
SourceInfo sourceInfo = methodCall.getSourceInfo();
if (methodCall instanceof JNewInstance) {
assert wrapper instanceof JConstructor;
wrapperCall =
new JNewInstance(sourceInfo, wrapper.getEnclosingType(), wrapper.getMethodId());
} else {
JMethodId wrapperId = wrapper.getMethodId();
// this.this$0.method(param) => $wrap<id>(this.this$0, param)
if (!method.isStatic() && !(wrapper instanceof JConstructor)) {
wrapperCall = new JMethodCall(sourceInfo, null, accessorClass, wrapperId,
wrapper.getType(), wrapperId.canBeVirtual());
JExpression instance = methodCall.getInstance();
assert instance != null;
wrapperCall.addArg(instance);
} else {
wrapperCall = new JMethodCall(sourceInfo, methodCall.getInstance(), accessorClass,
wrapperId, wrapper.getType(), wrapperId.canBeVirtual());
}
}
for (JExpression arg : methodCall.getArgs()) {
wrapperCall.addArg(arg);
}
if (wrapper instanceof JConstructor) {
int numberOfParamToAdd = wrapper.getParams().size() - method.getParams().size();
for (int i = 0; i < numberOfParamToAdd; i++) {
wrapperCall.addArg(new JNullLiteral(sourceInfo));
}
}
assert wrapperCall.getArgs().size() == wrapper.getParams().size();
tr.append(new Replace(methodCall, wrapperCall));
}
@Override
public synchronized void run(@Nonnull JMethod method) throws Exception {
if (method.isNative() || method.isAbstract() || !filter.accept(this.getClass(), method)) {
return;
}
TransformationRequest tr = new TransformationRequest(method);
Visitor visitor = new Visitor(tr, method.getEnclosingType());
visitor.accept(method);
tr.commit();
}
}