1 /*
<lambda>null2 * Copyright 2022 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
17 package androidx.privacysandbox.tools.apigenerator.parser
18
19 import androidx.privacysandbox.tools.PrivacySandboxCallback
20 import androidx.privacysandbox.tools.PrivacySandboxInterface
21 import androidx.privacysandbox.tools.PrivacySandboxService
22 import androidx.privacysandbox.tools.PrivacySandboxValue
23 import androidx.privacysandbox.tools.core.PrivacySandboxParsingException
24 import androidx.privacysandbox.tools.core.model.Constant
25 import androidx.privacysandbox.tools.core.model.Types
26 import java.nio.file.Path
27 import kotlin.metadata.KmClass
28 import kotlin.metadata.jvm.KotlinClassMetadata
29 import kotlin.metadata.jvm.Metadata
30 import org.objectweb.asm.ClassReader
31 import org.objectweb.asm.Opcodes
32 import org.objectweb.asm.Type
33 import org.objectweb.asm.tree.AnnotationNode
34 import org.objectweb.asm.tree.ClassNode
35
36 data class AnnotatedClasses(
37 val services: Set<ClassAndConstants>,
38 val values: Set<ClassAndConstants>,
39 val callbacks: Set<ClassAndConstants>,
40 val interfaces: Set<ClassAndConstants>,
41 )
42
43 data class ClassAndConstants(
44 val kClass: KmClass,
45 val constants: List<Constant>,
46 )
47
48 internal object AnnotatedClassReader {
49 val annotations = listOf(PrivacySandboxService::class)
50
51 fun readAnnotatedClasses(stubClassPath: Path): AnnotatedClasses {
52 val services = mutableSetOf<ClassAndConstants>()
53 val values = mutableSetOf<ClassAndConstants>()
54 val callbacks = mutableSetOf<ClassAndConstants>()
55 val interfaces = mutableSetOf<ClassAndConstants>()
56
57 stubClassPath
58 .toFile()
59 .walk()
60 .filter { it.isFile && it.extension == "class" }
61 .map { toClassNode(it.readBytes()) }
62 .forEach { classNode ->
63 // Data classes and enum classes store their constants on the object itself, rather
64 // than in the companion's class file, so we extract the constants from amongst the
65 // other fields on the annotated value/interface.
66 // Thankfully, data class fields are always non-static, and enum variants are always
67 // of the enum's type (hence not primitive or string, which consts must be).
68 // The const-allowed-types check also filters out the Companion and the VALUES
69 // array.
70 val constants =
71 classNode.fields
72 .filter { it.access.hasFlag(PUBLIC_STATIC_FINAL_ACCESS) }
73 .filter { it.desc in constAllowedTypes.keys }
74 .map { Constant(it.name, getConstType(it.desc), it.value) }
75 .toList()
76 if (classNode.isAnnotatedWith<PrivacySandboxService>()) {
77 services.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
78 }
79 // TODO(b/323369085): Validate that enum variants don't have methods
80 if (classNode.isAnnotatedWith<PrivacySandboxValue>()) {
81 values.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
82 }
83 if (classNode.isAnnotatedWith<PrivacySandboxCallback>()) {
84 callbacks.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
85 }
86 if (classNode.isAnnotatedWith<PrivacySandboxInterface>()) {
87 interfaces.add(ClassAndConstants(parseKotlinMetadata(classNode), constants))
88 }
89 }
90 return AnnotatedClasses(
91 services = services.toSet(),
92 values = values.toSet(),
93 callbacks = callbacks.toSet(),
94 interfaces = interfaces.toSet()
95 )
96 }
97
98 private fun toClassNode(classContents: ByteArray): ClassNode {
99 val reader = ClassReader(classContents)
100 val classNode = ClassNode(Opcodes.ASM9)
101 reader.accept(
102 classNode,
103 ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
104 )
105 return classNode
106 }
107
108 private fun parseKotlinMetadata(classNode: ClassNode): KmClass {
109 val metadataValues =
110 classNode.visibleAnnotationsWithType<Metadata>().firstOrNull()?.attributeMap
111 ?: throw PrivacySandboxParsingException(
112 "Missing Kotlin metadata annotation in ${classNode.name}. " +
113 "Is this a valid Kotlin class?"
114 )
115
116 // ASM models annotation attributes as flat List<Objects>, so the unchecked cast is
117 // inevitable when some of these objects have type parameters, like the lists below.
118 @Suppress("UNCHECKED_CAST")
119 val metadataAnnotation =
120 Metadata(
121 kind = metadataValues["k"] as Int?,
122 metadataVersion = (metadataValues["mv"] as? List<Int>?)?.toIntArray(),
123 data1 = (metadataValues["d1"] as? List<String>?)?.toTypedArray(),
124 data2 = (metadataValues["d2"] as? List<String>?)?.toTypedArray(),
125 extraInt = metadataValues["xi"] as? Int?,
126 packageName = metadataValues["pn"] as? String?,
127 extraString = metadataValues["xs"] as? String?,
128 )
129
130 return when (val metadata = KotlinClassMetadata.readStrict(metadataAnnotation)) {
131 is KotlinClassMetadata.Class -> metadata.kmClass
132 else ->
133 throw PrivacySandboxParsingException(
134 "Unable to parse Kotlin metadata from ${classNode.name}. " +
135 "Is this a valid Kotlin class?"
136 )
137 }
138 }
139
140 private inline fun <reified T> ClassNode.isAnnotatedWith(): Boolean {
141 return visibleAnnotationsWithType<T>().isNotEmpty()
142 }
143
144 private inline fun <reified T> ClassNode.visibleAnnotationsWithType(): List<AnnotationNode> {
145 return (visibleAnnotations ?: listOf<AnnotationNode>())
146 .filter { Type.getDescriptor(T::class.java) == it?.desc }
147 .filterNotNull()
148 }
149
150 /**
151 * Map of annotation attributes. This is a convenience wrapper around [AnnotationNode.values].
152 */
153 private val AnnotationNode.attributeMap: Map<String, Any>
154 get() {
155 values ?: return mapOf()
156 val attributes = mutableMapOf<String, Any>()
157 for (i in 0 until values.size step 2) {
158 attributes[values[i] as String] = values[i + 1]
159 }
160 return attributes
161 }
162
163 private val constAllowedTypes =
164 mapOf(
165 "Ljava/lang/String;" to Types.string,
166 "I" to Types.int,
167 "Z" to Types.boolean,
168 "B" to Types.byte,
169 "C" to Types.char,
170 "D" to Types.double,
171 "F" to Types.float,
172 "J" to Types.long,
173 "S" to Types.short
174 )
175
176 private fun getConstType(desc: String): androidx.privacysandbox.tools.core.model.Type {
177 return constAllowedTypes[desc]
178 ?: throw PrivacySandboxParsingException("Unrecognised constant type: '$desc'")
179 }
180 }
181
182 // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5
183 // TODO: Once we upgrade to Java 22 we can import these constants from
184 // java.lang.classfile
185 private const val PUBLIC_STATIC_FINAL_ACCESS =
186 0x0001 or // public
187 0x0008 or // static
188 0x0010 // final
189
hasFlagnull190 private fun Int.hasFlag(flag: Int) = flag and this == flag
191