• 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.errors.Unreachable;
8 import com.android.tools.r8.graph.AppInfo;
9 import com.android.tools.r8.graph.DexAccessFlags;
10 import com.android.tools.r8.graph.DexCallSite;
11 import com.android.tools.r8.graph.DexEncodedMethod;
12 import com.android.tools.r8.graph.DexItemFactory;
13 import com.android.tools.r8.graph.DexMethod;
14 import com.android.tools.r8.graph.DexMethodHandle;
15 import com.android.tools.r8.graph.DexProto;
16 import com.android.tools.r8.graph.DexString;
17 import com.android.tools.r8.graph.DexType;
18 import com.android.tools.r8.graph.DexTypeList;
19 import com.android.tools.r8.graph.DexValue;
20 import com.google.common.collect.Sets;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Set;
24 
25 // Represents the lambda descriptor inferred from calls site.
26 final class LambdaDescriptor {
27   private static final int LAMBDA_ALT_SERIALIZABLE = 1;
28   private static final int LAMBDA_ALT_HAS_EXTRA_INTERFACES = 2;
29   private static final int LAMBDA_ALT_HAS_BRIDGES = 4;
30   private static final int LAMBDA_ALT_MASK = LAMBDA_ALT_SERIALIZABLE
31       | LAMBDA_ALT_HAS_EXTRA_INTERFACES | LAMBDA_ALT_HAS_BRIDGES;
32 
33   static final LambdaDescriptor MATCH_FAILED = new LambdaDescriptor();
34 
35   final String uniqueId;
36   final DexString name;
37   final DexProto erasedProto;
38   final DexProto enforcedProto;
39   final DexMethodHandle implHandle;
40 
41   final List<DexType> interfaces = new ArrayList<>();
42   final Set<DexProto> bridges = Sets.newIdentityHashSet();
43   final DexTypeList captures;
44 
45   // Used for accessibility analysis and few assertions only.
46   private final DexEncodedMethod targetMethod;
47 
LambdaDescriptor()48   private LambdaDescriptor() {
49     uniqueId = null;
50     name = null;
51     erasedProto = null;
52     enforcedProto = null;
53     implHandle = null;
54     captures = null;
55     targetMethod = null;
56   }
57 
LambdaDescriptor(LambdaRewriter rewriter, DexCallSite callSite, DexString name, DexProto erasedProto, DexProto enforcedProto, DexMethodHandle implHandle, DexType mainInterface, DexTypeList captures)58   private LambdaDescriptor(LambdaRewriter rewriter, DexCallSite callSite,
59       DexString name, DexProto erasedProto, DexProto enforcedProto,
60       DexMethodHandle implHandle, DexType mainInterface, DexTypeList captures) {
61     assert rewriter != null;
62     assert callSite != null;
63     assert name != null;
64     assert erasedProto != null;
65     assert enforcedProto != null;
66     assert implHandle != null;
67     assert mainInterface != null;
68     assert captures != null;
69 
70     this.uniqueId = callSite.getHash();
71     this.name = name;
72     this.erasedProto = erasedProto;
73     this.enforcedProto = enforcedProto;
74     this.implHandle = implHandle;
75     this.captures = captures;
76 
77     this.interfaces.add(mainInterface);
78     this.targetMethod = lookupTargetMethod(rewriter);
79   }
80 
getImplReceiverType()81   final DexType getImplReceiverType() {
82     // The receiver of instance impl-method is captured as the first captured
83     // value or should be the first argument of the enforced method signature.
84     DexType[] params = enforcedProto.parameters.values;
85     DexType[] captures = this.captures.values;
86     assert captures.length > 0 || params.length > 0;
87     return captures.length > 0 ? captures[0] : params[0];
88   }
89 
lookupTargetMethod(LambdaRewriter rewriter)90   private DexEncodedMethod lookupTargetMethod(LambdaRewriter rewriter) {
91     // Find the lambda's impl-method target.
92     DexMethod method = implHandle.asMethod();
93     switch (implHandle.type) {
94       case INVOKE_INSTANCE: {
95         AppInfo appInfo = rewriter.converter.appInfo;
96         DexEncodedMethod target = appInfo.lookupVirtualTarget(getImplReceiverType(), method);
97         if (target == null) {
98           target = appInfo.lookupDirectTarget(method);
99         }
100         assert target == null ||
101             (!target.accessFlags.isConstructor() && !target.accessFlags.isStatic());
102         return target;
103       }
104 
105       case INVOKE_STATIC: {
106         AppInfo appInfo = rewriter.converter.appInfo;
107         DexEncodedMethod target = appInfo.lookupStaticTarget(method);
108         assert target == null || target.accessFlags.isStatic();
109         return target;
110       }
111 
112       case INVOKE_CONSTRUCTOR: {
113         AppInfo appInfo = rewriter.converter.appInfo;
114         DexEncodedMethod target = appInfo.lookupDirectTarget(method);
115         assert target == null || target.accessFlags.isConstructor();
116         return target;
117       }
118 
119       case INVOKE_INTERFACE: {
120         AppInfo appInfo = rewriter.converter.appInfo;
121         DexEncodedMethod target = appInfo.lookupVirtualTarget(getImplReceiverType(), method);
122         assert target == null ||
123             (!target.accessFlags.isConstructor() && !target.accessFlags.isStatic());
124         return target;
125       }
126 
127       default:
128         throw new Unreachable("Unexpected method handle kind in " + implHandle);
129     }
130   }
131 
getAccessibility()132   final DexAccessFlags getAccessibility() {
133     return targetMethod == null ? null : targetMethod.accessFlags;
134   }
135 
targetFoundInClass(DexType type)136   final boolean targetFoundInClass(DexType type) {
137     return targetMethod != null && targetMethod.method.holder == type;
138   }
139 
140   /** If the lambda delegates to lambda$ method. */
delegatesToLambdaImplMethod()141   boolean delegatesToLambdaImplMethod() {
142     DexString methodName = implHandle.asMethod().name;
143     return methodName.toString().startsWith(LambdaRewriter.EXPECTED_LAMBDA_METHOD_PREFIX);
144   }
145 
146   /** Is a stateless lambda, i.e. lambda does not capture any values */
isStateless()147   final boolean isStateless() {
148     return captures.isEmpty();
149   }
150 
151   /** Checks if call site needs a accessor when referenced from `accessedFrom`. */
needsAccessor(DexType accessedFrom)152   boolean needsAccessor(DexType accessedFrom) {
153     if (delegatesToLambdaImplMethod()) {
154       return false;
155     }
156 
157     if (implHandle.type.isInvokeInterface()) {
158       // Interface methods must be public.
159       return false;
160     }
161 
162     boolean staticTarget = implHandle.type.isInvokeStatic();
163     boolean instanceTarget = implHandle.type.isInvokeInstance();
164     boolean initTarget = implHandle.type.isInvokeConstructor();
165     assert instanceTarget || staticTarget | initTarget;
166 
167     if (targetMethod == null) {
168       // The target cannot be a private method, since otherwise it
169       // should have been found.
170 
171       if (staticTarget || initTarget) {
172         // Create accessor only in case it is accessed from other
173         // package, since otherwise it can be called directly.
174         // NOTE: This case is different from regular instance method case
175         // because the method being called must be present in method holder,
176         // and not in one from its supertypes.
177         boolean accessedFromSamePackage =
178             accessedFrom.getPackageDescriptor().equals(
179                 implHandle.asMethod().holder.getPackageDescriptor());
180         return !accessedFromSamePackage;
181       }
182 
183       // Since instance method was not found, always generate an accessor
184       // since it may be a protected method located in another package.
185       return true;
186     }
187 
188     DexAccessFlags flags = targetMethod.accessFlags;
189 
190     // Private methods always need accessors.
191     if (flags.isPrivate()) {
192       return true;
193     }
194     if (flags.isPublic()) {
195       return false;
196     }
197 
198     boolean accessedFromSamePackage =
199         accessedFrom.getPackageDescriptor().equals(
200             targetMethod.method.holder.getPackageDescriptor());
201     assert flags.isProtected() || accessedFromSamePackage;
202     return flags.isProtected() && !accessedFromSamePackage;
203   }
204 
205   /**
206    * Matches call site for lambda metafactory invocation pattern and
207    * returns extracted match information, or null if match failed.
208    */
infer(LambdaRewriter rewriter, DexCallSite callSite)209   static LambdaDescriptor infer(LambdaRewriter rewriter, DexCallSite callSite) {
210     // We expect bootstrap method to be either `metafactory` or `altMetafactory` method
211     // of `java.lang.invoke.LambdaMetafactory` class. Both methods are static.
212     if (!callSite.bootstrapMethod.type.isInvokeStatic()) {
213       return LambdaDescriptor.MATCH_FAILED;
214     }
215 
216     DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
217     boolean isMetafactoryMethod = bootstrapMethod == rewriter.metafactoryMethod;
218     boolean isAltMetafactoryMethod = bootstrapMethod == rewriter.metafactoryAltMethod;
219     if (!isMetafactoryMethod && !isAltMetafactoryMethod) {
220       // It is not a lambda, thus no need to manage this call site.
221       return LambdaDescriptor.MATCH_FAILED;
222     }
223 
224     // 'Method name' operand of the invoke-custom instruction represents
225     // the name of the functional interface main method.
226     DexString funcMethodName = callSite.methodName;
227 
228     // Signature of main functional interface method.
229     DexValue.DexValueMethodType funcErasedSignature =
230         getBootstrapArgument(callSite, 0, DexValue.DexValueMethodType.class);
231 
232     // Method handle of the implementation method.
233     DexMethodHandle lambdaImplMethodHandle =
234         getBootstrapArgument(callSite, 1, DexValue.DexValueMethodHandle.class).value;
235     // Even though there are some limitations on which method handle kinds are
236     // allowed for lambda impl-methods, there is no way to detect unsupported
237     // handle kinds after they are transformed into DEX method handle.
238 
239     // Signature to be enforced on main method.
240     DexValue.DexValueMethodType funcEnforcedSignature =
241         getBootstrapArgument(callSite, 2, DexValue.DexValueMethodType.class);
242     if (!isEnforcedSignatureValid(
243         rewriter, funcEnforcedSignature.value, funcErasedSignature.value)) {
244       throw new Unreachable(
245           "Enforced and erased signatures are inconsistent in " + callSite.toString());
246     }
247 
248     // 'Method type' of the invoke-custom instruction represents the signature
249     // of the lambda method factory.
250     DexProto lambdaFactoryProto = callSite.methodProto;
251     // Main functional interface is the return type of the lambda factory method.
252     DexType mainFuncInterface = lambdaFactoryProto.returnType;
253     // Lambda captures are represented as parameters of the lambda factory method.
254     DexTypeList captures = lambdaFactoryProto.parameters;
255 
256     // Create a match.
257     LambdaDescriptor match = new LambdaDescriptor(rewriter, callSite,
258         funcMethodName, funcErasedSignature.value, funcEnforcedSignature.value,
259         lambdaImplMethodHandle, mainFuncInterface, captures);
260 
261     if (isMetafactoryMethod) {
262       if (callSite.bootstrapArgs.size() != 3) {
263         throw new Unreachable(
264             "Unexpected number of metafactory method arguments in " + callSite.toString());
265       }
266     } else {
267       extractExtraLambdaInfo(rewriter, callSite, match);
268     }
269 
270     return match;
271   }
272 
extractExtraLambdaInfo( LambdaRewriter rewriter, DexCallSite callSite, LambdaDescriptor match)273   private static void extractExtraLambdaInfo(
274       LambdaRewriter rewriter, DexCallSite callSite, LambdaDescriptor match) {
275     int argIndex = 3;
276     int flagsArg = getBootstrapArgument(
277         callSite, argIndex++, DexValue.DexValueInt.class).value;
278     assert (flagsArg & ~LAMBDA_ALT_MASK) == 0;
279 
280     // Load extra interfaces if any.
281     if ((flagsArg & LAMBDA_ALT_HAS_EXTRA_INTERFACES) != 0) {
282       int count = getBootstrapArgument(
283           callSite, argIndex++, DexValue.DexValueInt.class).value;
284       for (int i = 0; i < count; i++) {
285         DexType type = getBootstrapArgument(
286             callSite, argIndex++, DexValue.DexValueType.class).value;
287         if (!match.interfaces.contains(type)) {
288           match.interfaces.add(type);
289         }
290       }
291     }
292 
293     // If the lambda is serializable, add it.
294     if ((flagsArg & LAMBDA_ALT_SERIALIZABLE) != 0) {
295       if (!match.interfaces.contains(rewriter.serializableType)) {
296         match.interfaces.add(rewriter.serializableType);
297       }
298     }
299 
300     // Load bridges if any.
301     if ((flagsArg & LAMBDA_ALT_HAS_BRIDGES) != 0) {
302       int count = getBootstrapArgument(
303           callSite, argIndex++, DexValue.DexValueInt.class).value;
304       for (int i = 0; i < count; i++) {
305         DexProto bridgeProto = getBootstrapArgument(
306             callSite, argIndex++, DexValue.DexValueMethodType.class).value;
307         match.bridges.add(bridgeProto);
308       }
309     }
310 
311     if (callSite.bootstrapArgs.size() != argIndex) {
312       throw new Unreachable(
313           "Unexpected number of metafactory method arguments in " + callSite.toString());
314     }
315   }
316 
317   @SuppressWarnings("unchecked")
getBootstrapArgument(DexCallSite callSite, int i, Class<T> clazz)318   private static <T> T getBootstrapArgument(DexCallSite callSite, int i, Class<T> clazz) {
319     List<DexValue> bootstrapArgs = callSite.bootstrapArgs;
320     if (bootstrapArgs.size() < i) {
321       throw new Unreachable("Expected to find at least "
322           + i + " bootstrap arguments in " + callSite.toString());
323     }
324     DexValue value = bootstrapArgs.get(i);
325     if (!clazz.isAssignableFrom(value.getClass())) {
326       throw new Unreachable("Unexpected type of "
327           + "bootstrap arguments #" + i + " in " + callSite.toString());
328     }
329     return (T) value;
330   }
331 
isEnforcedSignatureValid( LambdaRewriter rewriter, DexProto enforced, DexProto erased)332   private static boolean isEnforcedSignatureValid(
333       LambdaRewriter rewriter, DexProto enforced, DexProto erased) {
334     if (!isSameOrDerived(rewriter.factory, enforced.returnType, erased.returnType)) {
335       return false;
336     }
337     DexType[] enforcedValues = enforced.parameters.values;
338     DexType[] erasedValues = erased.parameters.values;
339     int count = enforcedValues.length;
340     if (count != erasedValues.length) {
341       return false;
342     }
343     for (int i = 0; i < count; i++) {
344       if (!isSameOrDerived(rewriter.factory, enforcedValues[i], erasedValues[i])) {
345         return false;
346       }
347     }
348     return true;
349   }
350 
351   // Checks if the types are the same OR both types are reference types and
352   // `subType` is derived from `b`. Note that in the latter case we only check if
353   // both types are class types, for the reasons mentioned in isSameOrAdaptableTo(...).
isSameOrDerived( DexItemFactory factory, DexType subType, DexType superType)354   static boolean isSameOrDerived(
355       DexItemFactory factory, DexType subType, DexType superType) {
356     if (subType == superType || (subType.isClassType() && superType.isClassType())) {
357       return true;
358     }
359 
360     if (subType.isArrayType()) {
361       if (superType.isArrayType()) {
362         // X[] -> Y[].
363         return isSameOrDerived(factory,
364             subType.toArrayElementType(factory), superType.toArrayElementType(factory));
365       }
366       return superType == factory.objectType; // T[] -> Object.
367     }
368 
369     return false;
370   }
371 }
372