• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 The Bazel Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //    http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 package com.google.devtools.build.android.desugar;
15 
16 import static com.google.common.base.Preconditions.checkArgument;
17 import static com.google.common.base.Preconditions.checkNotNull;
18 import static com.google.common.base.Preconditions.checkState;
19 
20 import com.google.common.collect.ImmutableList;
21 import com.google.common.collect.ImmutableSet;
22 import java.util.HashSet;
23 import java.util.LinkedHashSet;
24 import org.objectweb.asm.AnnotationVisitor;
25 import org.objectweb.asm.ClassReader;
26 import org.objectweb.asm.ClassVisitor;
27 import org.objectweb.asm.FieldVisitor;
28 import org.objectweb.asm.MethodVisitor;
29 import org.objectweb.asm.Opcodes;
30 import org.objectweb.asm.tree.AbstractInsnNode;
31 import org.objectweb.asm.tree.MethodNode;
32 import org.objectweb.asm.tree.TypeInsnNode;
33 
34 /**
35  * Visitor intended to fix up lambda classes to match assumptions made in {@link LambdaDesugaring}.
36  * Specifically this includes fixing visibilities and generating any missing factory methods.
37  *
38  * <p>Each instance can only visit one class.  This is because the signature of the needed factory
39  * method is passed into the constructor.
40  */
41 class LambdaClassFixer extends ClassVisitor {
42 
43   /** Magic method name used by {@link java.lang.invoke.LambdaMetafactory}. */
44   public static final String FACTORY_METHOD_NAME = "get$Lambda";
45   /** Field name we'll use to hold singleton instances where possible. */
46   public static final String SINGLETON_FIELD_NAME = "$instance";
47 
48   private final LambdaInfo lambdaInfo;
49   private final ClassReaderFactory factory;
50   private final ImmutableSet<String> interfaceLambdaMethods;
51   private final boolean allowDefaultMethods;
52   private final boolean copyBridgeMethods;
53   private final HashSet<String> implementedMethods = new HashSet<>();
54   private final LinkedHashSet<String> methodsToMoveIn = new LinkedHashSet<>();
55 
56   private String originalInternalName;
57   private ImmutableList<String> interfaces;
58 
59   private boolean hasState;
60   private boolean hasFactory;
61 
62   private String desc;
63   private String signature;
64 
LambdaClassFixer(ClassVisitor dest, LambdaInfo lambdaInfo, ClassReaderFactory factory, ImmutableSet<String> interfaceLambdaMethods, boolean allowDefaultMethods, boolean copyBridgeMethods)65   public LambdaClassFixer(ClassVisitor dest, LambdaInfo lambdaInfo, ClassReaderFactory factory,
66       ImmutableSet<String> interfaceLambdaMethods, boolean allowDefaultMethods,
67       boolean copyBridgeMethods) {
68     super(Opcodes.ASM5, dest);
69     checkArgument(!allowDefaultMethods || interfaceLambdaMethods.isEmpty());
70     checkArgument(allowDefaultMethods || copyBridgeMethods);
71     this.lambdaInfo = lambdaInfo;
72     this.factory = factory;
73     this.interfaceLambdaMethods = interfaceLambdaMethods;
74     this.allowDefaultMethods = allowDefaultMethods;
75     this.copyBridgeMethods = copyBridgeMethods;
76   }
77 
78   @Override
visit( int version, int access, String name, String signature, String superName, String[] interfaces)79   public void visit(
80       int version,
81       int access,
82       String name,
83       String signature,
84       String superName,
85       String[] interfaces) {
86     checkArgument(BitFlags.noneSet(access, Opcodes.ACC_INTERFACE), "Not a class: %s", name);
87     checkState(this.originalInternalName == null, "not intended for reuse but reused for %s", name);
88     originalInternalName = name;
89     hasState = false;
90     hasFactory = false;
91     desc = null;
92     this.signature = null;
93     this.interfaces = ImmutableList.copyOf(interfaces);
94     // Rename to desired name
95     super.visit(version, access, getInternalName(), signature, superName, interfaces);
96   }
97 
98   @Override
visitField( int access, String name, String desc, String signature, Object value)99   public FieldVisitor visitField(
100       int access, String name, String desc, String signature, Object value) {
101     hasState = true;
102     return super.visitField(access, name, desc, signature, value);
103   }
104 
105   @Override
visitMethod( int access, String name, String desc, String signature, String[] exceptions)106   public MethodVisitor visitMethod(
107       int access, String name, String desc, String signature, String[] exceptions) {
108     if (name.equals("writeReplace") && BitFlags.noneSet(access, Opcodes.ACC_STATIC)
109         && desc.equals("()Ljava/lang/Object;")) {
110       // Lambda serialization hooks use java/lang/invoke/SerializedLambda, which isn't available on
111       // Android. Since Jack doesn't do anything special for serializable lambdas we just drop these
112       // serialization hooks.
113       // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/output.html#a5324 gives
114       // details on the role and signature of this method.
115       return null;
116     }
117     if (BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC)) {
118       // Keep track of instance methods implemented in this class for later.  Since this visitor
119       // is intended for lambda classes, no need to look at the superclass.
120       implementedMethods.add(name + ":" + desc);
121     }
122     if (FACTORY_METHOD_NAME.equals(name)) {
123       hasFactory = true;
124       if (!lambdaInfo.needFactory()) {
125         return null; // drop generated factory method if we won't call it
126       }
127       access &= ~Opcodes.ACC_PRIVATE; // make factory method accessible
128     } else if ("<init>".equals(name)) {
129       this.desc = desc;
130       this.signature = signature;
131       if (!lambdaInfo.needFactory() && !desc.startsWith("()")) {
132         access &= ~Opcodes.ACC_PRIVATE; // make constructor accessible if we'll call it directly
133       }
134     }
135     MethodVisitor methodVisitor =
136         new LambdaClassMethodRewriter(super.visitMethod(access, name, desc, signature, exceptions));
137     if (!lambdaInfo.bridgeMethod().equals(lambdaInfo.methodReference())) {
138       // Skip UseBridgeMethod unless we actually need it
139       methodVisitor =
140           new UseBridgeMethod(methodVisitor, lambdaInfo, access, name, desc, signature, exceptions);
141     }
142     if (!FACTORY_METHOD_NAME.equals(name) && !"<init>".equals(name)) {
143       methodVisitor = new LambdaClassInvokeSpecialRewriter(methodVisitor);
144     }
145     return methodVisitor;
146   }
147 
148   @Override
visitEnd()149   public void visitEnd() {
150     checkState(!hasState || hasFactory,
151         "Expected factory method for capturing lambda %s", getInternalName());
152     if (!hasState) {
153       checkState(signature == null,
154           "Didn't expect generic constructor signature %s %s", getInternalName(), signature);
155       checkState(lambdaInfo.factoryMethodDesc().startsWith("()"),
156           "Expected 0-arg factory method for %s but found %s", getInternalName(),
157           lambdaInfo.factoryMethodDesc());
158       // Since this is a stateless class we populate and use a static singleton field "$instance".
159       // Field is package-private so we can read it from the class that had the invokedynamic.
160       String singletonFieldDesc = lambdaInfo.factoryMethodDesc().substring("()".length());
161       super.visitField(
162           Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
163           SINGLETON_FIELD_NAME,
164           singletonFieldDesc,
165           (String) null,
166           (Object) null)
167           .visitEnd();
168 
169       MethodVisitor codeBuilder =
170           super.visitMethod(
171               Opcodes.ACC_STATIC,
172               "<clinit>",
173               "()V",
174               (String) null,
175               new String[0]);
176       codeBuilder.visitTypeInsn(Opcodes.NEW, getInternalName());
177       codeBuilder.visitInsn(Opcodes.DUP);
178       codeBuilder.visitMethodInsn(Opcodes.INVOKESPECIAL, getInternalName(), "<init>",
179           checkNotNull(desc, "didn't see a constructor for %s", getInternalName()), /*itf*/ false);
180       codeBuilder.visitFieldInsn(
181           Opcodes.PUTSTATIC, getInternalName(), SINGLETON_FIELD_NAME, singletonFieldDesc);
182       codeBuilder.visitInsn(Opcodes.RETURN);
183       codeBuilder.visitMaxs(2, 0); // two values are pushed onto the stack
184       codeBuilder.visitEnd();
185     }
186 
187     copyRewrittenLambdaMethods();
188     if (copyBridgeMethods) {
189       copyBridgeMethods(interfaces);
190     }
191     super.visitEnd();
192   }
193 
getInternalName()194   private String getInternalName() {
195     return lambdaInfo.desiredInternalName();
196   }
197 
copyRewrittenLambdaMethods()198   private void copyRewrittenLambdaMethods() {
199     for (String rewritten : methodsToMoveIn) {
200       String interfaceInternalName = rewritten.substring(0, rewritten.indexOf('#'));
201       String methodName = rewritten.substring(interfaceInternalName.length() + 1);
202       ClassReader bytecode = checkNotNull(factory.readIfKnown(interfaceInternalName),
203           "Couldn't load interface with lambda method %s", rewritten);
204       CopyOneMethod copier = new CopyOneMethod(methodName);
205       // TODO(kmb): Set source file attribute for lambda classes so lambda debug info makes sense
206       bytecode.accept(copier, ClassReader.SKIP_DEBUG);
207       checkState(copier.copied(), "Didn't find %s", rewritten);
208     }
209   }
210 
copyBridgeMethods(ImmutableList<String> interfaces)211   private void copyBridgeMethods(ImmutableList<String> interfaces) {
212     for (String implemented : interfaces) {
213       ClassReader bytecode = factory.readIfKnown(implemented);
214       if (bytecode != null) {
215         // Don't copy line numbers and local variable tables.  They would be misleading or wrong
216         // and other methods in generated lambda classes don't have debug info either.
217         bytecode.accept(new CopyBridgeMethods(), ClassReader.SKIP_DEBUG);
218       } // else the interface is defined in a different Jar, which we can ignore here
219     }
220   }
221 
222   /**
223    * Rewriter for methods in generated lambda classes.
224    */
225   private class LambdaClassMethodRewriter extends MethodVisitor {
LambdaClassMethodRewriter(MethodVisitor dest)226     public LambdaClassMethodRewriter(MethodVisitor dest) {
227       super(Opcodes.ASM5, dest);
228     }
229 
230     @Override
visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)231     public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
232       String method = owner + "#" + name;
233       if (interfaceLambdaMethods.contains(method)) {
234         // Rewrite invocations of lambda methods in interfaces to anticipate the lambda method being
235         // moved into the lambda class (i.e., the class being visited here).
236         checkArgument(opcode == Opcodes.INVOKESTATIC, "Cannot move instance method %s", method);
237         owner = getInternalName();
238         itf = false; // owner was interface but is now a class
239         methodsToMoveIn.add(method);
240       } else if (originalInternalName.equals(owner)) {
241         // Reflect renaming of lambda classes
242         owner = getInternalName();
243       }
244 
245       if (name.startsWith("lambda$")) {
246         // Reflect renaming of lambda$ instance methods in LambdaDesugaring.  Do this even if we'll
247         // move the method into the lambda class we're processing so the renaming done in
248         // LambdaDesugaring doesn't kick in if the class were desugared a second time.
249         name = LambdaDesugaring.uniqueInPackage(owner, name);
250       }
251       super.visitMethodInsn(opcode, owner, name, desc, itf);
252     }
253 
254     @Override
visitTypeInsn(int opcode, String type)255     public void visitTypeInsn(int opcode, String type) {
256       if (originalInternalName.equals(type)) {
257         // Reflect renaming of lambda classes
258         type = getInternalName();
259       }
260       super.visitTypeInsn(opcode, type);
261     }
262 
263     @Override
visitFieldInsn(int opcode, String owner, String name, String desc)264     public void visitFieldInsn(int opcode, String owner, String name, String desc) {
265       if (originalInternalName.equals(owner)) {
266         // Reflect renaming of lambda classes
267         owner = getInternalName();
268       }
269       super.visitFieldInsn(opcode, owner, name, desc);
270     }
271 
272     @Override
visitAnnotation(String desc, boolean visible)273     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
274       // Drop annotation that's part of the generated lambda class that's not available on Android.
275       // Proguard complains about this otherwise.
276       if ("Ljava/lang/invoke/LambdaForm$Hidden;".equals(desc)) {
277         return null;
278       }
279       return super.visitAnnotation(desc, visible);
280     }
281   }
282 
283   /** Rewriter for invokespecial in generated lambda classes. */
284   private static class LambdaClassInvokeSpecialRewriter extends MethodVisitor {
285 
LambdaClassInvokeSpecialRewriter(MethodVisitor dest)286     public LambdaClassInvokeSpecialRewriter(MethodVisitor dest) {
287       super(Opcodes.ASM5, dest);
288     }
289 
290     @Override
visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)291     public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
292       if (opcode == Opcodes.INVOKESPECIAL && name.startsWith("lambda$")) {
293         opcode = itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL;
294       }
295 
296       super.visitMethodInsn(opcode, owner, name, desc, itf);
297     }
298   }
299 
300   /**
301    * Visitor that copies bridge methods from the visited interface into the class visited by the
302    * surrounding {@link LambdaClassFixer}. Descends recursively into interfaces extended by the
303    * visited interface.
304    */
305   private class CopyBridgeMethods extends ClassVisitor {
306 
307     @SuppressWarnings("hiding") private ImmutableList<String> interfaces;
308 
CopyBridgeMethods()309     public CopyBridgeMethods() {
310       // No delegate visitor; instead we'll add methods to the outer class's delegate where needed
311       super(Opcodes.ASM5);
312     }
313 
314     @Override
visit( int version, int access, String name, String signature, String superName, String[] interfaces)315     public void visit(
316         int version,
317         int access,
318         String name,
319         String signature,
320         String superName,
321         String[] interfaces) {
322       checkArgument(BitFlags.isSet(access, Opcodes.ACC_INTERFACE));
323       checkState(this.interfaces == null);
324       this.interfaces = ImmutableList.copyOf(interfaces);
325     }
326 
327     @Override
visitMethod( int access, String name, String desc, String signature, String[] exceptions)328     public MethodVisitor visitMethod(
329         int access, String name, String desc, String signature, String[] exceptions) {
330       if ((access & (Opcodes.ACC_BRIDGE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC))
331           == Opcodes.ACC_BRIDGE) {
332         // Only copy bridge methods--hand-written default methods are not supported--and only if
333         // we haven't seen the method already.
334         if (implementedMethods.add(name + ":" + desc)) {
335           MethodVisitor result =
336               LambdaClassFixer.super.visitMethod(access, name, desc, signature, exceptions);
337           return allowDefaultMethods ? result : new AvoidJacocoInit(result);
338         }
339       }
340       return null;
341     }
342 
343     @Override
visitEnd()344     public void visitEnd() {
345       copyBridgeMethods(this.interfaces);
346     }
347   }
348 
349   private class CopyOneMethod extends ClassVisitor {
350 
351     private final String methodName;
352     private int copied = 0;
353 
CopyOneMethod(String methodName)354     public CopyOneMethod(String methodName) {
355       // No delegate visitor; instead we'll add methods to the outer class's delegate where needed
356       super(Opcodes.ASM5);
357       checkState(!allowDefaultMethods, "Couldn't copy interface lambda bodies");
358       this.methodName = methodName;
359     }
360 
copied()361     public boolean copied() {
362       return copied > 0;
363     }
364 
365     @Override
visit( int version, int access, String name, String signature, String superName, String[] interfaces)366     public void visit(
367         int version,
368         int access,
369         String name,
370         String signature,
371         String superName,
372         String[] interfaces) {
373       checkArgument(BitFlags.isSet(access, Opcodes.ACC_INTERFACE));
374     }
375 
376     @Override
visitMethod( int access, String name, String desc, String signature, String[] exceptions)377     public MethodVisitor visitMethod(
378         int access, String name, String desc, String signature, String[] exceptions) {
379       if (name.equals(methodName)) {
380         checkState(copied == 0, "Found unexpected second method %s with descriptor %s", name, desc);
381         ++copied;
382         // Rename for consistency with what we do in LambdaClassMethodRewriter
383         name = LambdaDesugaring.uniqueInPackage(getInternalName(), name);
384         return new AvoidJacocoInit(
385             LambdaClassFixer.super.visitMethod(access, name, desc, signature, exceptions));
386       }
387       return null;
388     }
389   }
390 
391   /**
392    * Method visitor that rewrites {@code $jacocoInit()} calls to equivalent field accesses.
393    *
394    * <p>This class should only be used to visit interface methods and assumes that the code in
395    * {@code $jacocoInit()} is always executed in the interface's static initializer, which is the
396    * case in the absence of hand-written static or default interface methods (which
397    * {@link Java7Compatibility} makes sure of).
398    */
399   private static class AvoidJacocoInit extends MethodVisitor {
AvoidJacocoInit(MethodVisitor dest)400     public AvoidJacocoInit(MethodVisitor dest) {
401       super(Opcodes.ASM5, dest);
402     }
403 
404     @Override
visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)405     public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
406       if (opcode == Opcodes.INVOKESTATIC && "$jacocoInit".equals(name)) {
407         // Rewrite $jacocoInit() calls to just read the $jacocoData field
408         super.visitFieldInsn(Opcodes.GETSTATIC, owner, "$jacocoData", "[Z");
409       } else {
410         super.visitMethodInsn(opcode, owner, name, desc, itf);
411       }
412     }
413   }
414 
415   private static class UseBridgeMethod extends MethodNode {
416 
417     private final MethodVisitor dest;
418     private final LambdaInfo lambdaInfo;
419 
UseBridgeMethod(MethodVisitor dest, LambdaInfo lambdaInfo, int access, String name, String desc, String signature, String[] exceptions)420     public UseBridgeMethod(MethodVisitor dest, LambdaInfo lambdaInfo,
421         int access, String name, String desc, String signature, String[] exceptions) {
422       super(Opcodes.ASM5, access, name, desc, signature, exceptions);
423       this.dest = dest;
424       this.lambdaInfo = lambdaInfo;
425     }
426 
427     @Override
visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)428     public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
429       if (owner.equals(lambdaInfo.methodReference().getOwner())
430           && name.equals(lambdaInfo.methodReference().getName())
431           && desc.equals(lambdaInfo.methodReference().getDesc())) {
432         if (lambdaInfo.methodReference().getTag() == Opcodes.H_NEWINVOKESPECIAL
433             && lambdaInfo.bridgeMethod().getTag() != Opcodes.H_NEWINVOKESPECIAL) {
434           // We're changing a constructor call to a factory method call, so we unfortunately need
435           // to go find the NEW/DUP pair preceding the constructor call and remove it
436           removeLastAllocation();
437         }
438         super.visitMethodInsn(
439             LambdaDesugaring.invokeOpcode(lambdaInfo.bridgeMethod()),
440             lambdaInfo.bridgeMethod().getOwner(),
441             lambdaInfo.bridgeMethod().getName(),
442             lambdaInfo.bridgeMethod().getDesc(),
443             lambdaInfo.bridgeMethod().isInterface());
444 
445       } else {
446         super.visitMethodInsn(opcode, owner, name, desc, itf);
447       }
448     }
449 
removeLastAllocation()450     private void removeLastAllocation() {
451       AbstractInsnNode insn = instructions.getLast();
452       while (insn != null && insn.getPrevious() != null) {
453         AbstractInsnNode prev = insn.getPrevious();
454         if (prev.getOpcode() == Opcodes.NEW && insn.getOpcode() == Opcodes.DUP
455             && ((TypeInsnNode) prev).desc.equals(lambdaInfo.methodReference().getOwner())) {
456           instructions.remove(prev);
457           instructions.remove(insn);
458           return;
459         }
460         insn = prev;
461       }
462       throw new IllegalStateException(
463           "Couldn't find allocation to rewrite ::new reference " + lambdaInfo.methodReference());
464     }
465 
466     @Override
visitEnd()467     public void visitEnd() {
468       accept(dest);
469     }
470   }
471 }
472