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.asm 17 18 import com.android.hoststubgen.ClassParseException 19 import com.android.hoststubgen.InvalidJarFileException 20 import com.android.hoststubgen.log 21 import java.io.PrintWriter 22 import java.util.Arrays 23 import java.util.concurrent.Executors 24 import java.util.concurrent.TimeUnit 25 import java.util.concurrent.atomic.AtomicReference 26 import java.util.function.Consumer 27 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry 28 import org.apache.commons.compress.archivers.zip.ZipFile 29 import org.objectweb.asm.ClassReader 30 import org.objectweb.asm.tree.AnnotationNode 31 import org.objectweb.asm.tree.ClassNode 32 import org.objectweb.asm.tree.FieldNode 33 import org.objectweb.asm.tree.MethodNode 34 import org.objectweb.asm.tree.TypeAnnotationNode 35 36 /** 37 * Stores all classes loaded from a jar file, in a form of [ClassNode] 38 */ 39 class ClassNodes { 40 val mAllClasses: MutableMap<String, ClassNode> = HashMap() 41 42 /** 43 * Total number of classes registered. 44 */ 45 val size: Int 46 get() = mAllClasses.size 47 48 /** Add a [ClassNode] */ 49 fun addClass(cn: ClassNode): Boolean { 50 if (mAllClasses.containsKey(cn.name)) { 51 return false 52 } 53 mAllClasses[cn.name.toJvmClassName()] = cn 54 return true 55 } 56 57 /** Get a class's [ClassNodes] (which may not exist) */ 58 fun findClass(name: String): ClassNode? { 59 return mAllClasses[name.toJvmClassName()] 60 } 61 62 /** Get a class's [ClassNodes] (which must exists) */ 63 fun getClass(name: String): ClassNode { 64 return findClass(name) ?: throw ClassParseException("Class $name not found") 65 } 66 67 /** @return whether a class exists or not */ 68 fun hasClass(name: String): Boolean { 69 return mAllClasses.containsKey(name.toJvmClassName()) 70 } 71 72 /** Find a field, which may not exist. */ 73 fun findField( 74 className: String, 75 fieldName: String, 76 ): FieldNode? { 77 return findClass(className)?.fields?.firstOrNull { it.name == fieldName }?.let { fn -> 78 return fn 79 } 80 } 81 82 /** Find a method, which may not exist. */ 83 fun findMethod( 84 className: String, 85 methodName: String, 86 descriptor: String, 87 ): MethodNode? { 88 return findClass(className)?.methods 89 ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn -> 90 return mn 91 } 92 } 93 94 /** @return true if a class has a class initializer. */ 95 fun hasClassInitializer(className: String): Boolean { 96 return findMethod(className, CLASS_INITIALIZER_NAME, CLASS_INITIALIZER_DESC) != null 97 } 98 99 /** Run the lambda on each class in alphabetical order. */ 100 fun forEach(consumer: (classNode: ClassNode) -> Unit) { 101 val keys = mAllClasses.keys.toTypedArray() 102 Arrays.sort(keys) 103 104 for (name in keys) { 105 consumer(mAllClasses[name]!!) 106 } 107 } 108 109 /** 110 * Dump all classes. 111 */ 112 fun dump(pw: PrintWriter) { 113 forEach { classNode -> dumpClass(pw, classNode) } 114 } 115 116 private fun dumpClass(pw: PrintWriter, cn: ClassNode) { 117 pw.printf("Class: %s [access: %x]\n", cn.name, cn.access) 118 dumpAnnotations( 119 pw, " ", 120 cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations, 121 cn.visibleAnnotations, cn.invisibleAnnotations, 122 ) 123 124 for (f in cn.fields ?: emptyList()) { 125 pw.printf( 126 " Field: %s [sig: %s] [desc: %s] [access: %x]\n", 127 f.name, f.signature, f.desc, f.access 128 ) 129 dumpAnnotations( 130 pw, " ", 131 f.visibleTypeAnnotations, f.invisibleTypeAnnotations, 132 f.visibleAnnotations, f.invisibleAnnotations, 133 ) 134 } 135 for (m in cn.methods ?: emptyList()) { 136 pw.printf( 137 " Method: %s [sig: %s] [desc: %s] [access: %x]\n", 138 m.name, m.signature, m.desc, m.access 139 ) 140 dumpAnnotations( 141 pw, " ", 142 m.visibleTypeAnnotations, m.invisibleTypeAnnotations, 143 m.visibleAnnotations, m.invisibleAnnotations, 144 ) 145 } 146 } 147 148 private fun dumpAnnotations( 149 pw: PrintWriter, 150 prefix: String, 151 visibleTypeAnnotations: List<TypeAnnotationNode>?, 152 invisibleTypeAnnotations: List<TypeAnnotationNode>?, 153 visibleAnnotations: List<AnnotationNode>?, 154 invisibleAnnotations: List<AnnotationNode>?, 155 ) { 156 for (an in visibleTypeAnnotations ?: emptyList()) { 157 pw.printf("%sTypeAnnotation(vis): %s\n", prefix, an.desc) 158 } 159 for (an in invisibleTypeAnnotations ?: emptyList()) { 160 pw.printf("%sTypeAnnotation(inv): %s\n", prefix, an.desc) 161 } 162 for (an in visibleAnnotations ?: emptyList()) { 163 pw.printf("%sAnnotation(vis): %s\n", prefix, an.desc) 164 if (an.values == null) { 165 continue 166 } 167 var i = 0 168 while (i < an.values.size - 1) { 169 pw.printf("%s - %s -> %s \n", prefix, an.values[i], an.values[i + 1]) 170 i += 2 171 } 172 } 173 for (an in invisibleAnnotations ?: emptyList()) { 174 pw.printf("%sAnnotation(inv): %s\n", prefix, an.desc) 175 if (an.values == null) { 176 continue 177 } 178 var i = 0 179 while (i < an.values.size - 1) { 180 pw.printf("%s - %s -> %s \n", prefix, an.values[i], an.values[i + 1]) 181 i += 2 182 } 183 } 184 } 185 186 companion object { 187 /** 188 * Load all the classes, without code. 189 */ 190 fun loadClassStructures( 191 inJar: ZipFile, 192 jarName: String, 193 timeCollector: Consumer<Double>? = null, 194 ): ClassNodes { 195 val allClasses = ClassNodes() 196 197 // Load classes in parallel. 198 val executor = Executors.newFixedThreadPool(4) 199 200 // First exception defected. 201 val exception = AtomicReference<Throwable>() 202 203 // Called on a BG thread. Read a single jar entry and add it to [allClasses]. 204 fun parseClass(inZip: ZipFile, entry: ZipArchiveEntry) { 205 try { 206 val classBytes = inZip.getInputStream(entry).use { it.readAllBytes() } 207 val cr = ClassReader(classBytes) 208 val cn = ClassNode() 209 cr.accept( 210 cn, ClassReader.SKIP_CODE 211 or ClassReader.SKIP_DEBUG 212 or ClassReader.SKIP_FRAMES 213 ) 214 synchronized(allClasses) { 215 if (!allClasses.addClass(cn)) { 216 log.w("Duplicate class found: ${cn.name}") 217 } 218 } 219 } catch (e: Throwable) { 220 log.e("Failed to load class: $e") 221 exception.compareAndSet(null, e) 222 } 223 } 224 225 // Actually open the jar and read it on worker threads. 226 val time = log.iTime("Reading class structure from $jarName") { 227 log.withIndent { 228 inJar.entries.asSequence().forEach { entry -> 229 if (entry.name.endsWith(".class")) { 230 executor.submit { 231 parseClass(inJar, entry) 232 } 233 } else if (entry.name.endsWith(".dex")) { 234 // Seems like it's an ART jar file. We can't process it. 235 // It's a fatal error. 236 throw InvalidJarFileException( 237 "$jarName is not a desktop jar file." 238 + " It contains a *.dex file." 239 ) 240 } else { 241 // Unknown file type. Skip. 242 } 243 } 244 245 // Wait for all the work to complete. (must do it before closing the zip) 246 log.i("Waiting for all loaders to finish...") 247 executor.shutdown() 248 executor.awaitTermination(5, TimeUnit.MINUTES) 249 log.i("All loaders to finished.") 250 } 251 252 // If any exception is detected, throw it. 253 exception.get()?.let { 254 throw it 255 } 256 257 if (allClasses.size == 0) { 258 log.w("$jarName contains no *.class files.") 259 } else { 260 log.i("Loaded ${allClasses.size} classes from $jarName.") 261 } 262 } 263 timeCollector?.accept(time) 264 return allClasses 265 } 266 } 267 } 268