• 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.dex.Constants;
8 import com.android.tools.r8.errors.Unimplemented;
9 import com.android.tools.r8.errors.Unreachable;
10 import com.android.tools.r8.graph.DexAccessFlags;
11 import com.android.tools.r8.graph.DexAnnotationSet;
12 import com.android.tools.r8.graph.DexAnnotationSetRefList;
13 import com.android.tools.r8.graph.DexClass;
14 import com.android.tools.r8.graph.DexCode;
15 import com.android.tools.r8.graph.DexEncodedField;
16 import com.android.tools.r8.graph.DexEncodedMethod;
17 import com.android.tools.r8.graph.DexField;
18 import com.android.tools.r8.graph.DexItemFactory;
19 import com.android.tools.r8.graph.DexMethod;
20 import com.android.tools.r8.graph.DexMethodHandle;
21 import com.android.tools.r8.graph.DexProgramClass;
22 import com.android.tools.r8.graph.DexProto;
23 import com.android.tools.r8.graph.DexString;
24 import com.android.tools.r8.graph.DexType;
25 import com.android.tools.r8.graph.DexTypeList;
26 import com.android.tools.r8.graph.DexValue.DexValueNull;
27 import com.android.tools.r8.ir.code.Invoke;
28 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
29 import java.util.List;
30 import java.util.concurrent.atomic.AtomicBoolean;
31 
32 /**
33  * Represents lambda class generated for a lambda descriptor in context
34  * of lambda instantiation point.
35  *
36  * Even though call sites, and thus lambda descriptors, are canonicalized
37  * across the application, the context may require several lambda classes
38  * to be generated for the same lambda descriptor.
39  *
40  * One reason is that we always generate a lambda class in the same package
41  * lambda instantiation point is located in, so if same call site is used in
42  * two classes from different packages (which can happen if same public method
43  * is being references via method reference expression) we generate separate
44  * lambda classes in those packages.
45  *
46  * Another reason is that if we generate an accessor, we generate it in the
47  * class referencing the call site, and thus two such classes will require two
48  * separate lambda classes.
49  */
50 final class LambdaClass {
51 
52   final LambdaRewriter rewriter;
53   final DexType type;
54   final LambdaDescriptor descriptor;
55   final DexMethod constructor;
56   final DexMethod classConstructor;
57   final DexField instanceField;
58   final Target target;
59   final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
60 
LambdaClass(LambdaRewriter rewriter, DexType accessedFrom, DexType lambdaClassType, LambdaDescriptor descriptor)61   LambdaClass(LambdaRewriter rewriter, DexType accessedFrom,
62       DexType lambdaClassType, LambdaDescriptor descriptor) {
63     assert rewriter != null;
64     assert lambdaClassType != null;
65     assert descriptor != null;
66 
67     this.rewriter = rewriter;
68     this.type = lambdaClassType;
69     this.descriptor = descriptor;
70 
71     DexItemFactory factory = rewriter.factory;
72     DexProto constructorProto = factory.createProto(
73         factory.voidType, descriptor.captures.values);
74     this.constructor = factory.createMethod(
75         lambdaClassType, constructorProto, rewriter.constructorName);
76 
77     this.target = createTarget(accessedFrom);
78 
79     boolean stateless = isStateless();
80     this.classConstructor = !stateless ? null
81         : factory.createMethod(lambdaClassType, constructorProto, rewriter.classConstructorName);
82     this.instanceField = !stateless ? null
83         : factory.createField(lambdaClassType, lambdaClassType, rewriter.instanceFieldName);
84   }
85 
86   // Generate unique lambda class type for lambda descriptor and instantiation point context.
createLambdaClassType( LambdaRewriter rewriter, DexType accessedFrom, LambdaDescriptor match)87   static DexType createLambdaClassType(
88       LambdaRewriter rewriter, DexType accessedFrom, LambdaDescriptor match) {
89     StringBuilder lambdaClassDescriptor = new StringBuilder("L");
90 
91     // We always create lambda class in the same package where it is referenced.
92     String packageDescriptor = accessedFrom.getPackageDescriptor();
93     if (!packageDescriptor.isEmpty()) {
94       lambdaClassDescriptor.append(packageDescriptor).append('/');
95     }
96 
97     // Lambda class name prefix
98     lambdaClassDescriptor.append(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX);
99 
100     // If the lambda class should match 1:1 the class it is accessed from, we
101     // just add the name of this type to make lambda class name unique.
102     // It also helps link the class lambda originated from in some cases.
103     if (match.delegatesToLambdaImplMethod() || match.needsAccessor(accessedFrom)) {
104       lambdaClassDescriptor.append(accessedFrom.getName()).append('$');
105     }
106 
107     // Add unique lambda descriptor id
108     lambdaClassDescriptor.append(match.uniqueId).append(';');
109     return rewriter.factory.createType(lambdaClassDescriptor.toString());
110   }
111 
synthesizeLambdaClass()112   final DexProgramClass synthesizeLambdaClass() {
113     return new DexProgramClass(
114         type,
115         null,
116         new DexAccessFlags(Constants.ACC_FINAL | Constants.ACC_SYNTHETIC),
117         rewriter.factory.objectType,
118         buildInterfaces(),
119         rewriter.factory.createString("lambda"),
120         DexAnnotationSet.empty(),
121         synthesizeStaticFields(),
122         synthesizeInstanceFields(),
123         synthesizeDirectMethods(),
124         synthesizeVirtualMethods()
125     );
126   }
127 
getCaptureField(int index)128   final DexField getCaptureField(int index) {
129     return rewriter.factory.createField(this.type,
130         descriptor.captures.values[index], rewriter.factory.createString("f$" + index));
131   }
132 
isStateless()133   final boolean isStateless() {
134     return descriptor.isStateless();
135   }
136 
137   // Synthesize virtual methods.
synthesizeVirtualMethods()138   private DexEncodedMethod[] synthesizeVirtualMethods() {
139     DexEncodedMethod[] methods = new DexEncodedMethod[1 + descriptor.bridges.size()];
140     int index = 0;
141 
142     // Synthesize main method.
143     DexMethod mainMethod = rewriter.factory
144         .createMethod(type, descriptor.erasedProto, descriptor.name);
145     methods[index++] = new DexEncodedMethod(
146         mainMethod,
147         new DexAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_FINAL),
148         DexAnnotationSet.empty(),
149         DexAnnotationSetRefList.empty(),
150         new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
151 
152     // Synthesize bridge methods.
153     for (DexProto bridgeProto : descriptor.bridges) {
154       DexMethod bridgeMethod = rewriter.factory.createMethod(type, bridgeProto, descriptor.name);
155       methods[index++] = new DexEncodedMethod(
156           bridgeMethod,
157           new DexAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_FINAL
158               | Constants.ACC_SYNTHETIC | Constants.ACC_BRIDGE),
159           DexAnnotationSet.empty(),
160           DexAnnotationSetRefList.empty(),
161           new SynthesizedCode(
162               new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
163     }
164     return methods;
165   }
166 
167   // Synthesize direct methods.
synthesizeDirectMethods()168   private DexEncodedMethod[] synthesizeDirectMethods() {
169     boolean stateless = isStateless();
170     DexEncodedMethod[] methods = new DexEncodedMethod[stateless ? 2 : 1];
171 
172     // Constructor.
173     methods[0] = new DexEncodedMethod(
174         constructor,
175         new DexAccessFlags((stateless ? Constants.ACC_PRIVATE : Constants.ACC_PUBLIC) |
176             Constants.ACC_SYNTHETIC | Constants.ACC_CONSTRUCTOR),
177         DexAnnotationSet.empty(),
178         DexAnnotationSetRefList.empty(),
179         new SynthesizedCode(new LambdaConstructorSourceCode(this)));
180 
181     // Class constructor for stateless lambda classes.
182     if (stateless) {
183       methods[1] = new DexEncodedMethod(
184           classConstructor,
185           new DexAccessFlags(
186               Constants.ACC_SYNTHETIC | Constants.ACC_CONSTRUCTOR | Constants.ACC_STATIC),
187           DexAnnotationSet.empty(),
188           DexAnnotationSetRefList.empty(),
189           new SynthesizedCode(new LambdaClassConstructorSourceCode(this)));
190     }
191     return methods;
192   }
193 
194   // Synthesize instance fields to represent captured values.
synthesizeInstanceFields()195   private DexEncodedField[] synthesizeInstanceFields() {
196     DexType[] fieldTypes = descriptor.captures.values;
197     int fieldCount = fieldTypes.length;
198     DexEncodedField[] fields = new DexEncodedField[fieldCount];
199     for (int i = 0; i < fieldCount; i++) {
200       DexAccessFlags accessFlags = new DexAccessFlags(
201           Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PRIVATE);
202       fields[i] = new DexEncodedField(
203           getCaptureField(i), accessFlags, DexAnnotationSet.empty(), null);
204     }
205     return fields;
206   }
207 
208   // Synthesize static fields to represent singleton instance.
synthesizeStaticFields()209   private DexEncodedField[] synthesizeStaticFields() {
210     if (!isStateless()) {
211       return DexEncodedField.EMPTY_ARRAY;
212     }
213 
214     // Create instance field for stateless lambda.
215     assert this.instanceField != null;
216     DexEncodedField[] fields = new DexEncodedField[1];
217     fields[0] = new DexEncodedField(
218         this.instanceField,
219         new DexAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_FINAL
220             | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC),
221         DexAnnotationSet.empty(),
222         DexValueNull.NULL);
223     return fields;
224   }
225 
226   // Build a list of implemented interfaces.
buildInterfaces()227   private DexTypeList buildInterfaces() {
228     List<DexType> interfaces = descriptor.interfaces;
229     return interfaces.isEmpty() ? DexTypeList.empty()
230         : new DexTypeList(interfaces.toArray(new DexType[interfaces.size()]));
231   }
232 
233   // Creates a delegation target for this particular lambda class. Note that we
234   // should always be able to create targets for the lambdas we support.
createTarget(DexType accessedFrom)235   private Target createTarget(DexType accessedFrom) {
236     if (descriptor.delegatesToLambdaImplMethod()) {
237       return createLambdaImplMethodTarget(accessedFrom);
238     }
239 
240     // Method referenced directly, without lambda$ method.
241     switch (descriptor.implHandle.type) {
242       case INVOKE_SUPER:
243         throw new Unimplemented("Method references to super methods are not yet supported");
244       case INVOKE_INTERFACE:
245         return createInterfaceMethodTarget(accessedFrom);
246       case INVOKE_CONSTRUCTOR:
247         return createConstructorTarget(accessedFrom);
248       case INVOKE_STATIC:
249         return createStaticMethodTarget(accessedFrom);
250       case INVOKE_INSTANCE:
251         return createInstanceMethodTarget(accessedFrom);
252       default:
253         throw new Unreachable("Unexpected method handle type in " + descriptor.implHandle);
254     }
255   }
256 
createLambdaImplMethodTarget(DexType accessedFrom)257   private Target createLambdaImplMethodTarget(DexType accessedFrom) {
258     DexMethodHandle implHandle = descriptor.implHandle;
259     assert implHandle != null;
260     DexMethod implMethod = implHandle.asMethod();
261 
262     // Lambda$ method. We must always find it.
263     assert implMethod.holder == accessedFrom;
264     assert descriptor.targetFoundInClass(accessedFrom);
265     assert descriptor.getAccessibility() != null;
266     assert descriptor.getAccessibility().isPrivate();
267     assert descriptor.getAccessibility().isSynthetic();
268 
269     if (implHandle.type.isInvokeStatic()) {
270       return new StaticLambdaImplTarget();
271     }
272 
273     assert implHandle.type.isInvokeInstance();
274 
275     // If lambda$ method is an instance method we convert it into a static methods and
276     // relax its accessibility.
277     DexProto implProto = implMethod.proto;
278     DexType[] implParams = implProto.parameters.values;
279     DexType[] newParams = new DexType[implParams.length + 1];
280     newParams[0] = implMethod.holder;
281     System.arraycopy(implParams, 0, newParams, 1, implParams.length);
282 
283     DexProto newProto = rewriter.factory.createProto(implProto.returnType, newParams);
284     return new InstanceLambdaImplTarget(
285         rewriter.factory.createMethod(implMethod.holder, newProto, implMethod.name));
286   }
287 
288   // Create targets for instance method referenced directly without
289   // lambda$ methods. It may require creation of accessors in some cases.
createInstanceMethodTarget(DexType accessedFrom)290   private Target createInstanceMethodTarget(DexType accessedFrom) {
291     assert descriptor.implHandle.type.isInvokeInstance();
292 
293     if (!descriptor.needsAccessor(accessedFrom)) {
294       return new NoAccessorMethodTarget(Invoke.Type.VIRTUAL);
295     }
296     // We need to generate an accessor method in `accessedFrom` class/interface
297     // for accessing the original instance impl-method. Note that impl-method's
298     // holder does not have to be the same as `accessedFrom`.
299     DexMethod implMethod = descriptor.implHandle.asMethod();
300     DexProto implProto = implMethod.proto;
301     DexType[] implParams = implProto.parameters.values;
302 
303     // The accessor method will be static, package private, and take the
304     // receiver as the first argument. The receiver must be captured and
305     // be the first captured value in case there are more than one.
306     DexType[] accessorParams = new DexType[1 + implParams.length];
307     accessorParams[0] = descriptor.getImplReceiverType();
308     System.arraycopy(implParams, 0, accessorParams, 1, implParams.length);
309     DexProto accessorProto = rewriter.factory.createProto(implProto.returnType, accessorParams);
310     DexMethod accessorMethod = rewriter.factory.createMethod(
311         accessedFrom, accessorProto, generateUniqueLambdaMethodName());
312 
313     return new ClassMethodWithAccessorTarget(accessorMethod);
314   }
315 
316   // Create targets for static method referenced directly without
317   // lambda$ methods. It may require creation of accessors in some cases.
createStaticMethodTarget(DexType accessedFrom)318   private Target createStaticMethodTarget(DexType accessedFrom) {
319     assert descriptor.implHandle.type.isInvokeStatic();
320 
321     if (!descriptor.needsAccessor(accessedFrom)) {
322       return new NoAccessorMethodTarget(Invoke.Type.STATIC);
323     }
324 
325     // We need to generate an accessor method in `accessedFrom` class/interface
326     // for accessing the original static impl-method. The accessor method will be
327     // static, package private with exactly same signature and the original method.
328     DexMethod accessorMethod = rewriter.factory.createMethod(accessedFrom,
329         descriptor.implHandle.asMethod().proto, generateUniqueLambdaMethodName());
330     return new ClassMethodWithAccessorTarget(accessorMethod);
331   }
332 
333   // Create targets for constructor referenced directly without lambda$ methods.
334   // It may require creation of accessors in some cases.
createConstructorTarget(DexType accessedFrom)335   private Target createConstructorTarget(DexType accessedFrom) {
336     DexMethodHandle implHandle = descriptor.implHandle;
337     assert implHandle != null;
338     assert implHandle.type.isInvokeConstructor();
339 
340     if (!descriptor.needsAccessor(accessedFrom)) {
341       return new NoAccessorMethodTarget(Invoke.Type.DIRECT);
342     }
343 
344     // We need to generate an accessor method in `accessedFrom` class/interface for
345     // instantiating the class and calling constructor on it. The accessor method will
346     // be static, package private with exactly same parameters as the constructor,
347     // and return the newly created instance.
348     DexMethod implMethod = implHandle.asMethod();
349     DexType returnType = implMethod.holder;
350     DexProto accessorProto = rewriter.factory.createProto(
351         returnType, implMethod.proto.parameters.values);
352     DexMethod accessorMethod = rewriter.factory.createMethod(accessedFrom,
353         accessorProto, generateUniqueLambdaMethodName());
354     return new ClassMethodWithAccessorTarget(accessorMethod);
355   }
356 
357   // Create targets for interface methods.
createInterfaceMethodTarget(DexType accessedFrom)358   private Target createInterfaceMethodTarget(DexType accessedFrom) {
359     assert descriptor.implHandle.type.isInvokeInterface();
360     assert !descriptor.needsAccessor(accessedFrom);
361     return new NoAccessorMethodTarget(Invoke.Type.INTERFACE);
362   }
363 
generateUniqueLambdaMethodName()364   private DexString generateUniqueLambdaMethodName() {
365     return rewriter.factory.createString(
366         LambdaRewriter.EXPECTED_LAMBDA_METHOD_PREFIX + descriptor.uniqueId);
367   }
368 
369   // Represents information about the method lambda class need to delegate the call to. It may
370   // be the same method as specified in lambda descriptor or a newly synthesized accessor.
371   // Also provides action for ensuring accessibility of the referenced symbols.
372   abstract class Target {
373 
374     final DexMethod callTarget;
375     final Invoke.Type invokeType;
376 
Target(DexMethod callTarget, Invoke.Type invokeType)377     Target(DexMethod callTarget, Invoke.Type invokeType) {
378       assert callTarget != null;
379       assert invokeType != null;
380       this.callTarget = callTarget;
381       this.invokeType = invokeType;
382     }
383 
384     // Ensure access of the referenced symbol(s).
ensureAccessibility()385     abstract boolean ensureAccessibility();
386 
definitionFor(DexType type)387     DexClass definitionFor(DexType type) {
388       return rewriter.converter.appInfo.app.definitionFor(type);
389     }
390 
programDefinitionFor(DexType type)391     DexProgramClass programDefinitionFor(DexType type) {
392       return rewriter.converter.appInfo.app.programDefinitionFor(type);
393     }
394   }
395 
396   // Used for targeting methods referenced directly without creating accessors.
397   private final class NoAccessorMethodTarget extends Target {
398 
NoAccessorMethodTarget(Invoke.Type invokeType)399     NoAccessorMethodTarget(Invoke.Type invokeType) {
400       super(descriptor.implHandle.asMethod(), invokeType);
401     }
402 
403     @Override
ensureAccessibility()404     boolean ensureAccessibility() {
405       return true;
406     }
407   }
408 
409   // Used for static private lambda$ methods. Only needs access relaxation.
410   private final class StaticLambdaImplTarget extends Target {
411 
StaticLambdaImplTarget()412     StaticLambdaImplTarget() {
413       super(descriptor.implHandle.asMethod(), Invoke.Type.STATIC);
414     }
415 
416     @Override
ensureAccessibility()417     boolean ensureAccessibility() {
418       // We already found the static method to be called, just relax its accessibility.
419       assert descriptor.getAccessibility() != null;
420       descriptor.getAccessibility().unsetPrivate();
421       DexClass implMethodHolder = definitionFor(descriptor.implHandle.asMethod().holder);
422       if (implMethodHolder.isInterface()) {
423         descriptor.getAccessibility().setPublic();
424       }
425       return true;
426     }
427   }
428 
429   // Used for instance private lambda$ methods. Needs to be converted to
430   // a package-private static method.
431   private class InstanceLambdaImplTarget extends Target {
432 
InstanceLambdaImplTarget(DexMethod staticMethod)433     InstanceLambdaImplTarget(DexMethod staticMethod) {
434       super(staticMethod, Invoke.Type.STATIC);
435     }
436 
437     @Override
ensureAccessibility()438     boolean ensureAccessibility() {
439       // For all instantiation points for which compiler creates lambda$
440       // methods, it creates these methods in the same class/interface.
441       DexMethod implMethod = descriptor.implHandle.asMethod();
442       DexClass implMethodHolder = definitionFor(implMethod.holder);
443 
444       DexEncodedMethod[] directMethods = implMethodHolder.directMethods;
445       for (int i = 0; i < directMethods.length; i++) {
446         DexEncodedMethod encodedMethod = directMethods[i];
447         if (implMethod.match(encodedMethod)) {
448           // We need to create a new static method with the same code to be able to safely
449           // relax its accessibility without making it virtual.
450           DexEncodedMethod newMethod = new DexEncodedMethod(
451               callTarget, encodedMethod.accessFlags, encodedMethod.annotations,
452               encodedMethod.parameterAnnotations, encodedMethod.getCode());
453           // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
454           encodedMethod.accessFlags.setStatic();
455           encodedMethod.accessFlags.unsetPrivate();
456           if (implMethodHolder.isInterface()) {
457             encodedMethod.accessFlags.setPublic();
458           }
459           DexCode dexCode = newMethod.getCode().asDexCode();
460           dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
461           assert (dexCode.getDebugInfo() == null)
462               || (callTarget.proto.parameters.values.length
463               == dexCode.getDebugInfo().parameters.length);
464           directMethods[i] = newMethod;
465           return true;
466         }
467       }
468       return false;
469     }
470   }
471 
472   // Used for instance/static methods or constructors accessed via
473   // synthesized accessor method. Needs accessor method to be created.
474   private class ClassMethodWithAccessorTarget extends Target {
475 
ClassMethodWithAccessorTarget(DexMethod accessorMethod)476     ClassMethodWithAccessorTarget(DexMethod accessorMethod) {
477       super(accessorMethod, Invoke.Type.STATIC);
478     }
479 
480     @Override
ensureAccessibility()481     boolean ensureAccessibility() {
482       // Create a static accessor with proper accessibility.
483       DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
484       assert accessorClass != null;
485 
486       DexAccessFlags accessorFlags = new DexAccessFlags(
487           Constants.ACC_SYNTHETIC | Constants.ACC_STATIC |
488               (accessorClass.isInterface() ? Constants.ACC_PUBLIC : 0));
489       DexEncodedMethod accessorEncodedMethod = new DexEncodedMethod(
490           callTarget, accessorFlags, DexAnnotationSet.empty(), DexAnnotationSetRefList.empty(),
491           new SynthesizedCode(new AccessorMethodSourceCode(LambdaClass.this)));
492       accessorClass.directMethods = appendMethod(
493           accessorClass.directMethods, accessorEncodedMethod);
494       rewriter.converter.optimizeSynthesizedMethod(accessorEncodedMethod);
495       return true;
496     }
497 
appendMethod(DexEncodedMethod[] methods, DexEncodedMethod method)498     private DexEncodedMethod[] appendMethod(DexEncodedMethod[] methods, DexEncodedMethod method) {
499       int size = methods.length;
500       DexEncodedMethod[] newMethods = new DexEncodedMethod[size + 1];
501       System.arraycopy(methods, 0, newMethods, 0, size);
502       newMethods[size] = method;
503       return newMethods;
504     }
505   }
506 }
507