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