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