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