1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.layoutlib.create; 18 19 import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 20 21 import org.objectweb.asm.AnnotationVisitor; 22 import org.objectweb.asm.Attribute; 23 import org.objectweb.asm.ClassReader; 24 import org.objectweb.asm.ClassVisitor; 25 import org.objectweb.asm.Handle; 26 import org.objectweb.asm.Label; 27 import org.objectweb.asm.MethodVisitor; 28 import org.objectweb.asm.Opcodes; 29 import org.objectweb.asm.Type; 30 import org.objectweb.asm.TypePath; 31 32 import java.util.ArrayList; 33 34 /** 35 * This method adapter generates delegate methods. 36 * <p/> 37 * Given a method {@code SomeClass.MethodName()}, this generates 1 or 2 methods: 38 * <ul> 39 * <li> A copy of the original method named {@code SomeClass.MethodName_Original()}. 40 * The content is the original method as-is from the reader. 41 * This step is omitted if the method is native, since it has no Java implementation. 42 * <li> A brand new implementation of {@code SomeClass.MethodName()} which calls to a 43 * non-existing method named {@code SomeClass_Delegate.MethodName()}. 44 * The implementation of this 'delegate' method is done in layoutlib_brigde. 45 * </ul> 46 * A method visitor is generally constructed to generate a single method; however 47 * here we might want to generate one or two depending on the context. To achieve 48 * that, the visitor here generates the 'original' method and acts as a no-op if 49 * no such method exists (e.g. when the original is a native method). 50 * The delegate method is generated after the {@code visitEnd} of the original method 51 * or by having the class adapter <em>directly</em> call {@link #generateDelegateCode()} 52 * for native methods. 53 * <p/> 54 * When generating the 'delegate', the implementation generates a call to a class 55 * class named <code><className>_Delegate</code> with static methods matching 56 * the methods to be overridden here. The methods have the same return type. 57 * The argument type list is the same except the "this" reference is passed first 58 * for non-static methods. 59 * <p/> 60 * A new annotation is added to these 'delegate' methods so that we can easily find them 61 * for automated testing. 62 * <p/> 63 * This class isn't intended to be generic or reusable. 64 * It is called by {@link DelegateClassAdapter}, which takes care of properly initializing 65 * the two method writers for the original and the delegate class, as needed, with their 66 * expected names. 67 * <p/> 68 * The class adapter also takes care of calling {@link #generateDelegateCode()} directly for 69 * a native and use the visitor pattern for non-natives. 70 * Note that native methods have, by definition, no code so there's nothing a visitor 71 * can visit. 72 * <p/> 73 * Instances of this class are not re-usable. 74 * The class adapter creates a new instance for each method. 75 */ 76 class DelegateMethodAdapter extends MethodVisitor { 77 78 /** Suffix added to delegate classes. */ 79 public static final String DELEGATE_SUFFIX = "_Delegate"; 80 81 /** The parent method writer to copy of the original method. 82 * Null when dealing with a native original method. */ 83 private MethodVisitor mOrgWriter; 84 /** The parent method writer to generate the delegating method. Never null. */ 85 private MethodVisitor mDelWriter; 86 /** The original method descriptor (return type + argument types.) */ 87 private String mDesc; 88 /** True if the original method is static. */ 89 private final boolean mIsStatic; 90 /** True if the method is contained in a static inner class */ 91 private final boolean mIsStaticInnerClass; 92 /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */ 93 private final String mClassName; 94 /** The corresponding delegate class name */ 95 private final String mDelegateClassName; 96 /** The method name. */ 97 private final String mMethodName; 98 /** Logger object. */ 99 private final Log mLog; 100 101 /** Array used to capture the first line number information from the original method 102 * and duplicate it in the delegate. */ 103 private Object[] mDelegateLineNumber; 104 105 /** 106 * Creates a new {@link DelegateMethodAdapter} that will transform this method 107 * into a delegate call. 108 * <p/> 109 * See {@link DelegateMethodAdapter} for more details. 110 * 111 * @param log The logger object. Must not be null. 112 * @param mvOriginal The parent method writer to copy of the original method. 113 * Must be {@code null} when dealing with a native original method. 114 * @param mvDelegate The parent method writer to generate the delegating method. 115 * Must never be null. 116 * @param className The internal class name of the class to visit, 117 * e.g. <code>com/android/SomeClass$InnerClass</code>. 118 * @param delegateClassName The internal class name of the delegate class 119 * @param methodName The simple name of the method. 120 * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} + 121 * {@link Type#getArgumentTypes(String)}) 122 * @param isStatic True if the method is declared static. 123 */ DelegateMethodAdapter(Log log, MethodVisitor mvOriginal, MethodVisitor mvDelegate, String className, String delegateClassName, String methodName, String desc, boolean isStatic, boolean isStaticClass)124 public DelegateMethodAdapter(Log log, 125 MethodVisitor mvOriginal, 126 MethodVisitor mvDelegate, 127 String className, 128 String delegateClassName, 129 String methodName, 130 String desc, 131 boolean isStatic, 132 boolean isStaticClass) { 133 super(Main.ASM_VERSION); 134 mLog = log; 135 mOrgWriter = mvOriginal; 136 mDelWriter = mvDelegate; 137 mClassName = className; 138 mDelegateClassName = delegateClassName; 139 mMethodName = methodName; 140 mDesc = desc; 141 mIsStatic = isStatic; 142 mIsStaticInnerClass = isStaticClass; 143 } 144 DelegateMethodAdapter(Log log, MethodVisitor mvOriginal, MethodVisitor mvDelegate, String className, String methodName, String desc, boolean isStatic, boolean isStaticClass)145 public DelegateMethodAdapter(Log log, 146 MethodVisitor mvOriginal, 147 MethodVisitor mvDelegate, 148 String className, 149 String methodName, 150 String desc, 151 boolean isStatic, 152 boolean isStaticClass) { 153 this(log, mvOriginal, mvDelegate, className, className + DELEGATE_SUFFIX, methodName, 154 desc, isStatic, isStaticClass); 155 } 156 157 /** 158 * Generates the new code for the method. 159 * <p/> 160 * For native methods, this must be invoked directly by {@link DelegateClassAdapter} 161 * (since they have no code to visit). 162 * <p/> 163 * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to 164 * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern 165 * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then 166 * this method will be invoked from {@link MethodVisitor#visitEnd()}. 167 */ generateDelegateCode()168 public void generateDelegateCode() { 169 /* 170 * The goal is to generate a call to a static delegate method. 171 * If this method is non-static, the first parameter will be 'this'. 172 * All the parameters must be passed and then the eventual return type returned. 173 * 174 * Example, let's say we have a method such as 175 * public void myMethod(int a, Object b, ArrayList<String> c) { ... } 176 * 177 * We'll want to create a body that calls a delegate method like this: 178 * TheClass_Delegate.myMethod(this, a, b, c); 179 * 180 * If the method is non-static and the class name is an inner class (e.g. has $ in its 181 * last segment), we want to push the 'this' of the outer class first: 182 * OuterClass_InnerClass_Delegate.myMethod( 183 * OuterClass.this, 184 * OuterClass$InnerClass.this, 185 * a, b, c); 186 * 187 * Only one level of inner class is supported right now, for simplicity and because 188 * we don't need more. 189 * 190 * The generated class name is the current class name with "_Delegate" appended to it. 191 * One thing to realize is that we don't care about generics -- since generic types 192 * are erased at build time, they have no influence on the method name being called. 193 */ 194 195 // Add our annotation 196 AnnotationVisitor aw = mDelWriter.visitAnnotation( 197 Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(), 198 true); // visible at runtime 199 if (aw != null) { 200 aw.visitEnd(); 201 } 202 203 mDelWriter.visitCode(); 204 205 if (mDelegateLineNumber != null) { 206 Object[] p = mDelegateLineNumber; 207 mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]); 208 } 209 210 ArrayList<Type> paramTypes = new ArrayList<>(); 211 String delegateClassName = mDelegateClassName; 212 boolean pushedArg0 = false; 213 int maxStack = 0; 214 215 // Check if the last segment of the class name has inner an class. 216 // Right now we only support one level of inner classes. 217 Type outerType = null; 218 int slash = mClassName.lastIndexOf('/'); 219 int dol = mClassName.lastIndexOf('$'); 220 if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) { 221 String outerClass = mClassName.substring(0, dol); 222 outerType = Type.getObjectType(outerClass); 223 224 // Change a delegate class name to "com/foo/Outer_Inner_Delegate" 225 delegateClassName = delegateClassName.replace('$', '_'); 226 } 227 228 // For an instance method (e.g. non-static), push the 'this' preceded 229 // by the 'this' of any outer class, if any. 230 if (!mIsStatic) { 231 232 if (outerType != null && !mIsStaticInnerClass) { 233 // The first-level inner class has a package-protected member called 'this$0' 234 // that points to the outer class. 235 236 // Push this.getField("this$0") on the call stack. 237 mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this 238 mDelWriter.visitFieldInsn(Opcodes.GETFIELD, 239 mClassName, // class where the field is defined 240 "this$0", // field name 241 outerType.getDescriptor()); // type of the field 242 maxStack++; 243 paramTypes.add(outerType); 244 245 } 246 247 // Push "this" for the instance method, which is always ALOAD 0 248 mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); 249 maxStack++; 250 pushedArg0 = true; 251 paramTypes.add(Type.getObjectType(mClassName)); 252 } 253 254 // Push all other arguments. Start at arg 1 if we already pushed 'this' above. 255 Type[] argTypes = Type.getArgumentTypes(mDesc); 256 int maxLocals = pushedArg0 ? 1 : 0; 257 for (Type t : argTypes) { 258 int size = t.getSize(); 259 mDelWriter.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals); 260 maxLocals += size; 261 maxStack += size; 262 paramTypes.add(t); 263 } 264 265 // Construct the descriptor of the delegate based on the parameters 266 // we pushed on the call stack. The return type remains unchanged. 267 String desc = Type.getMethodDescriptor( 268 Type.getReturnType(mDesc), 269 paramTypes.toArray(new Type[paramTypes.size()])); 270 271 // Invoke the static delegate 272 mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC, 273 delegateClassName, 274 mMethodName, 275 desc, 276 false); 277 278 Type returnType = Type.getReturnType(mDesc); 279 mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); 280 281 mDelWriter.visitMaxs(maxStack, maxLocals); 282 mDelWriter.visitEnd(); 283 284 // For debugging now. Maybe we should collect these and store them in 285 // a text file for helping create the delegates. We could also compare 286 // the text file to a golden and break the build on unsupported changes 287 // or regressions. Even better we could fancy-print something that looks 288 // like the expected Java method declaration. 289 mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc); 290 } 291 292 /* Pass down to visitor writer. In this implementation, either do nothing. */ 293 @Override visitCode()294 public void visitCode() { 295 if (mOrgWriter != null) { 296 mOrgWriter.visitCode(); 297 } 298 } 299 300 /* 301 * visitMaxs is called just before visitEnd if there was any code to rewrite. 302 */ 303 @Override visitMaxs(int maxStack, int maxLocals)304 public void visitMaxs(int maxStack, int maxLocals) { 305 if (mOrgWriter != null) { 306 mOrgWriter.visitMaxs(maxStack, maxLocals); 307 } 308 } 309 310 /** End of visiting. Generate the delegating code. */ 311 @Override visitEnd()312 public void visitEnd() { 313 if (mOrgWriter != null) { 314 mOrgWriter.visitEnd(); 315 } 316 generateDelegateCode(); 317 } 318 319 /* Writes all annotation from the original method. */ 320 @Override visitAnnotation(String desc, boolean visible)321 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 322 if (mOrgWriter != null) { 323 return mOrgWriter.visitAnnotation(desc, visible); 324 } else { 325 return null; 326 } 327 } 328 329 /* Writes all annotation default values from the original method. */ 330 @Override visitAnnotationDefault()331 public AnnotationVisitor visitAnnotationDefault() { 332 if (mOrgWriter != null) { 333 return mOrgWriter.visitAnnotationDefault(); 334 } else { 335 return null; 336 } 337 } 338 339 @Override visitParameterAnnotation(int parameter, String desc, boolean visible)340 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, 341 boolean visible) { 342 if (mOrgWriter != null) { 343 return mOrgWriter.visitParameterAnnotation(parameter, desc, visible); 344 } else { 345 return null; 346 } 347 } 348 349 /* Writes all attributes from the original method. */ 350 @Override visitAttribute(Attribute attr)351 public void visitAttribute(Attribute attr) { 352 if (mOrgWriter != null) { 353 mOrgWriter.visitAttribute(attr); 354 } 355 } 356 357 /* 358 * Only writes the first line number present in the original code so that source 359 * viewers can direct to the correct method, even if the content doesn't match. 360 */ 361 @Override visitLineNumber(int line, Label start)362 public void visitLineNumber(int line, Label start) { 363 // Capture the first line values for the new delegate method 364 if (mDelegateLineNumber == null) { 365 mDelegateLineNumber = new Object[] { line, start }; 366 } 367 if (mOrgWriter != null) { 368 mOrgWriter.visitLineNumber(line, start); 369 } 370 } 371 372 @Override visitInsn(int opcode)373 public void visitInsn(int opcode) { 374 if (mOrgWriter != null) { 375 mOrgWriter.visitInsn(opcode); 376 } 377 } 378 379 @Override visitLabel(Label label)380 public void visitLabel(Label label) { 381 if (mOrgWriter != null) { 382 mOrgWriter.visitLabel(label); 383 } 384 } 385 386 @Override visitTryCatchBlock(Label start, Label end, Label handler, String type)387 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { 388 if (mOrgWriter != null) { 389 mOrgWriter.visitTryCatchBlock(start, end, handler, type); 390 } 391 } 392 393 @Override visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)394 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 395 if (mOrgWriter != null) { 396 mOrgWriter.visitMethodInsn(opcode, owner, name, desc, itf); 397 } 398 } 399 400 @Override visitFieldInsn(int opcode, String owner, String name, String desc)401 public void visitFieldInsn(int opcode, String owner, String name, String desc) { 402 if (mOrgWriter != null) { 403 mOrgWriter.visitFieldInsn(opcode, owner, name, desc); 404 } 405 } 406 407 @Override visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack)408 public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { 409 if (mOrgWriter != null) { 410 mOrgWriter.visitFrame(type, nLocal, local, nStack, stack); 411 } 412 } 413 414 @Override visitIincInsn(int var, int increment)415 public void visitIincInsn(int var, int increment) { 416 if (mOrgWriter != null) { 417 mOrgWriter.visitIincInsn(var, increment); 418 } 419 } 420 421 @Override visitIntInsn(int opcode, int operand)422 public void visitIntInsn(int opcode, int operand) { 423 if (mOrgWriter != null) { 424 mOrgWriter.visitIntInsn(opcode, operand); 425 } 426 } 427 428 @Override visitJumpInsn(int opcode, Label label)429 public void visitJumpInsn(int opcode, Label label) { 430 if (mOrgWriter != null) { 431 mOrgWriter.visitJumpInsn(opcode, label); 432 } 433 } 434 435 @Override visitLdcInsn(Object cst)436 public void visitLdcInsn(Object cst) { 437 if (mOrgWriter != null) { 438 mOrgWriter.visitLdcInsn(cst); 439 } 440 } 441 442 @Override visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index)443 public void visitLocalVariable(String name, String desc, String signature, 444 Label start, Label end, int index) { 445 if (mOrgWriter != null) { 446 mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index); 447 } 448 } 449 450 @Override visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels)451 public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { 452 if (mOrgWriter != null) { 453 mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels); 454 } 455 } 456 457 @Override visitMultiANewArrayInsn(String desc, int dims)458 public void visitMultiANewArrayInsn(String desc, int dims) { 459 if (mOrgWriter != null) { 460 mOrgWriter.visitMultiANewArrayInsn(desc, dims); 461 } 462 } 463 464 @Override visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels)465 public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { 466 if (mOrgWriter != null) { 467 mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels); 468 } 469 } 470 471 @Override visitTypeInsn(int opcode, String type)472 public void visitTypeInsn(int opcode, String type) { 473 if (mOrgWriter != null) { 474 mOrgWriter.visitTypeInsn(opcode, type); 475 } 476 } 477 478 @Override visitVarInsn(int opcode, int var)479 public void visitVarInsn(int opcode, int var) { 480 if (mOrgWriter != null) { 481 mOrgWriter.visitVarInsn(opcode, var); 482 } 483 } 484 485 @Override visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments)486 public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, 487 Object... bootstrapMethodArguments) { 488 if (mOrgWriter != null) { 489 mOrgWriter.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, 490 bootstrapMethodArguments); 491 } 492 } 493 494 @Override visitParameter(String name, int access)495 public void visitParameter(String name, int access) { 496 if (mOrgWriter != null) { 497 mOrgWriter.visitParameter(name, access); 498 } 499 } 500 501 @Override visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible)502 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, 503 boolean visible) { 504 if (mOrgWriter != null) { 505 return mOrgWriter.visitTypeAnnotation(typeRef, typePath, descriptor, visible); 506 } else { 507 return null; 508 } 509 } 510 511 @Override visitAnnotableParameterCount(int parameterCount, boolean visible)512 public void visitAnnotableParameterCount(int parameterCount, boolean visible) { 513 if (mOrgWriter != null) { 514 mOrgWriter.visitAnnotableParameterCount(parameterCount, visible); 515 } 516 } 517 518 @Override visitMethodInsn(int opcode, String owner, String name, String descriptor)519 public void visitMethodInsn(int opcode, String owner, String name, String descriptor) { 520 if (mOrgWriter != null) { 521 mOrgWriter.visitMethodInsn(opcode, owner, name, descriptor); 522 } 523 } 524 525 @Override visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible)526 public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, 527 boolean visible) { 528 if (mOrgWriter != null) { 529 return mOrgWriter.visitInsnAnnotation(typeRef, typePath, descriptor, visible); 530 } else { 531 return null; 532 } 533 } 534 535 @Override visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible)536 public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, 537 String descriptor, boolean visible) { 538 if (mOrgWriter != null) { 539 return mOrgWriter.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible); 540 } else { 541 return null; 542 } 543 } 544 545 @Override visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible)546 public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, 547 Label[] start, Label[] end, int[] index, String descriptor, boolean visible) { 548 if (mOrgWriter != null) { 549 return mOrgWriter.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, 550 descriptor, visible); 551 } else { 552 return null; 553 } 554 } 555 } 556