• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.model.Codebase
21 import com.android.tools.metalava.model.FieldItem
22 import com.android.tools.metalava.model.Item
23 import com.android.tools.metalava.model.MethodItem
24 import com.android.tools.metalava.model.PackageItem
25 import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
26 import com.android.tools.metalava.model.visitors.ApiVisitor
27 import com.google.common.io.ByteStreams
28 import org.objectweb.asm.ClassReader
29 import org.objectweb.asm.Opcodes
30 import org.objectweb.asm.tree.ClassNode
31 import org.objectweb.asm.tree.FieldNode
32 import org.objectweb.asm.tree.MethodNode
33 import java.io.File
34 import java.io.IOException
35 import java.util.function.Predicate
36 import java.util.zip.ZipFile
37 
38 /**
39  * In an Android source tree, rewrite the signature files in prebuilts/sdk
40  * by reading what's actually there in the android.jar files.
41  */
42 class ConvertJarsToSignatureFiles {
43     fun convertJars(root: File) {
44         var api = 1
45         while (true) {
46             val apiJar = File(
47                 root,
48                 if (api <= 3)
49                     "prebuilts/tools/common/api-versions/android-$api/android.jar"
50                 else
51                     "prebuilts/sdk/$api/public/android.jar"
52             )
53             if (!apiJar.isFile) {
54                 break
55             }
56             val signatureFile = "prebuilts/sdk/$api/public/api/android.txt"
57             val oldApiFile = File(root, "prebuilts/sdk/$api/public/api/android.txt")
58             val newApiFile =
59                 // Place new-style signature files in separate files?
60                 // File(root, "prebuilts/sdk/$api/public/api/android.${if (options.compatOutput) "txt" else "v2.txt"}")
61                 File(root, "prebuilts/sdk/$api/public/api/android.txt")
62 
63             progress("\nWriting signature files $signatureFile for $apiJar")
64 
65             // Treat android.jar file as not filtered since they contain misc stuff that shouldn't be
66             // there: package private super classes etc.
67             val jarCodebase = loadFromJarFile(apiJar, null, preFiltered = false)
68             val apiEmit = ApiType.PUBLIC_API.getEmitFilter()
69             val apiReference = ApiType.PUBLIC_API.getReferenceFilter()
70 
71             if (api >= 28) {
72                 // As of API 28 we'll put nullness annotations into the jar but some of them
73                 // may be @RecentlyNullable/@RecentlyNonNull. Translate these back into
74                 // normal @Nullable/@NonNull
75                 jarCodebase.accept(object : ApiVisitor() {
76                     override fun visitItem(item: Item) {
77                         unmarkRecent(item)
78                         super.visitItem(item)
79                     }
80 
81                     private fun unmarkRecent(new: Item) {
82                         val annotation = NullnessMigration.findNullnessAnnotation(new) ?: return
83                         // Nullness information change: Add migration annotation
84                         val annotationClass = if (annotation.isNullable()) ANDROIDX_NULLABLE else ANDROIDX_NONNULL
85 
86                         val modifiers = new.mutableModifiers()
87                         modifiers.removeAnnotation(annotation)
88 
89                         // Don't map annotation names - this would turn newly non null back into non null
90                         modifiers.addAnnotation(new.codebase.createAnnotation("@$annotationClass", new, mapName = false))
91                     }
92                 })
93                 assert(!SUPPORT_TYPE_USE_ANNOTATIONS) { "We'll need to rewrite type annotations here too" }
94             }
95 
96             // Sadly the old signature files have some APIs recorded as deprecated which
97             // are not in fact deprecated in the jar files. Try to pull this back in.
98 
99             val oldRemovedFile = File(root, "prebuilts/sdk/$api/public/api/removed.txt")
100             if (oldRemovedFile.isFile) {
101                 val oldCodebase = SignatureFileLoader.load(oldRemovedFile)
102                 val visitor = object : ComparisonVisitor() {
103                     override fun compare(old: MethodItem, new: MethodItem) {
104                         new.removed = true
105                         progress("\nRemoved $old")
106                     }
107 
108                     override fun compare(old: FieldItem, new: FieldItem) {
109                         new.removed = true
110                         progress("\nRemoved $old")
111                     }
112                 }
113                 CodebaseComparator().compare(visitor, oldCodebase, jarCodebase, null)
114             }
115 
116             // Read deprecated attributes. Seem to be missing from code model;
117             // try to read via ASM instead since it must clearly be there.
118             markDeprecated(jarCodebase, apiJar, apiJar.path)
119 
120             // ASM doesn't seem to pick up everything that's actually there according to
121             // javap. So as another fallback, read from the existing signature files:
122             if (oldApiFile.isFile) {
123                 val oldCodebase = SignatureFileLoader.load(oldApiFile)
124                 val visitor = object : ComparisonVisitor() {
125                     override fun compare(old: Item, new: Item) {
126                         if (old.deprecated && !new.deprecated && old !is PackageItem) {
127                             new.deprecated = true
128                             progress("\nRecorded deprecation from previous signature file for $old")
129                         }
130                     }
131                 }
132                 CodebaseComparator().compare(visitor, oldCodebase, jarCodebase, null)
133             }
134 
135             createReportFile(jarCodebase, newApiFile, "API") { printWriter ->
136                 SignatureWriter(printWriter, apiEmit, apiReference, jarCodebase.preFiltered)
137             }
138 
139             // Delete older redundant .xml files
140             val xmlFile = File(newApiFile.parentFile, "android.xml")
141             if (xmlFile.isFile) {
142                 xmlFile.delete()
143             }
144 
145             api++
146         }
147     }
148 
149     private fun markDeprecated(codebase: Codebase, file: File, path: String) {
150         when {
151             file.name.endsWith(SdkConstants.DOT_JAR) -> try {
152                 ZipFile(file).use { jar ->
153                     val enumeration = jar.entries()
154                     while (enumeration.hasMoreElements()) {
155                         val entry = enumeration.nextElement()
156                         if (entry.name.endsWith(SdkConstants.DOT_CLASS)) {
157                             try {
158                                 jar.getInputStream(entry).use { `is` ->
159                                     val bytes = ByteStreams.toByteArray(`is`)
160                                     if (bytes != null) {
161                                         markDeprecated(codebase, bytes, path + ":" + entry.name)
162                                     }
163                                 }
164                             } catch (e: Exception) {
165                                 options.stdout.println("Could not read jar file entry ${entry.name} from $file: $e")
166                             }
167                         }
168                     }
169                 }
170             } catch (e: IOException) {
171                 options.stdout.println("Could not read jar file contents from $file: $e")
172             }
173             file.isDirectory -> {
174                 val listFiles = file.listFiles()
175                 listFiles?.forEach {
176                     markDeprecated(codebase, it, it.path)
177                 }
178             }
179             file.path.endsWith(SdkConstants.DOT_CLASS) -> {
180                 val bytes = file.readBytes()
181                 markDeprecated(codebase, bytes, file.path)
182             }
183             else -> options.stdout.println("Ignoring entry $file")
184         }
185     }
186 
187     private fun markDeprecated(codebase: Codebase, bytes: ByteArray, path: String) {
188         val reader: ClassReader
189         val classNode: ClassNode
190         try {
191             // TODO: We don't actually need to build a DOM.
192             reader = ClassReader(bytes)
193             classNode = ClassNode()
194             reader.accept(classNode, 0)
195         } catch (t: Throwable) {
196             options.stderr.println("Error processing $path: broken class file?")
197             return
198         }
199 
200         if ((classNode.access and Opcodes.ACC_DEPRECATED) != 0) {
201             val item = codebase.findClass(classNode, MATCH_ALL)
202             if (item != null && !item.deprecated) {
203                 item.deprecated = true
204                 progress("\nTurned deprecation on for $item")
205             }
206         }
207 
208         val methodList = classNode.methods
209         for (f in methodList) {
210             val methodNode = f as MethodNode
211             if ((methodNode.access and Opcodes.ACC_DEPRECATED) == 0) {
212                 continue
213             }
214             val item = codebase.findMethod(classNode, methodNode, MATCH_ALL)
215             if (item != null && !item.deprecated) {
216                 item.deprecated = true
217                 progress("\nTurned deprecation on for $item")
218             }
219         }
220 
221         val fieldList = classNode.fields
222         for (f in fieldList) {
223             val fieldNode = f as FieldNode
224             if ((fieldNode.access and Opcodes.ACC_DEPRECATED) == 0) {
225                 continue
226             }
227             val item = codebase.findField(classNode, fieldNode, MATCH_ALL)
228             if (item != null && !item.deprecated) {
229                 item.deprecated = true
230                 progress("\nTurned deprecation on for $item")
231             }
232         }
233     }
234 
235     companion object {
236         val MATCH_ALL: Predicate<Item> = Predicate { true }
237     }
238 }