• 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.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 val CLASS_INITIALIZER_NAME = "<clinit>"
33 
34 /** Descriptor of the class initializer method. */
35 val CLASS_INITIALIZER_DESC = "()V"
36 
37 /** Name of constructors. */
38 val CTOR_NAME = "<init>"
39 
40 /**
41  * Find any of [anyAnnotations] from the list of visible / invisible annotations.
42  */
43 fun findAnyAnnotation(
44         anyAnnotations: Set<String>,
45         visibleAnnotations: List<AnnotationNode>?,
46         invisibleAnnotations: List<AnnotationNode>?,
47     ): AnnotationNode? {
48     for (an in visibleAnnotations ?: emptyList()) {
49         if (anyAnnotations.contains(an.desc)) {
50             return an
51         }
52     }
53     for (an in invisibleAnnotations ?: emptyList()) {
54         if (anyAnnotations.contains(an.desc)) {
55             return an
56         }
57     }
58     return null
59 }
60 
findAnnotationValueAsStringnull61 fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): String? {
62     for (i in 0..(an.values?.size ?: 0) - 2 step 2) {
63         val name = an.values[i]
64 
65         if (name != propertyName) {
66             continue
67         }
68         val value = an.values[i + 1]
69         if (value is String) {
70             return value
71         }
72         throw ClassParseException(
73                 "The type of '$name' in annotation \"${an.desc}\" must be String" +
74                         ", but is ${value?.javaClass?.canonicalName}")
75     }
76     return null
77 }
78 
79 val periodOrSlash = charArrayOf('.', '/')
80 
getPackageNameFromFullClassNamenull81 fun getPackageNameFromFullClassName(fullClassName: String): String {
82     val pos = fullClassName.lastIndexOfAny(periodOrSlash)
83     if (pos == -1) {
84         return ""
85     } else {
86         return fullClassName.substring(0, pos)
87     }
88 }
89 
getClassNameFromFullClassNamenull90 fun getClassNameFromFullClassName(fullClassName: String): String {
91     val pos = fullClassName.lastIndexOfAny(periodOrSlash)
92     if (pos == -1) {
93         return fullClassName
94     } else {
95         return fullClassName.substring(pos + 1)
96     }
97 }
98 
getOuterClassNameFromFullClassNamenull99 fun getOuterClassNameFromFullClassName(fullClassName: String): String {
100     val start = fullClassName.lastIndexOfAny(periodOrSlash)
101     val end = fullClassName.indexOf('$')
102     if (end == -1) {
103         return fullClassName.substring(start + 1)
104     } else {
105         return fullClassName.substring(start + 1, end)
106     }
107 }
108 
109 /**
110  * If [className] is a fully qualified name, just return it.
111  * Otherwise, prepend [defaultPackageName].
112  */
resolveClassNameWithDefaultPackagenull113 fun resolveClassNameWithDefaultPackage(className: String, defaultPackageName: String): String {
114     if (className.contains('.') || className.contains('/')) {
115         return className
116     }
117     return "$defaultPackageName.$className"
118 }
119 
Stringnull120 fun String.toJvmClassName(): String {
121     return this.replace('.', '/')
122 }
123 
toHumanReadableClassNamenull124 fun String.toHumanReadableClassName(): String {
125     return this.replace('/', '.')
126 }
127 
toHumanReadableMethodNamenull128 fun String.toHumanReadableMethodName(): String {
129     return this.replace('/', '.')
130 }
131 
132 private val numericalInnerClassName = """.*\$\d+$""".toRegex()
133 
isAnonymousInnerClassnull134 fun isAnonymousInnerClass(cn: ClassNode): Boolean {
135     // TODO: Is there a better way?
136     return cn.name.matches(numericalInnerClassName)
137 }
138 
139 /**
140  * Take a class name. If it's a nested class, then return the name of its direct outer class name.
141  * Otherwise, return null.
142  */
getDirectOuterClassNamenull143 fun getDirectOuterClassName(className: String): String? {
144     val pos = className.lastIndexOf('$')
145     if (pos < 0) {
146         return null
147     }
148     return className.substring(0, pos)
149 }
150 
151 /**
152  * Write bytecode to push all the method arguments to the stack.
153  * The number of arguments and their type are taken from [methodDescriptor].
154  */
writeByteCodeToPushArgumentsnull155 fun writeByteCodeToPushArguments(
156         methodDescriptor: String,
157         writer: MethodVisitor,
158         argOffset: Int = 0,
159         ) {
160     var i = argOffset - 1
161     Type.getArgumentTypes(methodDescriptor).forEach { type ->
162         i++
163 
164         // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
165 
166         // Note, long and double will consume two local variable spaces, so the extra `i++`.
167         when (type) {
168             Type.VOID_TYPE -> throw HostStubGenInternalException("VOID_TYPE not expected")
169             Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
170                 -> writer.visitVarInsn(Opcodes.ILOAD, i)
171             Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i)
172             Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
173             Type.DOUBLE_TYPE -> writer.visitVarInsn(Opcodes.DLOAD, i++)
174             else -> writer.visitVarInsn(Opcodes.ALOAD, i)
175         }
176     }
177 }
178 
179 /**
180  * Write bytecode to "RETURN" that matches the method's return type, according to
181  * [methodDescriptor].
182  */
writeByteCodeToReturnnull183 fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) {
184     Type.getReturnType(methodDescriptor).let { type ->
185         // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
186         when (type) {
187             Type.VOID_TYPE -> writer.visitInsn(Opcodes.RETURN)
188             Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
189                 -> writer.visitInsn(Opcodes.IRETURN)
190             Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN)
191             Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
192             Type.DOUBLE_TYPE -> writer.visitInsn(Opcodes.DRETURN)
193             else -> writer.visitInsn(Opcodes.ARETURN)
194         }
195     }
196 }
197 
198 /**
199  * Given a method descriptor, insert an [argType] as the first argument to it.
200  */
prependArgTypeToMethodDescriptornull201 fun prependArgTypeToMethodDescriptor(methodDescriptor: String, argType: Type): String {
202     val returnType = Type.getReturnType(methodDescriptor)
203     val argTypes = Type.getArgumentTypes(methodDescriptor).toMutableList()
204 
205     argTypes.add(0, argType)
206 
207     return Type.getMethodDescriptor(returnType, *argTypes.toTypedArray())
208 }
209 
210 /**
211  * Return the "visibility" modifier from an `access` integer.
212  *
213  * (see https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1)
214  */
getVisibilityModifiernull215 fun getVisibilityModifier(access: Int): Int {
216     return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED)
217 }
218 
219 /**
220  * Return true if an `access` integer is "private" or "package private".
221  */
isVisibilityPrivateOrPackagePrivatenull222 fun isVisibilityPrivateOrPackagePrivate(access: Int): Boolean {
223     return when (getVisibilityModifier(access)) {
224         0 -> true // Package private.
225         Opcodes.ACC_PRIVATE -> true
226         else -> false
227     }
228 }
229 
230 enum class Visibility {
231     PRIVATE,
232     PACKAGE_PRIVATE,
233     PROTECTED,
234     PUBLIC;
235 
236     companion object {
fromAccessnull237         fun fromAccess(access: Int): Visibility {
238             if ((access and Opcodes.ACC_PUBLIC) != 0) {
239                 return PUBLIC
240             }
241             if ((access and Opcodes.ACC_PROTECTED) != 0) {
242                 return PROTECTED
243             }
244             if ((access and Opcodes.ACC_PRIVATE) != 0) {
245                 return PRIVATE
246             }
247 
248             return PACKAGE_PRIVATE
249         }
250     }
251 }
252 
ClassNodenull253 fun ClassNode.isEnum(): Boolean {
254     return (this.access and Opcodes.ACC_ENUM) != 0
255 }
256 
ClassNodenull257 fun ClassNode.isAnnotation(): Boolean {
258     return (this.access and Opcodes.ACC_ANNOTATION) != 0
259 }
260 
ClassNodenull261 fun ClassNode.isSynthetic(): Boolean {
262     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
263 }
264 
MethodNodenull265 fun MethodNode.isSynthetic(): Boolean {
266     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
267 }
268 
isStaticnull269 fun MethodNode.isStatic(): Boolean {
270     return (this.access and Opcodes.ACC_STATIC) != 0
271 }
272 
FieldNodenull273 fun FieldNode.isEnum(): Boolean {
274     return (this.access and Opcodes.ACC_ENUM) != 0
275 }
276 
FieldNodenull277 fun FieldNode.isSynthetic(): Boolean {
278     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
279 }
280 
ClassNodenull281 fun ClassNode.getVisibility(): Visibility {
282     return Visibility.fromAccess(this.access)
283 }
284 
MethodNodenull285 fun MethodNode.getVisibility(): Visibility {
286     return Visibility.fromAccess(this.access)
287 }
288 
FieldNodenull289 fun FieldNode.getVisibility(): Visibility {
290     return Visibility.fromAccess(this.access)
291 }
292 
293 /** Return the [access] flags without the visibility */
clearVisibilitynull294 fun clearVisibility(access: Int): Int {
295     return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE).inv()
296 }
297 
298 /** Return the visibility part of the [access] flags */
getVisibilitynull299 fun getVisibility(access: Int): Int {
300     return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE)
301 }
302 
303 
304 /*
305 
306 Dump of the members of TinyFrameworkEnumSimple:
307 
308 class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple	keep
309   field Cat	keep (ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM)
310   field Dog	keep
311   field $VALUES	keep (ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC)
312 
313   method values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
314     ^- NOT synthetic (ACC_PUBLIC, ACC_STATIC)
315   method valueOf	(Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
316     ^- NOT synthetic (ACC_PUBLIC, ACC_STATIC)
317   method <init>	(Ljava/lang/String;I)V	keep
318     ^- NOT synthetic (ACC_PRIVATE)
319 
320   method $values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
321      (ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC)
322   method <clinit>	()V	keep
323 
324 Dump of the members of TinyFrameworkEnumSimple:
325 
326 class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex	keep
327   field RED	keep
328   field BLUE	keep
329   field GREEN	keep
330   field mLongName	keep
331   field mShortName	keep
332   field $VALUES	keep
333   method values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
334   method valueOf	(Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
335   method <init>	(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V	keep
336   method getLongName	()Ljava/lang/String;	keep
337   method getShortName	()Ljava/lang/String;	keep
338   method $values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
339   method <clinit>	()V	keep
340 
341  */
342 
isAutoGeneratedEnumMembernull343 fun isAutoGeneratedEnumMember(mn: MethodNode): Boolean {
344     if (mn.isSynthetic()) {
345         return true
346     }
347     if (mn.name == "<init>" && mn.desc == "(Ljava/lang/String;I)V") {
348         return true
349     }
350     if (mn.name == "<clinit>" && mn.desc == "()V") {
351         return true
352     }
353     if (mn.name == "values" && mn.desc.startsWith("()")) {
354         return true
355     }
356     if (mn.name == "valueOf" && mn.desc.startsWith("(Ljava/lang/String;)")) {
357         return true
358     }
359 
360     return false
361 }
362 
isAutoGeneratedEnumMembernull363 fun isAutoGeneratedEnumMember(fn: FieldNode): Boolean {
364     if (fn.isSynthetic() || fn.isEnum()) {
365         return true
366     }
367     return false
368 }
369 
370 /**
371  * Class to help handle [ClassVisitor], [MethodVisitor] and [FieldVisitor] in a unified way.
372  */
373 abstract class UnifiedVisitor {
visitAnnotationnull374     abstract fun visitAnnotation(descriptor: String, visible: Boolean)
375 
376     companion object {
377         fun on(target: ClassVisitor): UnifiedVisitor {
378             return object : UnifiedVisitor() {
379                 override fun visitAnnotation(descriptor: String, visible: Boolean) {
380                     target.visitAnnotation(descriptor, visible)
381                 }
382             }
383         }
384 
385         fun on(target: MethodVisitor): UnifiedVisitor {
386             return object : UnifiedVisitor() {
387                 override fun visitAnnotation(descriptor: String, visible: Boolean) {
388                     target.visitAnnotation(descriptor, visible)
389                 }
390             }
391         }
392 
393         fun on(target: FieldVisitor): UnifiedVisitor {
394             return object : UnifiedVisitor() {
395                 override fun visitAnnotation(descriptor: String, visible: Boolean) {
396                     target.visitAnnotation(descriptor, visible)
397                 }
398             }
399         }
400     }
401 }
402