• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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