• 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 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     var ret = this
195     if (ret.startsWith("L")) {
196         ret = ret.substring(1)
197     }
198     if (ret.endsWith(";")) {
199         ret = ret.substring(0, ret.length - 1)
200     }
201     return ret.replace('/', '.')
202 }
203 
toHumanReadableMethodNamenull204 fun String.toHumanReadableMethodName(): String {
205     return this.replace('/', '.')
206 }
207 
zipEntryNameToClassNamenull208 fun zipEntryNameToClassName(entryFilename: String): String? {
209     val suffix = ".class"
210     if (!entryFilename.endsWith(suffix)) {
211         return null
212     }
213     return entryFilename.substring(0, entryFilename.length - suffix.length)
214 }
215 
216 private val numericalInnerClassName = """.*\$\d+$""".toRegex()
217 
isAnonymousInnerClassnull218 fun isAnonymousInnerClass(cn: ClassNode): Boolean {
219     // TODO: Is there a better way?
220     return cn.outerClass != null && cn.name.matches(numericalInnerClassName)
221 }
222 
223 /**
224  * Write bytecode to push all the method arguments to the stack.
225  * The number of arguments and their type are taken from [methodDescriptor].
226  */
writeByteCodeToPushArgumentsnull227 fun writeByteCodeToPushArguments(
228         methodDescriptor: String,
229         writer: MethodVisitor,
230         argOffset: Int = 0,
231         ) {
232     var i = argOffset - 1
233     Type.getArgumentTypes(methodDescriptor).forEach { type ->
234         i++
235 
236         // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
237 
238         // Note, long and double will consume two local variable spaces, so the extra `i++`.
239         when (type) {
240             Type.VOID_TYPE -> throw HostStubGenInternalException("VOID_TYPE not expected")
241             Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
242                 -> writer.visitVarInsn(Opcodes.ILOAD, i)
243             Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i)
244             Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
245             Type.DOUBLE_TYPE -> writer.visitVarInsn(Opcodes.DLOAD, i++)
246             else -> writer.visitVarInsn(Opcodes.ALOAD, i)
247         }
248     }
249 }
250 
251 /**
252  * Write bytecode to "RETURN" that matches the method's return type, according to
253  * [methodDescriptor].
254  */
writeByteCodeToReturnnull255 fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) {
256     Type.getReturnType(methodDescriptor).let { type ->
257         // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
258         when (type) {
259             Type.VOID_TYPE -> writer.visitInsn(Opcodes.RETURN)
260             Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
261                 -> writer.visitInsn(Opcodes.IRETURN)
262             Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN)
263             Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
264             Type.DOUBLE_TYPE -> writer.visitInsn(Opcodes.DRETURN)
265             else -> writer.visitInsn(Opcodes.ARETURN)
266         }
267     }
268 }
269 
270 /**
271  * Write bytecode to pop the 2 uninitialized instances out of the stack
272  * after performing constructor redirection.
273  */
adjustStackForConstructorRedirectionnull274 fun adjustStackForConstructorRedirection(writer: MethodVisitor) {
275     // Stack: { uninitialized, uninitialized, obj }
276     writer.visitInsn(Opcodes.SWAP)
277     // Stack: { uninitialized, obj, uninitialized }
278     writer.visitInsn(Opcodes.POP)
279     // Stack: { uninitialized, obj }
280     writer.visitInsn(Opcodes.SWAP)
281     // Stack: { obj, uninitialized }
282     writer.visitInsn(Opcodes.POP)
283     // Stack: { obj }
284 
285     // We end up with only the desired object on the stack
286 }
287 
288 /**
289  * Given a method descriptor, insert an [argType] as the first argument to it.
290  */
prependArgTypeToMethodDescriptornull291 fun prependArgTypeToMethodDescriptor(methodDescriptor: String, classInternalName: String): String {
292     val returnType = Type.getReturnType(methodDescriptor)
293     val argTypes = Type.getArgumentTypes(methodDescriptor).toMutableList()
294 
295     argTypes.add(0, Type.getType("L$classInternalName;"))
296 
297     return Type.getMethodDescriptor(returnType, *argTypes.toTypedArray())
298 }
299 
300 /**
301  * Given a method descriptor, change the return type to [classInternalName].
302  */
changeMethodDescriptorReturnTypenull303 fun changeMethodDescriptorReturnType(methodDescriptor: String, classInternalName: String): String {
304     val argTypes = Type.getArgumentTypes(methodDescriptor)
305     val returnType = Type.getType("L$classInternalName;")
306     return Type.getMethodDescriptor(returnType, *argTypes)
307 }
308 
309 /**
310  * Return the "visibility" modifier from an `access` integer.
311  *
312  * (see https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1)
313  */
getVisibilityModifiernull314 fun getVisibilityModifier(access: Int): Int {
315     return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED)
316 }
317 
318 /**
319  * Return true if an `access` integer is "private" or "package private".
320  */
isVisibilityPrivateOrPackagePrivatenull321 fun isVisibilityPrivateOrPackagePrivate(access: Int): Boolean {
322     return when (getVisibilityModifier(access)) {
323         0 -> true // Package private.
324         Opcodes.ACC_PRIVATE -> true
325         else -> false
326     }
327 }
328 
329 enum class Visibility {
330     PRIVATE,
331     PACKAGE_PRIVATE,
332     PROTECTED,
333     PUBLIC;
334 
335     companion object {
fromAccessnull336         fun fromAccess(access: Int): Visibility {
337             if ((access and Opcodes.ACC_PUBLIC) != 0) {
338                 return PUBLIC
339             }
340             if ((access and Opcodes.ACC_PROTECTED) != 0) {
341                 return PROTECTED
342             }
343             if ((access and Opcodes.ACC_PRIVATE) != 0) {
344                 return PRIVATE
345             }
346 
347             return PACKAGE_PRIVATE
348         }
349     }
350 }
351 
ClassNodenull352 fun ClassNode.isEnum(): Boolean {
353     return (this.access and Opcodes.ACC_ENUM) != 0
354 }
355 
ClassNodenull356 fun ClassNode.isAnnotation(): Boolean {
357     return (this.access and Opcodes.ACC_ANNOTATION) != 0
358 }
359 
ClassNodenull360 fun ClassNode.isSynthetic(): Boolean {
361     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
362 }
363 
ClassNodenull364 fun ClassNode.isAbstract(): Boolean {
365     return (this.access and Opcodes.ACC_ABSTRACT) != 0
366 }
367 
MethodNodenull368 fun MethodNode.isSynthetic(): Boolean {
369     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
370 }
371 
isStaticnull372 fun MethodNode.isStatic(): Boolean {
373     return (this.access and Opcodes.ACC_STATIC) != 0
374 }
375 
MethodNodenull376 fun MethodNode.isPublic(): Boolean {
377     return (this.access and Opcodes.ACC_PUBLIC) != 0
378 }
379 
MethodNodenull380 fun MethodNode.isAbstract(): Boolean {
381     return (this.access and Opcodes.ACC_ABSTRACT) != 0
382 }
383 
MethodNodenull384 fun MethodNode.isNative(): Boolean {
385     return (this.access and Opcodes.ACC_NATIVE) != 0
386 }
387 
MethodNodenull388 fun MethodNode.isSpecial(): Boolean {
389     return CTOR_NAME == this.name || CLASS_INITIALIZER_NAME == this.name
390 }
391 
FieldNodenull392 fun FieldNode.isEnum(): Boolean {
393     return (this.access and Opcodes.ACC_ENUM) != 0
394 }
395 
FieldNodenull396 fun FieldNode.isSynthetic(): Boolean {
397     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
398 }
399 
ClassNodenull400 fun ClassNode.getVisibility(): Visibility {
401     return Visibility.fromAccess(this.access)
402 }
403 
MethodNodenull404 fun MethodNode.getVisibility(): Visibility {
405     return Visibility.fromAccess(this.access)
406 }
407 
FieldNodenull408 fun FieldNode.getVisibility(): Visibility {
409     return Visibility.fromAccess(this.access)
410 }
411 
412 /** Return the [access] flags without the visibility */
clearVisibilitynull413 fun clearVisibility(access: Int): Int {
414     return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE).inv()
415 }
416 
417 /** Return the visibility part of the [access] flags */
getVisibilitynull418 fun getVisibility(access: Int): Int {
419     return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE)
420 }
421 
422 
423 /*
424 
425 Dump of the members of TinyFrameworkEnumSimple:
426 
427 class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple	keep
428   field Cat	keep (ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM)
429   field Dog	keep
430   field $VALUES	keep (ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC)
431 
432   method values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
433     ^- NOT synthetic (ACC_PUBLIC, ACC_STATIC)
434   method valueOf	(Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
435     ^- NOT synthetic (ACC_PUBLIC, ACC_STATIC)
436   method <init>	(Ljava/lang/String;I)V	keep
437     ^- NOT synthetic (ACC_PRIVATE)
438 
439   method $values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
440      (ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC)
441   method <clinit>	()V	keep
442 
443 Dump of the members of TinyFrameworkEnumSimple:
444 
445 class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex	keep
446   field RED	keep
447   field BLUE	keep
448   field GREEN	keep
449   field mLongName	keep
450   field mShortName	keep
451   field $VALUES	keep
452   method values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
453   method valueOf	(Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
454   method <init>	(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V	keep
455   method getLongName	()Ljava/lang/String;	keep
456   method getShortName	()Ljava/lang/String;	keep
457   method $values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
458   method <clinit>	()V	keep
459 
460  */
461 
isAutoGeneratedEnumMembernull462 fun isAutoGeneratedEnumMember(mn: MethodNode): Boolean {
463     if (mn.isSynthetic()) {
464         return true
465     }
466     if (mn.name == "<init>" && mn.desc == "(Ljava/lang/String;I)V") {
467         return true
468     }
469     if (mn.name == "<clinit>" && mn.desc == "()V") {
470         return true
471     }
472     if (mn.name == "values" && mn.desc.startsWith("()")) {
473         return true
474     }
475     if (mn.name == "valueOf" && mn.desc.startsWith("(Ljava/lang/String;)")) {
476         return true
477     }
478 
479     return false
480 }
481 
isAutoGeneratedEnumMembernull482 fun isAutoGeneratedEnumMember(fn: FieldNode): Boolean {
483     if (fn.isSynthetic() || fn.isEnum()) {
484         return true
485     }
486     return false
487 }
488 
489 /**
490  * Class to help handle [ClassVisitor], [MethodVisitor] and [FieldVisitor] in a unified way.
491  */
492 abstract class UnifiedVisitor {
visitAnnotationnull493     abstract fun visitAnnotation(descriptor: String, visible: Boolean)
494 
495     companion object {
496         fun on(target: ClassVisitor): UnifiedVisitor {
497             return object : UnifiedVisitor() {
498                 override fun visitAnnotation(descriptor: String, visible: Boolean) {
499                     target.visitAnnotation(descriptor, visible)
500                 }
501             }
502         }
503 
504         fun on(target: MethodVisitor): UnifiedVisitor {
505             return object : UnifiedVisitor() {
506                 override fun visitAnnotation(descriptor: String, visible: Boolean) {
507                     target.visitAnnotation(descriptor, visible)
508                 }
509             }
510         }
511 
512         fun on(target: FieldVisitor): UnifiedVisitor {
513             return object : UnifiedVisitor() {
514                 override fun visitAnnotation(descriptor: String, visible: Boolean) {
515                     target.visitAnnotation(descriptor, visible)
516                 }
517             }
518         }
519     }
520 }
521