LambdaClass.java revision 52c0414fff5db9769a60a3e2880acd8781a88ab1
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
5package com.android.tools.r8.ir.desugar;
6
7import com.android.tools.r8.dex.Constants;
8import com.android.tools.r8.errors.Unimplemented;
9import com.android.tools.r8.errors.Unreachable;
10import com.android.tools.r8.graph.DexAccessFlags;
11import com.android.tools.r8.graph.DexAnnotationSet;
12import com.android.tools.r8.graph.DexAnnotationSetRefList;
13import com.android.tools.r8.graph.DexClass;
14import com.android.tools.r8.graph.DexCode;
15import com.android.tools.r8.graph.DexEncodedField;
16import com.android.tools.r8.graph.DexEncodedMethod;
17import com.android.tools.r8.graph.DexField;
18import com.android.tools.r8.graph.DexItemFactory;
19import com.android.tools.r8.graph.DexMethod;
20import com.android.tools.r8.graph.DexMethodHandle;
21import com.android.tools.r8.graph.DexProgramClass;
22import com.android.tools.r8.graph.DexProto;
23import com.android.tools.r8.graph.DexString;
24import com.android.tools.r8.graph.DexType;
25import com.android.tools.r8.graph.DexTypeList;
26import com.android.tools.r8.graph.DexValue;
27import com.android.tools.r8.ir.code.Invoke;
28import com.android.tools.r8.ir.synthetic.SynthesizedCode;
29import java.util.List;
30import 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 */
50final 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
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.
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
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
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
133  final boolean isStateless() {
134    return descriptor.isStateless();
135  }
136
137  // Synthesize virtual methods.
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.
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.
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.
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        DexValue.NULL);
223    return fields;
224  }
225
226  // Build a list of implemented interfaces.
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.
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
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.
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.
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.
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.
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
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
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).
385    abstract boolean ensureAccessibility();
386
387    DexClass definitionFor(DexType type) {
388      return rewriter.converter.appInfo.app.definitionFor(type);
389    }
390
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
399    NoAccessorMethodTarget(Invoke.Type invokeType) {
400      super(descriptor.implHandle.asMethod(), invokeType);
401    }
402
403    @Override
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
412    StaticLambdaImplTarget() {
413      super(descriptor.implHandle.asMethod(), Invoke.Type.STATIC);
414    }
415
416    @Override
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
433    InstanceLambdaImplTarget(DexMethod staticMethod) {
434      super(staticMethod, Invoke.Type.STATIC);
435    }
436
437    @Override
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
476    ClassMethodWithAccessorTarget(DexMethod accessorMethod) {
477      super(accessorMethod, Invoke.Type.STATIC);
478    }
479
480    @Override
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
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