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 org.objectweb.asm.ClassReader
22 import org.objectweb.asm.tree.AnnotationNode
23 import org.objectweb.asm.tree.ClassNode
24 import org.objectweb.asm.tree.FieldNode
25 import org.objectweb.asm.tree.MethodNode
26 import org.objectweb.asm.tree.TypeAnnotationNode
27 import java.io.BufferedInputStream
28 import java.io.PrintWriter
29 import java.util.Arrays
30 import java.util.zip.ZipFile
31 
32 /**
33  * Stores all classes loaded from a jar file, in a form of [ClassNode]
34  */
35 class ClassNodes {
36     val mAllClasses: MutableMap<String, ClassNode> = HashMap()
37 
38     /**
39      * Total number of classes registered.
40      */
41     val size: Int
42         get() = mAllClasses.size
43 
44     /** Add a [ClassNode] */
45     fun addClass(cn: ClassNode): Boolean {
46         if (mAllClasses.containsKey(cn.name)) {
47             return false
48         }
49         mAllClasses[cn.name.toJvmClassName()] = cn
50         return true
51     }
52 
53     /** Get a class's [ClassNodes] (which may not exist) */
54     fun findClass(name: String): ClassNode? {
55         return mAllClasses[name.toJvmClassName()]
56     }
57 
58     /** Get a class's [ClassNodes] (which must exists) */
59     fun getClass(name: String): ClassNode {
60         return findClass(name) ?: throw ClassParseException("Class $name not found")
61     }
62 
63     /** @return whether a class exists or not */
64     fun hasClass(name: String): Boolean {
65         return mAllClasses.containsKey(name.toJvmClassName())
66     }
67 
68     /** Find a field, which may not exist. */
69     fun findField(
70         className: String,
71         fieldName: String,
72     ): FieldNode? {
73         return findClass(className)?.fields?.firstOrNull { it.name == fieldName }?.let { fn ->
74             return fn
75         }
76     }
77 
78     /** Find a method, which may not exist. */
79     fun findMethod(
80         className: String,
81         methodName: String,
82         descriptor: String,
83     ): MethodNode? {
84         return findClass(className)?.methods
85             ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
86                 return mn
87             }
88     }
89 
90     /** @return true if a class has a class initializer. */
91     fun hasClassInitializer(className: String): Boolean {
92         return findMethod(className, CLASS_INITIALIZER_NAME, CLASS_INITIALIZER_DESC) != null
93     }
94 
95     /** Run the lambda on each class in alphabetical order. */
96     fun forEach(consumer: (classNode: ClassNode) -> Unit) {
97         val keys = mAllClasses.keys.toTypedArray()
98         Arrays.sort(keys)
99 
100         for (name in keys) {
101             consumer(mAllClasses[name]!!)
102         }
103     }
104 
105     /**
106      * Dump all classes.
107      */
108     fun dump(pw: PrintWriter) {
109         forEach { classNode -> dumpClass(pw, classNode) }
110     }
111 
112     private fun dumpClass(pw: PrintWriter, cn: ClassNode) {
113         pw.printf("Class: %s [access: %x]\n", cn.name, cn.access)
114         dumpAnnotations(
115             pw, "  ",
116             cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations,
117             cn.visibleAnnotations, cn.invisibleAnnotations,
118         )
119 
120         for (f in cn.fields ?: emptyList()) {
121             pw.printf(
122                 "  Field: %s [sig: %s] [desc: %s] [access: %x]\n",
123                 f.name, f.signature, f.desc, f.access
124             )
125             dumpAnnotations(
126                 pw, "    ",
127                 f.visibleTypeAnnotations, f.invisibleTypeAnnotations,
128                 f.visibleAnnotations, f.invisibleAnnotations,
129             )
130         }
131         for (m in cn.methods ?: emptyList()) {
132             pw.printf(
133                 "  Method: %s [sig: %s] [desc: %s] [access: %x]\n",
134                 m.name, m.signature, m.desc, m.access
135             )
136             dumpAnnotations(
137                 pw, "    ",
138                 m.visibleTypeAnnotations, m.invisibleTypeAnnotations,
139                 m.visibleAnnotations, m.invisibleAnnotations,
140             )
141         }
142     }
143 
144     private fun dumpAnnotations(
145         pw: PrintWriter,
146         prefix: String,
147         visibleTypeAnnotations: List<TypeAnnotationNode>?,
148         invisibleTypeAnnotations: List<TypeAnnotationNode>?,
149         visibleAnnotations: List<AnnotationNode>?,
150         invisibleAnnotations: List<AnnotationNode>?,
151     ) {
152         for (an in visibleTypeAnnotations ?: emptyList()) {
153             pw.printf("%sTypeAnnotation(vis): %s\n", prefix, an.desc)
154         }
155         for (an in invisibleTypeAnnotations ?: emptyList()) {
156             pw.printf("%sTypeAnnotation(inv): %s\n", prefix, an.desc)
157         }
158         for (an in visibleAnnotations ?: emptyList()) {
159             pw.printf("%sAnnotation(vis): %s\n", prefix, an.desc)
160             if (an.values == null) {
161                 continue
162             }
163             var i = 0
164             while (i < an.values.size - 1) {
165                 pw.printf("%s  - %s -> %s \n", prefix, an.values[i], an.values[i + 1])
166                 i += 2
167             }
168         }
169         for (an in invisibleAnnotations ?: emptyList()) {
170             pw.printf("%sAnnotation(inv): %s\n", prefix, an.desc)
171             if (an.values == null) {
172                 continue
173             }
174             var i = 0
175             while (i < an.values.size - 1) {
176                 pw.printf("%s  - %s -> %s \n", prefix, an.values[i], an.values[i + 1])
177                 i += 2
178             }
179         }
180     }
181 
182     companion object {
183         /**
184          * Load all the classes, without code.
185          */
186         fun loadClassStructures(inJar: String): ClassNodes {
187             log.i("Reading class structure from $inJar ...")
188             val start = System.currentTimeMillis()
189 
190             val allClasses = ClassNodes()
191 
192             log.withIndent {
193                 ZipFile(inJar).use { inZip ->
194                     val inEntries = inZip.entries()
195 
196                     while (inEntries.hasMoreElements()) {
197                         val entry = inEntries.nextElement()
198 
199                         BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
200                             if (entry.name.endsWith(".class")) {
201                                 val cr = ClassReader(bis)
202                                 val cn = ClassNode()
203                                 cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
204                                         or ClassReader.SKIP_FRAMES)
205                                 if (!allClasses.addClass(cn)) {
206                                     log.w("Duplicate class found: ${cn.name}")
207                                 }
208                             } else if (entry.name.endsWith(".dex")) {
209                                 // Seems like it's an ART jar file. We can't process it.
210                                 // It's a fatal error.
211                                 throw InvalidJarFileException(
212                                     "$inJar is not a desktop jar file. It contains a *.dex file.")
213                             } else {
214                                 // Unknown file type. Skip.
215                                 while (bis.available() > 0) {
216                                     bis.skip((1024 * 1024).toLong())
217                                 }
218                             }
219                         }
220                     }
221                 }
222             }
223             if (allClasses.size == 0) {
224                 log.w("$inJar contains no *.class files.")
225             }
226 
227             val end = System.currentTimeMillis()
228             log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
229             return allClasses
230         }
231     }
232 }