1 /* <lambda>null2 * Copyright (C) 2023 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 package com.android.hoststubgen.visitors 17 18 import com.android.hoststubgen.HostStubGenErrors 19 import com.android.hoststubgen.HostStubGenStats 20 import com.android.hoststubgen.LogLevel 21 import com.android.hoststubgen.asm.ClassNodes 22 import com.android.hoststubgen.asm.UnifiedVisitor 23 import com.android.hoststubgen.asm.getPackageNameFromFullClassName 24 import com.android.hoststubgen.asm.resolveClassNameWithDefaultPackage 25 import com.android.hoststubgen.asm.toJvmClassName 26 import com.android.hoststubgen.filters.FilterPolicy 27 import com.android.hoststubgen.filters.FilterPolicyWithReason 28 import com.android.hoststubgen.filters.OutputFilter 29 import com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl 30 import com.android.hoststubgen.hosthelper.HostStubGenKeptInStub 31 import com.android.hoststubgen.log 32 import org.objectweb.asm.ClassVisitor 33 import org.objectweb.asm.FieldVisitor 34 import org.objectweb.asm.MethodVisitor 35 import org.objectweb.asm.Opcodes 36 import org.objectweb.asm.commons.ClassRemapper 37 import org.objectweb.asm.util.TraceClassVisitor 38 import java.io.PrintWriter 39 40 val OPCODE_VERSION = Opcodes.ASM9 41 42 abstract class BaseAdapter ( 43 protected val classes: ClassNodes, 44 nextVisitor: ClassVisitor, 45 protected val filter: OutputFilter, 46 protected val options: Options, 47 ) : ClassVisitor(OPCODE_VERSION, nextVisitor) { 48 49 /** 50 * Options to control the behavior. 51 */ 52 data class Options ( 53 val errors: HostStubGenErrors, 54 val stats: HostStubGenStats?, 55 val enablePreTrace: Boolean, 56 val enablePostTrace: Boolean, 57 val enableNonStubMethodCallDetection: Boolean, 58 ) 59 60 protected lateinit var currentPackageName: String 61 protected lateinit var currentClassName: String 62 protected var nativeSubstitutionClass: String? = null 63 protected lateinit var classPolicy: FilterPolicyWithReason 64 65 /** 66 * Return whether an item with a given policy should be included in the output. 67 */ 68 protected abstract fun shouldEmit(policy: FilterPolicy): Boolean 69 70 /** 71 * Inject [HostStubGenKeptInStub] and [HostStubGenKeptInImpl] as needed to an item. 72 */ 73 protected fun injectInStubAndKeepAnnotations(policy: FilterPolicy, v: UnifiedVisitor) { 74 if (policy.needsInStub) { 75 v.visitAnnotation(HostStubGenKeptInStub.CLASS_DESCRIPTOR, true) 76 } 77 if (policy.needsInImpl) { 78 v.visitAnnotation(HostStubGenKeptInImpl.CLASS_DESCRIPTOR, true) 79 } 80 } 81 82 override fun visit( 83 version: Int, 84 access: Int, 85 name: String, 86 signature: String?, 87 superName: String?, 88 interfaces: Array<String>, 89 ) { 90 super.visit(version, access, name, signature, superName, interfaces) 91 currentClassName = name 92 currentPackageName = getPackageNameFromFullClassName(name) 93 classPolicy = filter.getPolicyForClass(currentClassName) 94 95 log.d("[%s] visit: %s (package: %s)", this.javaClass.simpleName, name, currentPackageName) 96 log.indent() 97 log.v("Emitting class: %s", name) 98 log.indent() 99 100 filter.getNativeSubstitutionClass(currentClassName)?.let { className -> 101 val fullClassName = resolveClassNameWithDefaultPackage(className, currentPackageName) 102 .toJvmClassName() 103 log.d(" NativeSubstitutionClass: $fullClassName") 104 if (classes.findClass(fullClassName) == null) { 105 log.w("Native substitution class $fullClassName not found. Class must be " + 106 "available at runtime.") 107 } else { 108 // If the class exists, it must have a KeepClass policy. 109 if (filter.getPolicyForClass(fullClassName).policy != FilterPolicy.KeepClass) { 110 // TODO: Use real annotation name. 111 options.errors.onErrorFound( 112 "Native substitution class $fullClassName should have @Keep.") 113 } 114 } 115 116 nativeSubstitutionClass = fullClassName 117 } 118 // Inject annotations to generated classes. 119 injectInStubAndKeepAnnotations(classPolicy.policy, UnifiedVisitor.on(this)) 120 } 121 122 override fun visitEnd() { 123 log.unindent() 124 log.unindent() 125 super.visitEnd() 126 } 127 128 var skipMemberModificationNestCount = 0 129 130 /** 131 * This method allows writing class members without any modifications. 132 */ 133 protected inline fun writeRawMembers(callback: () -> Unit) { 134 skipMemberModificationNestCount++ 135 try { 136 callback() 137 } finally { 138 skipMemberModificationNestCount-- 139 } 140 } 141 142 override fun visitField( 143 access: Int, 144 name: String, 145 descriptor: String, 146 signature: String?, 147 value: Any?, 148 ): FieldVisitor? { 149 if (skipMemberModificationNestCount > 0) { 150 return super.visitField(access, name, descriptor, signature, value) 151 } 152 val policy = filter.getPolicyForField(currentClassName, name) 153 log.d("visitField: %s %s [%x] Policy: %s", name, descriptor, access, policy) 154 155 log.withIndent { 156 if (!shouldEmit(policy.policy)) { 157 log.d("Removing %s %s", name, policy) 158 return null 159 } 160 161 log.v("Emitting field: %s %s %s", name, descriptor, policy) 162 val ret = super.visitField(access, name, descriptor, signature, value) 163 164 injectInStubAndKeepAnnotations(policy.policy, UnifiedVisitor.on(ret)) 165 166 return ret 167 } 168 } 169 170 override fun visitMethod( 171 access: Int, 172 name: String, 173 descriptor: String, 174 signature: String?, 175 exceptions: Array<String>?, 176 ): MethodVisitor? { 177 if (skipMemberModificationNestCount > 0) { 178 return super.visitMethod(access, name, descriptor, signature, exceptions) 179 } 180 val p = filter.getPolicyForMethod(currentClassName, name, descriptor) 181 log.d("visitMethod: %s%s [%x] [%s] Policy: %s", name, descriptor, access, signature, p) 182 options.stats?.onVisitPolicyForMethod(currentClassName, name, descriptor, p, access) 183 184 log.withIndent { 185 // If it's a substitute-from method, then skip (== remove). 186 // Instead of this method, we rename the substitute-to method with the original 187 // name, in the "Maybe rename the method" part below. 188 val policy = filter.getPolicyForMethod(currentClassName, name, descriptor) 189 if (policy.policy.isSubstitute) { 190 log.d("Skipping %s%s %s", name, descriptor, policy) 191 return null 192 } 193 if (!shouldEmit(p.policy)) { 194 log.d("Removing %s%s %s", name, descriptor, policy) 195 return null 196 } 197 198 var newAccess = access 199 200 // Maybe rename the method. 201 val newName: String 202 val renameTo = filter.getRenameTo(currentClassName, name, descriptor) 203 if (renameTo != null) { 204 newName = renameTo 205 206 // It's confusing, but here, `newName` is the original method name 207 // (the one with the @substitute/replace annotation). 208 // `name` is the name of the method we're currently visiting, so it's usually a 209 // "...$ravewnwood" name. 210 newAccess = checkSubstitutionMethodCompatibility( 211 classes, currentClassName, newName, name, descriptor, options.errors) 212 if (newAccess == NOT_COMPATIBLE) { 213 return null 214 } 215 216 log.v("Emitting %s.%s%s as %s %s", currentClassName, name, descriptor, 217 newName, policy) 218 } else { 219 log.v("Emitting method: %s%s %s", name, descriptor, policy) 220 newName = name 221 } 222 223 // Let subclass update the flag. 224 // But note, we only use it when calling the super's method, 225 // but not for visitMethodInner(), because when subclass wants to change access, 226 // it can do so inside visitMethodInner(). 227 newAccess = updateAccessFlags(newAccess, name, descriptor) 228 229 val ret = visitMethodInner(access, newName, descriptor, signature, exceptions, policy, 230 renameTo != null, 231 super.visitMethod(newAccess, newName, descriptor, signature, exceptions)) 232 233 ret?.let { 234 injectInStubAndKeepAnnotations(policy.policy, UnifiedVisitor.on(ret)) 235 } 236 237 return ret 238 } 239 } 240 241 open fun updateAccessFlags( 242 access: Int, 243 name: String, 244 descriptor: String, 245 ): Int { 246 return access 247 } 248 249 abstract fun visitMethodInner( 250 access: Int, 251 name: String, 252 descriptor: String, 253 signature: String?, 254 exceptions: Array<String>?, 255 policy: FilterPolicyWithReason, 256 substituted: Boolean, 257 superVisitor: MethodVisitor?, 258 ): MethodVisitor? 259 260 companion object { 261 fun getVisitor( 262 classInternalName: String, 263 classes: ClassNodes, 264 nextVisitor: ClassVisitor, 265 filter: OutputFilter, 266 packageRedirector: PackageRedirectRemapper, 267 forImpl: Boolean, 268 options: Options, 269 ): ClassVisitor { 270 var next = nextVisitor 271 272 val verbosePrinter = PrintWriter(log.getWriter(LogLevel.Verbose)) 273 274 // Inject TraceClassVisitor for debugging. 275 if (options.enablePostTrace) { 276 next = TraceClassVisitor(next, verbosePrinter) 277 } 278 279 // Handle --package-redirect 280 if (!packageRedirector.isEmpty) { 281 // Don't apply the remapper on redirect-from classes. 282 // Otherwise, if the target jar actually contains the "from" classes (which 283 // may or may not be the case) they'd be renamed. 284 // But we update all references in other places, so, a method call to a "from" class 285 // would be replaced with the "to" class. All type references (e.g. variable types) 286 // will be updated too. 287 if (!packageRedirector.isTarget(classInternalName)) { 288 next = ClassRemapper(next, packageRedirector) 289 } else { 290 log.v("Class $classInternalName is a redirect-from class, not applying" + 291 " --package-redirect") 292 } 293 } 294 295 var ret: ClassVisitor 296 if (forImpl) { 297 ret = ImplGeneratingAdapter(classes, next, filter, options) 298 } else { 299 ret = StubGeneratingAdapter(classes, next, filter, options) 300 } 301 302 // Inject TraceClassVisitor for debugging. 303 if (options.enablePreTrace) { 304 ret = TraceClassVisitor(ret, verbosePrinter) 305 } 306 return ret 307 } 308 } 309 } 310