| /* |
| * 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(); |
| } |
| } |