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