xref: /aosp_15_r20/frameworks/base/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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.HostStubGenInternalException
20 import org.objectweb.asm.ClassVisitor
21 import org.objectweb.asm.FieldVisitor
22 import org.objectweb.asm.MethodVisitor
23 import org.objectweb.asm.Opcodes
24 import org.objectweb.asm.Type
25 import org.objectweb.asm.tree.AnnotationNode
26 import org.objectweb.asm.tree.ClassNode
27 import org.objectweb.asm.tree.FieldNode
28 import org.objectweb.asm.tree.MethodNode
29 
30 
31 /** Name of the class initializer method. */
32 const val CLASS_INITIALIZER_NAME = "<clinit>"
33 
34 /** Descriptor of the class initializer method. */
35 const val CLASS_INITIALIZER_DESC = "()V"
36 
37 /** Name of constructors. */
38 const val CTOR_NAME = "<init>"
39 
40 /**
41  * Find any of [set] from the list of visible / invisible annotations.
42  */
43 fun findAnyAnnotation(
44     set: Set<String>,
45     visibleAnnotations: List<AnnotationNode>?,
46     invisibleAnnotations: List<AnnotationNode>?,
47 ): AnnotationNode? {
48     return visibleAnnotations?.find { it.desc in set }
49         ?: invisibleAnnotations?.find { it.desc in set }
50 }
51 
ClassNodenull52 fun ClassNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
53     return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
54 }
55 
findAnyAnnotationnull56 fun MethodNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
57     return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
58 }
59 
findAnyAnnotationnull60 fun FieldNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
61     return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
62 }
63 
findAllAnnotationsnull64 fun findAllAnnotations(
65     set: Set<String>,
66     visibleAnnotations: List<AnnotationNode>?,
67     invisibleAnnotations: List<AnnotationNode>?
68 ): List<AnnotationNode> {
69     return (visibleAnnotations ?: emptyList()).filter { it.desc in set } +
70             (invisibleAnnotations ?: emptyList()).filter { it.desc in set }
71 }
72 
ClassNodenull73 fun ClassNode.findAllAnnotations(set: Set<String>): List<AnnotationNode> {
74     return findAllAnnotations(set, this.visibleAnnotations, this.invisibleAnnotations)
75 }
76 
findAllAnnotationsnull77 fun MethodNode.findAllAnnotations(set: Set<String>): List<AnnotationNode> {
78     return findAllAnnotations(set, this.visibleAnnotations, this.invisibleAnnotations)
79 }
80 
findAllAnnotationsnull81 fun FieldNode.findAllAnnotations(set: Set<String>): List<AnnotationNode> {
82     return findAllAnnotations(set, this.visibleAnnotations, this.invisibleAnnotations)
83 }
84 
findAnnotationValueAsObjectnull85 fun <T> findAnnotationValueAsObject(
86     an: AnnotationNode,
87     propertyName: String,
88     expectedTypeHumanReadableName: String,
89     converter: (Any?) -> T?,
90 ): T? {
91     for (i in 0..(an.values?.size ?: 0) - 2 step 2) {
92         val name = an.values[i]
93 
94         if (name != propertyName) {
95             continue
96         }
97         val value = an.values[i + 1]
98         if (value == null) {
99             return null
100         }
101 
102         try {
103             return converter(value)
104         } catch (e: ClassCastException) {
105             throw ClassParseException(
106                 "The type of '$propertyName' in annotation @${an.desc} must be " +
107                         "$expectedTypeHumanReadableName, but is ${value?.javaClass?.canonicalName}")
108         }
109     }
110     return null
111 }
112 
findAnnotationValueAsStringnull113 fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): String? {
114     return findAnnotationValueAsObject(an, propertyName, "String", {it as String})
115 }
116 
findAnnotationValueAsTypenull117 fun findAnnotationValueAsType(an: AnnotationNode, propertyName: String): Type? {
118     return findAnnotationValueAsObject(an, propertyName, "Class", {it as Type})
119 }
120 
121 
122 val periodOrSlash = charArrayOf('.', '/')
123 
getPackageNameFromFullClassNamenull124 fun getPackageNameFromFullClassName(fullClassName: String): String {
125     val pos = fullClassName.lastIndexOfAny(periodOrSlash)
126     if (pos == -1) {
127         return ""
128     } else {
129         return fullClassName.substring(0, pos)
130     }
131 }
132 
getClassNameFromFullClassNamenull133 fun getClassNameFromFullClassName(fullClassName: String): String {
134     val pos = fullClassName.lastIndexOfAny(periodOrSlash)
135     if (pos == -1) {
136         return fullClassName
137     } else {
138         return fullClassName.substring(pos + 1)
139     }
140 }
141 
getOuterClassNameFromFullClassNamenull142 fun getOuterClassNameFromFullClassName(fullClassName: String): String {
143     val start = fullClassName.lastIndexOfAny(periodOrSlash)
144     val end = fullClassName.indexOf('$')
145     if (end == -1) {
146         return fullClassName.substring(start + 1)
147     } else {
148         return fullClassName.substring(start + 1, end)
149     }
150 }
151 
152 /**
153  * If [className] is a fully qualified name, just return it.
154  * Otherwise, prepend [defaultPackageName].
155  */
resolveClassNameWithDefaultPackagenull156 fun resolveClassNameWithDefaultPackage(className: String, defaultPackageName: String): String {
157     if (className.contains('.') || className.contains('/')) {
158         return className
159     }
160     return "$defaultPackageName.$className"
161 }
162 
splitWithLastPeriodnull163 fun splitWithLastPeriod(name: String): Pair<String, String>? {
164     val pos = name.lastIndexOf('.')
165     if (pos < 0) {
166         return null
167     }
168     return Pair(name.substring(0, pos), name.substring(pos + 1))
169 }
170 
Stringnull171 fun String.startsWithAny(vararg prefixes: String): Boolean {
172     prefixes.forEach {
173         if (this.startsWith(it)) {
174             return true
175         }
176     }
177     return false
178 }
179 
endsWithAnynull180 fun String.endsWithAny(vararg suffixes: String): Boolean {
181     suffixes.forEach {
182         if (this.endsWith(it)) {
183             return true
184         }
185     }
186     return false
187 }
188 
Stringnull189 fun String.toJvmClassName(): String {
190     return this.replace('.', '/')
191 }
192 
toHumanReadableClassNamenull193 fun String.toHumanReadableClassName(): String {
194     return this.replace('/', '.')
195 }
196 
toHumanReadableMethodNamenull197 fun String.toHumanReadableMethodName(): String {
198     return this.replace('/', '.')
199 }
200 
zipEntryNameToClassNamenull201 fun zipEntryNameToClassName(entryFilename: String): String? {
202     val suffix = ".class"
203     if (!entryFilename.endsWith(suffix)) {
204         return null
205     }
206     return entryFilename.substring(0, entryFilename.length - suffix.length)
207 }
208 
209 private val numericalInnerClassName = """.*\$\d+$""".toRegex()
210 
isAnonymousInnerClassnull211 fun isAnonymousInnerClass(cn: ClassNode): Boolean {
212     // TODO: Is there a better way?
213     return cn.name.matches(numericalInnerClassName)
214 }
215 
216 /**
217  * Write bytecode to push all the method arguments to the stack.
218  * The number of arguments and their type are taken from [methodDescriptor].
219  */
writeByteCodeToPushArgumentsnull220 fun writeByteCodeToPushArguments(
221         methodDescriptor: String,
222         writer: MethodVisitor,
223         argOffset: Int = 0,
224         ) {
225     var i = argOffset - 1
226     Type.getArgumentTypes(methodDescriptor).forEach { type ->
227         i++
228 
229         // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
230 
231         // Note, long and double will consume two local variable spaces, so the extra `i++`.
232         when (type) {
233             Type.VOID_TYPE -> throw HostStubGenInternalException("VOID_TYPE not expected")
234             Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
235                 -> writer.visitVarInsn(Opcodes.ILOAD, i)
236             Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i)
237             Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
238             Type.DOUBLE_TYPE -> writer.visitVarInsn(Opcodes.DLOAD, i++)
239             else -> writer.visitVarInsn(Opcodes.ALOAD, i)
240         }
241     }
242 }
243 
244 /**
245  * Write bytecode to "RETURN" that matches the method's return type, according to
246  * [methodDescriptor].
247  */
writeByteCodeToReturnnull248 fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) {
249     Type.getReturnType(methodDescriptor).let { type ->
250         // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
251         when (type) {
252             Type.VOID_TYPE -> writer.visitInsn(Opcodes.RETURN)
253             Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
254                 -> writer.visitInsn(Opcodes.IRETURN)
255             Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN)
256             Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
257             Type.DOUBLE_TYPE -> writer.visitInsn(Opcodes.DRETURN)
258             else -> writer.visitInsn(Opcodes.ARETURN)
259         }
260     }
261 }
262 
263 /**
264  * Given a method descriptor, insert an [argType] as the first argument to it.
265  */
prependArgTypeToMethodDescriptornull266 fun prependArgTypeToMethodDescriptor(methodDescriptor: String, classInternalName: String): String {
267     val returnType = Type.getReturnType(methodDescriptor)
268     val argTypes = Type.getArgumentTypes(methodDescriptor).toMutableList()
269 
270     argTypes.add(0, Type.getType("L" + classInternalName + ";"))
271 
272     return Type.getMethodDescriptor(returnType, *argTypes.toTypedArray())
273 }
274 
275 /**
276  * Return the "visibility" modifier from an `access` integer.
277  *
278  * (see https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1)
279  */
getVisibilityModifiernull280 fun getVisibilityModifier(access: Int): Int {
281     return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED)
282 }
283 
284 /**
285  * Return true if an `access` integer is "private" or "package private".
286  */
isVisibilityPrivateOrPackagePrivatenull287 fun isVisibilityPrivateOrPackagePrivate(access: Int): Boolean {
288     return when (getVisibilityModifier(access)) {
289         0 -> true // Package private.
290         Opcodes.ACC_PRIVATE -> true
291         else -> false
292     }
293 }
294 
295 enum class Visibility {
296     PRIVATE,
297     PACKAGE_PRIVATE,
298     PROTECTED,
299     PUBLIC;
300 
301     companion object {
fromAccessnull302         fun fromAccess(access: Int): Visibility {
303             if ((access and Opcodes.ACC_PUBLIC) != 0) {
304                 return PUBLIC
305             }
306             if ((access and Opcodes.ACC_PROTECTED) != 0) {
307                 return PROTECTED
308             }
309             if ((access and Opcodes.ACC_PRIVATE) != 0) {
310                 return PRIVATE
311             }
312 
313             return PACKAGE_PRIVATE
314         }
315     }
316 }
317 
ClassNodenull318 fun ClassNode.isEnum(): Boolean {
319     return (this.access and Opcodes.ACC_ENUM) != 0
320 }
321 
ClassNodenull322 fun ClassNode.isAnnotation(): Boolean {
323     return (this.access and Opcodes.ACC_ANNOTATION) != 0
324 }
325 
ClassNodenull326 fun ClassNode.isSynthetic(): Boolean {
327     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
328 }
329 
ClassNodenull330 fun ClassNode.isAbstract(): Boolean {
331     return (this.access and Opcodes.ACC_ABSTRACT) != 0
332 }
333 
MethodNodenull334 fun MethodNode.isSynthetic(): Boolean {
335     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
336 }
337 
isStaticnull338 fun MethodNode.isStatic(): Boolean {
339     return (this.access and Opcodes.ACC_STATIC) != 0
340 }
341 
MethodNodenull342 fun MethodNode.isPublic(): Boolean {
343     return (this.access and Opcodes.ACC_PUBLIC) != 0
344 }
345 
MethodNodenull346 fun MethodNode.isNative(): Boolean {
347     return (this.access and Opcodes.ACC_NATIVE) != 0
348 }
349 
MethodNodenull350 fun MethodNode.isSpecial(): Boolean {
351     return CTOR_NAME == this.name || CLASS_INITIALIZER_NAME == this.name
352 }
353 
FieldNodenull354 fun FieldNode.isEnum(): Boolean {
355     return (this.access and Opcodes.ACC_ENUM) != 0
356 }
357 
FieldNodenull358 fun FieldNode.isSynthetic(): Boolean {
359     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
360 }
361 
ClassNodenull362 fun ClassNode.getVisibility(): Visibility {
363     return Visibility.fromAccess(this.access)
364 }
365 
MethodNodenull366 fun MethodNode.getVisibility(): Visibility {
367     return Visibility.fromAccess(this.access)
368 }
369 
FieldNodenull370 fun FieldNode.getVisibility(): Visibility {
371     return Visibility.fromAccess(this.access)
372 }
373 
374 /** Return the [access] flags without the visibility */
clearVisibilitynull375 fun clearVisibility(access: Int): Int {
376     return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE).inv()
377 }
378 
379 /** Return the visibility part of the [access] flags */
getVisibilitynull380 fun getVisibility(access: Int): Int {
381     return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE)
382 }
383 
384 
385 /*
386 
387 Dump of the members of TinyFrameworkEnumSimple:
388 
389 class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple	keep
390   field Cat	keep (ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM)
391   field Dog	keep
392   field $VALUES	keep (ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC)
393 
394   method values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
395     ^- NOT synthetic (ACC_PUBLIC, ACC_STATIC)
396   method valueOf	(Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
397     ^- NOT synthetic (ACC_PUBLIC, ACC_STATIC)
398   method <init>	(Ljava/lang/String;I)V	keep
399     ^- NOT synthetic (ACC_PRIVATE)
400 
401   method $values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
402      (ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC)
403   method <clinit>	()V	keep
404 
405 Dump of the members of TinyFrameworkEnumSimple:
406 
407 class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex	keep
408   field RED	keep
409   field BLUE	keep
410   field GREEN	keep
411   field mLongName	keep
412   field mShortName	keep
413   field $VALUES	keep
414   method values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
415   method valueOf	(Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
416   method <init>	(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V	keep
417   method getLongName	()Ljava/lang/String;	keep
418   method getShortName	()Ljava/lang/String;	keep
419   method $values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
420   method <clinit>	()V	keep
421 
422  */
423 
isAutoGeneratedEnumMembernull424 fun isAutoGeneratedEnumMember(mn: MethodNode): Boolean {
425     if (mn.isSynthetic()) {
426         return true
427     }
428     if (mn.name == "<init>" && mn.desc == "(Ljava/lang/String;I)V") {
429         return true
430     }
431     if (mn.name == "<clinit>" && mn.desc == "()V") {
432         return true
433     }
434     if (mn.name == "values" && mn.desc.startsWith("()")) {
435         return true
436     }
437     if (mn.name == "valueOf" && mn.desc.startsWith("(Ljava/lang/String;)")) {
438         return true
439     }
440 
441     return false
442 }
443 
isAutoGeneratedEnumMembernull444 fun isAutoGeneratedEnumMember(fn: FieldNode): Boolean {
445     if (fn.isSynthetic() || fn.isEnum()) {
446         return true
447     }
448     return false
449 }
450 
451 /**
452  * Class to help handle [ClassVisitor], [MethodVisitor] and [FieldVisitor] in a unified way.
453  */
454 abstract class UnifiedVisitor {
visitAnnotationnull455     abstract fun visitAnnotation(descriptor: String, visible: Boolean)
456 
457     companion object {
458         fun on(target: ClassVisitor): UnifiedVisitor {
459             return object : UnifiedVisitor() {
460                 override fun visitAnnotation(descriptor: String, visible: Boolean) {
461                     target.visitAnnotation(descriptor, visible)
462                 }
463             }
464         }
465 
466         fun on(target: MethodVisitor): UnifiedVisitor {
467             return object : UnifiedVisitor() {
468                 override fun visitAnnotation(descriptor: String, visible: Boolean) {
469                     target.visitAnnotation(descriptor, visible)
470                 }
471             }
472         }
473 
474         fun on(target: FieldVisitor): UnifiedVisitor {
475             return object : UnifiedVisitor() {
476                 override fun visitAnnotation(descriptor: String, visible: Boolean) {
477                     target.visitAnnotation(descriptor, visible)
478                 }
479             }
480         }
481     }
482 }
483