1 /*
<lambda>null2 * Copyright (C) 2018 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 com.android.tools.metalava
18
19 import com.android.SdkConstants
20 import com.android.tools.metalava.cli.common.SignatureFileLoader
21 import com.android.tools.metalava.model.ANDROIDX_NONNULL
22 import com.android.tools.metalava.model.ANDROIDX_NULLABLE
23 import com.android.tools.metalava.model.ClassItem
24 import com.android.tools.metalava.model.Codebase
25 import com.android.tools.metalava.model.FieldItem
26 import com.android.tools.metalava.model.Item
27 import com.android.tools.metalava.model.MethodItem
28 import com.android.tools.metalava.model.PackageItem
29 import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
30 import com.android.tools.metalava.model.text.FileFormat
31 import com.android.tools.metalava.model.text.SignatureFile
32 import com.android.tools.metalava.model.visitors.ApiVisitor
33 import java.io.File
34 import java.io.IOException
35 import java.io.PrintWriter
36 import java.util.function.Predicate
37 import java.util.zip.ZipFile
38 import org.objectweb.asm.ClassReader
39 import org.objectweb.asm.Opcodes
40 import org.objectweb.asm.Type
41 import org.objectweb.asm.tree.ClassNode
42 import org.objectweb.asm.tree.FieldNode
43 import org.objectweb.asm.tree.MethodNode
44
45 /**
46 * In an Android source tree, rewrite the signature files in prebuilts/sdk by reading what's
47 * actually there in the android.jar files.
48 */
49 class ConvertJarsToSignatureFiles(
50 private val stderr: PrintWriter,
51 private val stdout: PrintWriter,
52 private val progressTracker: ProgressTracker,
53 private val fileFormat: FileFormat,
54 ) {
55 fun convertJars(jarCodebaseLoader: JarCodebaseLoader, root: File) {
56 var api = 1
57 while (true) {
58 val apiJar =
59 File(
60 root,
61 if (api <= 3) "prebuilts/tools/common/api-versions/android-$api/android.jar"
62 else "prebuilts/sdk/$api/public/android.jar"
63 )
64 if (!apiJar.isFile) {
65 break
66 }
67 val signatureFile = "prebuilts/sdk/$api/public/api/android.txt"
68 val oldApiFile = File(root, "prebuilts/sdk/$api/public/api/android.txt")
69 val newApiFile =
70 // Place new-style signature files in separate files?
71 File(root, "prebuilts/sdk/$api/public/api/android.txt")
72
73 progressTracker.progress("Writing signature files $signatureFile for $apiJar")
74
75 val annotationManager = DefaultAnnotationManager()
76 val signatureFileLoader = SignatureFileLoader(annotationManager = annotationManager)
77
78 val jarCodebase = jarCodebaseLoader.loadFromJarFile(apiJar)
79 val apiPredicateConfig = ApiPredicate.Config()
80 val apiEmit = ApiType.PUBLIC_API.getEmitFilter(apiPredicateConfig)
81 val apiReference = ApiType.PUBLIC_API.getReferenceFilter(apiPredicateConfig)
82
83 if (api >= 28) {
84 // As of API 28 we'll put nullness annotations into the jar but some of them
85 // may be @RecentlyNullable/@RecentlyNonNull. Translate these back into
86 // normal @Nullable/@NonNull
87 jarCodebase.accept(
88 object :
89 ApiVisitor(
90 config = ApiVisitor.Config(),
91 ) {
92 override fun visitItem(item: Item) {
93 unmarkRecent(item)
94 super.visitItem(item)
95 }
96
97 private fun unmarkRecent(new: Item) {
98 val annotation = NullnessMigration.findNullnessAnnotation(new) ?: return
99 // Nullness information change: Add migration annotation
100 val annotationClass =
101 if (annotation.isNullable()) ANDROIDX_NULLABLE else ANDROIDX_NONNULL
102
103 val modifiers = new.mutableModifiers()
104 modifiers.removeAnnotation(annotation)
105
106 modifiers.addAnnotation(
107 new.codebase.createAnnotation(
108 "@$annotationClass",
109 new,
110 )
111 )
112 }
113 }
114 )
115 assert(!SUPPORT_TYPE_USE_ANNOTATIONS) {
116 "We'll need to rewrite type annotations here too"
117 }
118 }
119
120 // Read deprecated attributes. Seem to be missing from code model;
121 // try to read via ASM instead since it must clearly be there.
122 markDeprecated(jarCodebase, apiJar, apiJar.path)
123
124 // ASM doesn't seem to pick up everything that's actually there according to
125 // javap. So as another fallback, read from the existing signature files:
126 if (oldApiFile.isFile) {
127 try {
128 val oldCodebase = signatureFileLoader.load(SignatureFile.fromFile(oldApiFile))
129 val visitor =
130 object : ComparisonVisitor() {
131 override fun compare(old: Item, new: Item) {
132 if (old.originallyDeprecated && old !is PackageItem) {
133 new.deprecateIfRequired("previous signature file for $old")
134 }
135 }
136 }
137 CodebaseComparator(apiVisitorConfig = ApiVisitor.Config())
138 .compare(visitor, oldCodebase, jarCodebase, null)
139 } catch (e: Exception) {
140 throw IllegalStateException("Could not load $oldApiFile: ${e.message}", e)
141 }
142 }
143
144 createReportFile(progressTracker, jarCodebase, newApiFile, "API") { printWriter ->
145 SignatureWriter(
146 printWriter,
147 apiEmit,
148 apiReference,
149 jarCodebase.preFiltered,
150 fileFormat = fileFormat,
151 showUnannotated = false,
152 apiVisitorConfig = ApiVisitor.Config(),
153 )
154 }
155
156 // Delete older redundant .xml files
157 val xmlFile = File(newApiFile.parentFile, "android.xml")
158 if (xmlFile.isFile) {
159 xmlFile.delete()
160 }
161
162 api++
163 }
164 }
165
166 private fun markDeprecated(codebase: Codebase, file: File, path: String) {
167 when {
168 file.name.endsWith(SdkConstants.DOT_JAR) ->
169 try {
170 ZipFile(file).use { jar ->
171 val enumeration = jar.entries()
172 while (enumeration.hasMoreElements()) {
173 val entry = enumeration.nextElement()
174 if (entry.name.endsWith(SdkConstants.DOT_CLASS)) {
175 try {
176 jar.getInputStream(entry).use { inputStream ->
177 val bytes = inputStream.readBytes()
178 markDeprecated(codebase, bytes, path + ":" + entry.name)
179 }
180 } catch (e: Exception) {
181 stdout.println(
182 "Could not read jar file entry ${entry.name} from $file: $e"
183 )
184 }
185 }
186 }
187 }
188 } catch (e: IOException) {
189 stdout.println("Could not read jar file contents from $file: $e")
190 }
191 file.isDirectory -> {
192 val listFiles = file.listFiles()
193 listFiles?.forEach { markDeprecated(codebase, it, it.path) }
194 }
195 file.path.endsWith(SdkConstants.DOT_CLASS) -> {
196 val bytes = file.readBytes()
197 markDeprecated(codebase, bytes, file.path)
198 }
199 else -> stdout.println("Ignoring entry $file")
200 }
201 }
202
203 private fun markDeprecated(codebase: Codebase, bytes: ByteArray, path: String) {
204 val reader: ClassReader
205 val classNode: ClassNode
206 try {
207 // TODO: We don't actually need to build a DOM.
208 reader = ClassReader(bytes)
209 classNode = ClassNode()
210 reader.accept(classNode, 0)
211 } catch (t: Throwable) {
212 stderr.println("Error processing $path: broken class file?")
213 return
214 }
215
216 if ((classNode.access and Opcodes.ACC_DEPRECATED) != 0) {
217 val item = codebase.findClass(classNode, MATCH_ALL)
218 item.deprecateIfRequired("byte code for ${classNode.name}")
219 }
220
221 val methodList = classNode.methods
222 for (f in methodList) {
223 val methodNode = f as MethodNode
224 if ((methodNode.access and Opcodes.ACC_DEPRECATED) == 0) {
225 continue
226 }
227 val item = codebase.findMethod(classNode, methodNode, MATCH_ALL)
228 item.deprecateIfRequired("byte code for ${methodNode.name}")
229 }
230
231 val fieldList = classNode.fields
232 for (f in fieldList) {
233 val fieldNode = f as FieldNode
234 if ((fieldNode.access and Opcodes.ACC_DEPRECATED) == 0) {
235 continue
236 }
237 val item = codebase.findField(classNode, fieldNode, MATCH_ALL)
238 item.deprecateIfRequired("byte code for ${fieldNode.name}")
239 }
240 }
241
242 /** Mark the [Item] as deprecated if required. */
243 private fun Item?.deprecateIfRequired(source: String) {
244 this ?: return
245 if (!originallyDeprecated) {
246 // Set the deprecated flag in the modifiers which underpins [originallyDeprecated].
247 mutableModifiers().setDeprecated(true)
248 progressTracker.progress("Turned deprecation on for $this from $source")
249 }
250 }
251
252 companion object {
253 val MATCH_ALL: Predicate<Item> = Predicate { true }
254 }
255 }
256
257 /** Finds the given class by JVM owner */
Codebasenull258 private fun Codebase.findClassByOwner(owner: String, apiFilter: Predicate<Item>): ClassItem? {
259 val className = owner.replace('/', '.').replace('$', '.')
260 val cls = findClass(className)
261 return if (cls != null && apiFilter.test(cls)) {
262 cls
263 } else {
264 null
265 }
266 }
267
Codebasenull268 private fun Codebase.findClass(node: ClassNode, apiFilter: Predicate<Item>): ClassItem? {
269 return findClassByOwner(node.name, apiFilter)
270 }
271
findMethodnull272 private fun Codebase.findMethod(
273 classNode: ClassNode,
274 node: MethodNode,
275 apiFilter: Predicate<Item>
276 ): MethodItem? {
277 val cls = findClass(classNode, apiFilter) ?: return null
278 val types = Type.getArgumentTypes(node.desc)
279 val parameters =
280 if (types.isNotEmpty()) {
281 val sb = StringBuilder()
282 for (type in types) {
283 if (sb.isNotEmpty()) {
284 sb.append(", ")
285 }
286 sb.append(type.className.replace('/', '.').replace('$', '.'))
287 }
288 sb.toString()
289 } else {
290 ""
291 }
292 val methodName = if (node.name == "<init>") cls.simpleName() else node.name
293 val method = cls.findMethod(methodName, parameters)
294 return if (method != null && apiFilter.test(method)) {
295 method
296 } else {
297 null
298 }
299 }
300
Codebasenull301 private fun Codebase.findField(
302 classNode: ClassNode,
303 node: FieldNode,
304 apiFilter: Predicate<Item>
305 ): FieldItem? {
306 val cls = findClass(classNode, apiFilter) ?: return null
307 val field = cls.findField(node.name + 2)
308 return if (field != null && apiFilter.test(field)) {
309 field
310 } else {
311 null
312 }
313 }
314