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