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