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>&lt;className&gt;_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