1 /*
2  * Copyright (C) 2008 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.NotNull;
20 import com.android.tools.layoutlib.annotations.Nullable;
21 import com.android.tools.layoutlib.annotations.VisibleForTesting;
22 
23 import org.objectweb.asm.ClassReader;
24 import org.objectweb.asm.ClassVisitor;
25 import org.objectweb.asm.ClassWriter;
26 import org.objectweb.asm.FieldVisitor;
27 import org.objectweb.asm.MethodVisitor;
28 import org.objectweb.asm.Opcodes;
29 import org.objectweb.asm.Type;
30 
31 import java.util.Collections;
32 import java.util.Set;
33 
34 /**
35  * Class adapter that can stub some or all of the methods of the class.
36  */
37 class StubClassAdapter extends ClassVisitor {
38     public interface MethodVisitorFactory {
39         @NotNull
create(@otNull MethodVisitor mv, @NotNull String methodName, @NotNull Type returnType, @NotNull String invokeSignature, boolean isStatic, boolean isNative)40         MethodVisitor create(@NotNull MethodVisitor mv,
41                 @NotNull String methodName,
42                 @NotNull Type returnType,
43                 @NotNull String invokeSignature,
44                 boolean isStatic, boolean isNative);
45     }
46 
47     public static class Builder {
48         private Log mLogger;
49         private Set<String> mDeleteReturns;
50         private String mClassName;
51         private ClassVisitor mCv;
52         private boolean mStubNativesOnly;
53         private boolean mRemoveStaticInitializers;
54         private boolean mRemovePrivates;
55         private MethodVisitorFactory mMethodVisitorFactory;
56 
Builder(@otNull Log log, @NotNull ClassVisitor classVisitor)57         private Builder(@NotNull Log log, @NotNull ClassVisitor classVisitor) {
58             mLogger = log;
59             mCv = classVisitor;
60         }
61 
62         @NotNull
withDeleteReturns(@ullable Set<String> deleteReturns)63         public Builder withDeleteReturns(@Nullable Set<String> deleteReturns) {
64             mDeleteReturns = deleteReturns;
65             return this;
66         }
67 
68         @NotNull
withNewClassName(@ullable String className)69         public Builder withNewClassName(@Nullable String className) {
70             mClassName = className;
71             return this;
72         }
73 
useOnlyStubNative(boolean stubNativesOnly)74         public Builder useOnlyStubNative(boolean stubNativesOnly) {
75             mStubNativesOnly = stubNativesOnly;
76             return this;
77         }
78 
79         @NotNull
withMethodVisitorFactory(@ullable MethodVisitorFactory factory)80         public Builder withMethodVisitorFactory(@Nullable MethodVisitorFactory factory) {
81             mMethodVisitorFactory = factory;
82             return this;
83         }
84 
85         @NotNull
removePrivates()86         public Builder removePrivates() {
87             mRemovePrivates = true;
88             return this;
89         }
90 
91         @NotNull
removeStaticInitializers()92         public Builder removeStaticInitializers() {
93             mRemoveStaticInitializers = true;
94             return this;
95         }
96 
build()97         public StubClassAdapter build() {
98             return new StubClassAdapter(mLogger,
99                     mDeleteReturns != null ? mDeleteReturns : Collections.emptySet(),
100                     mClassName,
101                     mCv,
102                     mMethodVisitorFactory != null ? mMethodVisitorFactory : StubCallMethodAdapter::new,
103                     mStubNativesOnly,
104                     mRemovePrivates,
105                     mRemoveStaticInitializers);
106         }
107     }
108 
109     /** True if all methods should be stubbed, false if only native ones must be stubbed. */
110     private final boolean mStubAll;
111     /** True if the class is an interface. */
112     private boolean mIsInterface;
113     private final String mClassName;
114     private final Log mLog;
115     private final Set<String> mDeleteReturns;
116     private final MethodVisitorFactory mMethodVisitorFactory;
117     private final boolean mRemovePrivates;
118     private final boolean mRemoveStaticInitalizers;
119 
120 
121     @NotNull
builder(@otNull Log log, @NotNull ClassVisitor classVisitor)122     public static StubClassAdapter.Builder builder(@NotNull Log log,
123             @NotNull ClassVisitor classVisitor) {
124         return new Builder(log, classVisitor);
125     }
126     /**
127      * Creates a new class adapter that will stub some or all methods.
128      * @param deleteReturns list of types that trigger the deletion of methods returning them.
129      * @param className Optional new name for the class being modified
130      * @param cv The parent class writer visitor
131      * @param stubNativesOnly True if only native methods should be stubbed. False if all
132      * @param removePrivates If true, all private methods and fields will be removed
133      * @param removeStaticInitializers If true, static initializers will be removed
134      */
StubClassAdapter(@otNull Log logger, @NotNull Set<String> deleteReturns, @Nullable String className, @NotNull ClassVisitor cv, @NotNull MethodVisitorFactory methodVisitorFactory, boolean stubNativesOnly, boolean removePrivates, boolean removeStaticInitializers)135     private StubClassAdapter(@NotNull Log logger,
136             @NotNull Set<String> deleteReturns, @Nullable String className,
137             @NotNull ClassVisitor cv, @NotNull MethodVisitorFactory methodVisitorFactory,
138             boolean stubNativesOnly, boolean removePrivates, boolean removeStaticInitializers) {
139         super(Main.ASM_VERSION, cv);
140         mLog = logger;
141         mClassName = className;
142         mStubAll = !stubNativesOnly;
143         mIsInterface = false;
144         mDeleteReturns = deleteReturns;
145         mMethodVisitorFactory = methodVisitorFactory;
146         mRemovePrivates = removePrivates;
147         mRemoveStaticInitalizers = removeStaticInitializers;
148     }
149 
150     /**
151      * Utility method that receives a class in serialized form and returns the stubbed version.
152      */
153     @VisibleForTesting
154     @NotNull
stubClass(@otNull Log log, @NotNull byte[] bytes, @Nullable String newName)155     static byte[] stubClass(@NotNull Log log, @NotNull byte[] bytes,
156             @Nullable String newName) {
157         ClassReader classReader = new ClassReader(bytes);
158         ClassWriter classWriter = new ClassWriter(0);
159 
160         // We replace every method with a Stub exception throw and remove all private
161         // methods and static initializers since we only care about the interfaces.
162         ClassVisitor cv = StubClassAdapter.builder(log, classWriter)
163                 .withNewClassName(newName)
164                 .withMethodVisitorFactory(StubExceptionMethodAdapter::new)
165                 .removePrivates()
166                 .removeStaticInitializers()
167                 .build();
168 
169         classReader.accept(cv, 0);
170 
171         return classWriter.toByteArray();
172     }
173 
174     /**
175      * Utility method that receives a class in serialized form and returns the stubbed version.
176      */
177     @NotNull
stubClass(@otNull Log log, @NotNull byte[] bytes)178     public static byte[] stubClass(@NotNull Log log, @NotNull byte[] bytes) {
179         return stubClass(log, bytes, null);
180     }
181 
182     /* Visits the class header. */
183     @Override
visit(int version, int access, String name, String signature, String superName, String[] interfaces)184     public void visit(int version, int access, String name,
185             String signature, String superName, String[] interfaces) {
186 
187         if (mClassName != null) {
188             // This class might be being renamed.
189             name = mClassName;
190         }
191 
192         // remove final
193         access = access & ~Opcodes.ACC_FINAL;
194         // note: leave abstract classes as such
195         // don't try to implement stub for interfaces
196 
197         mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
198         super.visit(version, access, name, signature, superName, interfaces);
199     }
200 
201     /* Visits the header of an inner class. */
202     @Override
visitInnerClass(String name, String outerName, String innerName, int access)203     public void visitInnerClass(String name, String outerName, String innerName, int access) {
204         // remove final
205         access = access & ~Opcodes.ACC_FINAL;
206         // note: leave abstract classes as such
207         // don't try to implement stub for interfaces
208 
209         super.visitInnerClass(name, outerName, innerName, access);
210     }
211 
212     /* Visits a method. */
213     @Override
visitMethod(int access, String name, String desc, String signature, String[] exceptions)214     public MethodVisitor visitMethod(int access, String name, String desc,
215             String signature, String[] exceptions) {
216         if (mRemovePrivates && (access & Opcodes.ACC_PRIVATE) != 0) {
217             return null;
218         }
219 
220         if (mRemoveStaticInitalizers && "<clinit>".equals(name)) {
221             return null;
222         }
223 
224         if (mDeleteReturns != null) {
225             Type t = Type.getReturnType(desc);
226             if (t.getSort() == Type.OBJECT) {
227                 String returnType = t.getInternalName();
228                 if (returnType != null) {
229                     if (mDeleteReturns.contains(returnType)) {
230                         return null;
231                     }
232                 }
233             }
234         }
235 
236         String methodSignature = mClassName != null ?
237                 mClassName.replace('/', '.') + "#" + name :
238                 signature;
239 
240         // remove final
241         access = access & ~Opcodes.ACC_FINAL;
242 
243         // stub this method if they are all to be stubbed or if it is a native method
244         // and don't try to stub interfaces nor abstract non-native methods.
245         if (!mIsInterface &&
246             ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != Opcodes.ACC_ABSTRACT) &&
247             (mStubAll ||
248              (access & Opcodes.ACC_NATIVE) != 0)) {
249 
250             boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
251             boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
252 
253             // remove abstract, final and native
254             access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
255 
256             String invokeSignature = methodSignature + desc;
257             mLog.debug("  Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
258 
259             MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
260             return mMethodVisitorFactory.create(mw, name, returnType(desc), invokeSignature,
261                     isStatic, isNative);
262 
263         } else {
264             mLog.debug("  Keep: %s %s", name, desc);
265             return super.visitMethod(access, name, desc, signature, exceptions);
266         }
267     }
268 
269     @Override
visitField(int access, String name, String desc, String signature, Object value)270     public FieldVisitor visitField(int access, String name, String desc, String signature,
271             Object value) {
272         if (mRemovePrivates && (access & Opcodes.ACC_PRIVATE) != 0) {
273             return null;
274         }
275 
276         return super.visitField(access, name, desc, signature, value);
277     }
278 
279     /**
280      * Extracts the return {@link Type} of this descriptor.
281      */
returnType(String desc)282     private static Type returnType(String desc) {
283         if (desc != null) {
284             try {
285                 return Type.getReturnType(desc);
286             } catch (ArrayIndexOutOfBoundsException e) {
287                 // ignore, not a valid type.
288             }
289         }
290         return null;
291     }
292 }
293