• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4 
5 package com.android.tools.r8.ir.desugar;
6 
7 import com.android.tools.r8.Resource;
8 import com.android.tools.r8.errors.CompilationError;
9 import com.android.tools.r8.errors.Unimplemented;
10 import com.android.tools.r8.graph.DexApplication.Builder;
11 import com.android.tools.r8.graph.DexCallSite;
12 import com.android.tools.r8.graph.DexClass;
13 import com.android.tools.r8.graph.DexEncodedMethod;
14 import com.android.tools.r8.graph.DexItemFactory;
15 import com.android.tools.r8.graph.DexMethod;
16 import com.android.tools.r8.graph.DexMethodHandle;
17 import com.android.tools.r8.graph.DexProgramClass;
18 import com.android.tools.r8.graph.DexType;
19 import com.android.tools.r8.graph.DexValue;
20 import com.android.tools.r8.ir.code.BasicBlock;
21 import com.android.tools.r8.ir.code.IRCode;
22 import com.android.tools.r8.ir.code.Instruction;
23 import com.android.tools.r8.ir.code.InstructionListIterator;
24 import com.android.tools.r8.ir.code.InvokeStatic;
25 import com.android.tools.r8.ir.code.InvokeSuper;
26 import com.android.tools.r8.ir.conversion.IRConverter;
27 import com.google.common.collect.ImmutableSet;
28 import com.google.common.collect.Sets;
29 import java.util.ListIterator;
30 import java.util.Map;
31 import java.util.Set;
32 
33 //
34 // Default and static interface method desugaring rewriter (note that lambda
35 // desugaring should have already processed the code before this rewriter).
36 //
37 // In short, during default and static interface method desugaring
38 // the following actions are performed:
39 //
40 //   (1) All static interface methods are moved into companion classes. All calls
41 //       to these methods are redirected appropriately. All references to these
42 //       methods from method handles are reported as errors.
43 //
44 // Companion class is a synthesized class (<interface-name>-CC) created to host
45 // static and former default interface methods (see below) from the interface.
46 //
47 //   (2) All default interface methods are made static and moved into companion
48 //       class.
49 //
50 //   (3) All calls to default interface methods made via 'super' are changed
51 //       to directly call appropriate static methods in companion classes.
52 //
53 //   (4) All other calls or references to default interface methods are not changed.
54 //
55 //   (5) For all program classes explicitly implementing interfaces we analyze the
56 //       set of default interface methods missing and add them, the created methods
57 //       forward the call to an appropriate method in interface companion class.
58 //
59 public final class InterfaceMethodRewriter {
60   private static final String COMPANION_CLASS_NAME_SUFFIX = "-CC";
61   private static final String DEFAULT_METHOD_PREFIX = "$default$";
62 
63   private final IRConverter converter;
64   final DexItemFactory factory;
65 
66   // All forwarding methods generated during desugaring. We don't synchronize access
67   // to this collection since it is only filled in ClassProcessor running synchronously.
68   private final Set<DexEncodedMethod> forwardingMethods = Sets.newIdentityHashSet();
69 
70   /** Defines a minor variation in desugaring. */
71   public enum Flavor {
72     /** Process all application resources. */
73     IncludeAllResources,
74     /** Process all but DEX application resources. */
75     ExcludeDexResources
76   }
77 
InterfaceMethodRewriter(IRConverter converter)78   public InterfaceMethodRewriter(IRConverter converter) {
79     assert converter != null;
80     this.converter = converter;
81     this.factory = converter.application.dexItemFactory;
82   }
83 
84   // Rewrites the references to static and default interface methods.
85   // NOTE: can be called for different methods concurrently.
rewriteMethodReferences(DexEncodedMethod encodedMethod, IRCode code)86   public void rewriteMethodReferences(DexEncodedMethod encodedMethod, IRCode code) {
87     if (forwardingMethods.contains(encodedMethod)) {
88       return;
89     }
90 
91     ListIterator<BasicBlock> blocks = code.listIterator();
92     while (blocks.hasNext()) {
93       BasicBlock block = blocks.next();
94       InstructionListIterator instructions = block.listIterator();
95       while (instructions.hasNext()) {
96         Instruction instruction = instructions.next();
97 
98         if (instruction.isInvokeCustom()) {
99           // Check that static interface methods are not referenced
100           // from invoke-custom instructions via method handles.
101           DexCallSite callSite = instruction.asInvokeCustom().getCallSite();
102           reportStaticInterfaceMethodHandle(callSite.bootstrapMethod);
103           for (DexValue arg : callSite.bootstrapArgs) {
104             if (arg instanceof DexValue.DexValueMethodHandle) {
105               reportStaticInterfaceMethodHandle(((DexValue.DexValueMethodHandle) arg).value);
106             }
107           }
108           continue;
109         }
110 
111         if (instruction.isInvokeStatic()) {
112           InvokeStatic invokeStatic = instruction.asInvokeStatic();
113           DexMethod method = invokeStatic.getInvokedMethod();
114           if (isInterfaceClass(method.holder)) {
115             // Retarget call to an appropriate method of companion class.
116             instructions.replaceCurrentInstruction(
117                 new InvokeStatic(staticAsMethodOfCompanionClass(method),
118                     invokeStatic.outValue(), invokeStatic.arguments()));
119           }
120           continue;
121         }
122 
123         if (instruction.isInvokeSuper()) {
124           InvokeSuper invokeSuper = instruction.asInvokeSuper();
125           DexMethod method = invokeSuper.getInvokedMethod();
126           if (isInterfaceClass(method.holder)) {
127             // Retarget call to an appropriate method of companion class.
128             instructions.replaceCurrentInstruction(
129                 new InvokeStatic(defaultAsMethodOfCompanionClass(method),
130                     invokeSuper.outValue(), invokeSuper.arguments()));
131           }
132         }
133       }
134     }
135   }
136 
reportStaticInterfaceMethodHandle(DexMethodHandle handle)137   private void reportStaticInterfaceMethodHandle(DexMethodHandle handle) {
138     if (handle.type.isInvokeStatic() && isInterfaceClass(handle.asMethod().holder)) {
139       throw new Unimplemented(
140           "Desugaring of static interface method handle in is not yet supported.");
141     }
142   }
143 
isInterfaceClass(DexType type)144   private boolean isInterfaceClass(DexType type) {
145     return findRequiredClass(type).isInterface();
146   }
147 
148   // Returns the class for the type specified, report errors for missing classes.
findRequiredClass(DexType type)149   final DexClass findRequiredClass(DexType type) {
150     DexClass clazz = converter.appInfo.definitionFor(type);
151     if (clazz != null) {
152       return clazz;
153     }
154     throw new CompilationError("Type '" + type.toSourceString() +
155         "' required for default and static interface methods desugaring not found.");
156   }
157 
158   // Gets the companion class for the interface `type`.
getCompanionClassType(DexType type)159   final DexType getCompanionClassType(DexType type) {
160     assert type.isClassType();
161     String descriptor = type.descriptor.toString();
162     String ccTypeDescriptor = descriptor.substring(0, descriptor.length() - 1)
163         + COMPANION_CLASS_NAME_SUFFIX + ";";
164     return factory.createType(ccTypeDescriptor);
165   }
166 
isInMainDexList(DexType iface)167   private boolean isInMainDexList(DexType iface) {
168     ImmutableSet<DexType> list = converter.application.mainDexList;
169     return list.contains(iface);
170   }
171 
172   // Represent a static interface method as a method of companion class.
staticAsMethodOfCompanionClass(DexMethod method)173   final DexMethod staticAsMethodOfCompanionClass(DexMethod method) {
174     // No changes for static methods.
175     return factory.createMethod(getCompanionClassType(method.holder), method.proto, method.name);
176   }
177 
178   // Represent a default interface method as a method of companion class.
defaultAsMethodOfCompanionClass(DexMethod method)179   final DexMethod defaultAsMethodOfCompanionClass(DexMethod method) {
180     // Add an implicit argument to represent the receiver.
181     DexType[] params = method.proto.parameters.values;
182     DexType[] newParams = new DexType[params.length + 1];
183     newParams[0] = method.holder;
184     System.arraycopy(params, 0, newParams, 1, params.length);
185 
186     // Add prefix to avoid name conflicts.
187     return factory.createMethod(getCompanionClassType(method.holder),
188         factory.createProto(method.proto.returnType, newParams),
189         factory.createString(DEFAULT_METHOD_PREFIX + method.name.toString()));
190   }
191 
192   /**
193    * Move static and default interface methods to companion classes,
194    * add missing methods to forward to moved default methods implementation.
195    */
desugarInterfaceMethods(Builder builder, Flavor flavour)196   public void desugarInterfaceMethods(Builder builder, Flavor flavour) {
197     // Process all classes first. Add missing forwarding methods to
198     // replace desugared default interface methods.
199     forwardingMethods.addAll(processClasses(builder, flavour));
200 
201     // Process interfaces, create companion class if needed, move static methods
202     // to companion class, copy default interface methods to companion classes,
203     // make original default methods abstract, remove bridge methods.
204     Map<DexProgramClass, DexProgramClass> companionClasses =
205         processInterfaces(builder, flavour);
206 
207     for (Map.Entry<DexProgramClass, DexProgramClass> entry : companionClasses.entrySet()) {
208       // Don't need to optimize synthesized class since all of its methods
209       // are just moved from interfaces and don't need to be re-processed.
210       builder.addSynthesizedClass(entry.getValue(), isInMainDexList(entry.getKey().type));
211     }
212 
213     for (DexEncodedMethod method : forwardingMethods) {
214       converter.optimizeSynthesizedMethod(method);
215     }
216   }
217 
shouldProcess( DexProgramClass clazz, Flavor flavour, boolean mustBeInterface)218   private static boolean shouldProcess(
219       DexProgramClass clazz, Flavor flavour, boolean mustBeInterface) {
220     return (clazz.getOrigin() != Resource.Kind.DEX || flavour == Flavor.IncludeAllResources)
221         && clazz.isInterface() == mustBeInterface;
222   }
223 
processInterfaces( Builder builder, Flavor flavour)224   private Map<DexProgramClass, DexProgramClass> processInterfaces(
225       Builder builder, Flavor flavour) {
226     InterfaceProcessor processor = new InterfaceProcessor(this);
227     for (DexProgramClass clazz : builder.getProgramClasses()) {
228       if (shouldProcess(clazz, flavour, true)) {
229         processor.process(clazz.asProgramClass());
230       }
231     }
232     return processor.companionClasses;
233   }
234 
processClasses(Builder builder, Flavor flavour)235   private Set<DexEncodedMethod> processClasses(Builder builder, Flavor flavour) {
236     ClassProcessor processor = new ClassProcessor(this);
237     for (DexProgramClass clazz : builder.getProgramClasses()) {
238       if (shouldProcess(clazz, flavour, false)) {
239         processor.process(clazz);
240       }
241     }
242     return processor.getForwardMethods();
243   }
244 
isDefaultMethod(DexEncodedMethod method)245   final boolean isDefaultMethod(DexEncodedMethod method) {
246     assert !method.accessFlags.isConstructor();
247     assert !method.accessFlags.isStatic();
248 
249     if (method.accessFlags.isAbstract()) {
250       return false;
251     }
252     if (method.accessFlags.isNative()) {
253       throw new Unimplemented("Native default interface methods are not yet supported.");
254     }
255     if (!method.accessFlags.isPublic()) {
256       // NOTE: even though the class is allowed to have non-public interface methods
257       // with code, for example private methods, all such methods we are aware of are
258       // created by the compiler for stateful lambdas and they must be converted into
259       // static methods by lambda desugaring by this time.
260       throw new Unimplemented("Non public default interface methods are not yet supported.");
261     }
262     return true;
263   }
264 }
265