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