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