• 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.SdkConstants.DOT_CLASS
21 import com.android.SdkConstants.DOT_JAR
22 import com.android.tools.metalava.model.AnnotationRetention
23 import com.android.tools.metalava.model.Codebase
24 import com.google.common.io.Closer
25 import org.objectweb.asm.ClassReader
26 import org.objectweb.asm.ClassVisitor
27 import org.objectweb.asm.ClassWriter
28 import org.objectweb.asm.Opcodes
29 import org.objectweb.asm.Opcodes.ASM6
30 import java.io.BufferedInputStream
31 import java.io.BufferedOutputStream
32 import java.io.File
33 import java.io.FileInputStream
34 import java.io.FileOutputStream
35 import java.io.IOException
36 import java.nio.file.attribute.FileTime
37 import java.util.jar.JarEntry
38 import java.util.zip.ZipInputStream
39 import java.util.zip.ZipOutputStream
40 import kotlin.text.Charsets.UTF_8
41 
42 /**
43  * Converts public stub annotation sources into package private annotation sources.
44  * This is needed for the stub sources, where we want to reference annotations that aren't
45  * public, but (a) they need to be public during compilation, and (b) they need to be
46  * package private when compiled and packaged on their own such that annotation processors
47  * can find them. See b/110532131 for details.
48  */
49 class RewriteAnnotations {
50     /** Modifies annotation source files such that they are package private */
51     fun modifyAnnotationSources(codebase: Codebase?, source: File, target: File, pkg: String = "") {
52         val fileName = source.name
53         if (fileName.endsWith(SdkConstants.DOT_JAVA)) {
54             if (!options.includeSourceRetentionAnnotations) {
55                 // Only copy non-source retention annotation classes
56                 val qualifiedName = pkg + "." + fileName.substring(0, fileName.indexOf('.'))
57                 if (hasSourceRetention(codebase, qualifiedName)) {
58                     return
59                 }
60             }
61 
62             // Copy and convert
63             target.parentFile.mkdirs()
64             target.writeText(
65                 source.readText(UTF_8).replace(
66                     "\npublic @interface",
67                     "\n@interface"
68                 )
69             )
70         } else if (source.isDirectory) {
71             val newPackage = if (pkg.isEmpty()) fileName else "$pkg.$fileName"
72             source.listFiles()?.forEach {
73                 modifyAnnotationSources(codebase, it, File(target, it.name), newPackage)
74             }
75         }
76     }
77 
78     /** Copies annotation source files from [source] to [target] */
79     fun copyAnnotations(codebase: Codebase, source: File, target: File, pkg: String = "") {
80         val fileName = source.name
81         if (fileName.endsWith(SdkConstants.DOT_JAVA)) {
82             if (!options.includeSourceRetentionAnnotations) {
83                 // Only copy non-source retention annotation classes
84                 val qualifiedName = pkg + "." + fileName.substring(0, fileName.indexOf('.'))
85                 if (hasSourceRetention(codebase, qualifiedName)) {
86                     return
87                 }
88             }
89 
90             // Copy and convert
91             target.parentFile.mkdirs()
92             source.copyTo(target)
93         } else if (source.isDirectory) {
94             val newPackage = if (pkg.isEmpty()) fileName else "$pkg.$fileName"
95             source.listFiles()?.forEach {
96                 copyAnnotations(codebase, it, File(target, it.name), newPackage)
97             }
98         }
99     }
100 
101     /** Writes the bytecode for the compiled annotations in the given file list such that they are package private */
102     fun rewriteAnnotations(files: List<File>) {
103         for (file in files) {
104             // Jump directly into androidx/annotation if it appears we were invoked at the top level
105             if (file.isDirectory) {
106                 val android = File(file, "android${File.separator}annotation/")
107                 if (android.isDirectory) {
108                     rewriteAnnotations(android)
109                     val androidx = File(file, "androidx${File.separator}annotation/")
110                     if (androidx.isDirectory) {
111                         rewriteAnnotations(androidx)
112                     }
113                     continue
114                 }
115             }
116 
117             rewriteAnnotations(file)
118         }
119     }
120 
121     /** Returns true if the given annotation class name has source retention as far as the stub
122      * annotations are concerned.
123      */
124     private fun hasSourceRetention(codebase: Codebase?, qualifiedName: String): Boolean {
125         when {
126             qualifiedName == RECENTLY_NULLABLE ||
127                 qualifiedName == RECENTLY_NONNULL ||
128                 qualifiedName == ANDROID_NULLABLE ||
129                 qualifiedName == ANDROID_NONNULL -> return false
130             qualifiedName.equals(ANDROID_SDK_CONSTANT) -> return true
131             qualifiedName.startsWith("androidx.annotation.") -> return true
132         }
133 
134         // See if the annotation is pointing to an annotation class that is part of the API; if not, skip it.
135         if (codebase != null) {
136             val cls = codebase.findClass(qualifiedName) ?: return true
137             return cls.isAnnotationType() && cls.getRetention() == AnnotationRetention.SOURCE
138         } else {
139             error("Found annotation with unknown desired retention: " + qualifiedName)
140         }
141     }
142 
143     /** Writes the bytecode for the compiled annotations in the given file such that they are package private */
144     private fun rewriteAnnotations(file: File) {
145         when {
146             file.isDirectory -> file.listFiles()?.forEach { rewriteAnnotations(it) }
147             file.path.endsWith(DOT_CLASS) -> rewriteClassFile(file)
148             file.path.endsWith(DOT_JAR) -> rewriteJar(file)
149         }
150     }
151 
152     private fun rewriteClassFile(file: File) {
153         if (file.name.contains("$")) {
154             return // Not worrying about inner classes
155         }
156         val bytes = file.readBytes()
157         val rewritten = rewriteClass(bytes, file.path) ?: return
158         file.writeBytes(rewritten)
159     }
160 
161     private fun rewriteClass(bytes: ByteArray, path: String): ByteArray? {
162         return try {
163             val reader = ClassReader(bytes)
164             rewriteOuterClass(reader)
165         } catch (ioe: IOException) {
166             error("Could not process " + path + ": " + ioe.localizedMessage)
167         }
168     }
169 
170     private fun rewriteOuterClass(reader: ClassReader): ByteArray? {
171         val classWriter = ClassWriter(ASM6)
172         var skip = true
173         val classVisitor = object : ClassVisitor(ASM6, classWriter) {
174             override fun visit(
175                 version: Int,
176                 access: Int,
177                 name: String,
178                 signature: String?,
179                 superName: String?,
180                 interfaces: Array<out String>?
181             ) {
182                 // Only process public annotations in android.annotation and androidx.annotation
183                 if (access and Opcodes.ACC_PUBLIC != 0 &&
184                     access and Opcodes.ACC_ANNOTATION != 0 &&
185                     (name.startsWith("android/annotation/") || name.startsWith("androidx/annotation/"))
186                 ) {
187                     skip = false
188                     val flagsWithoutPublic = access and Opcodes.ACC_PUBLIC.inv()
189                     super.visit(version, flagsWithoutPublic, name, signature, superName, interfaces)
190                 }
191             }
192         }
193 
194         reader.accept(classVisitor, 0)
195         return if (skip) {
196             null
197         } else {
198             classWriter.toByteArray()
199         }
200     }
201 
202     private fun rewriteJar(file: File) {
203         val temp = File(file.path + ".temp-$PROGRAM_NAME")
204         rewriteJar(file, temp)
205         file.delete()
206         temp.renameTo(file)
207     }
208 
209     private val zeroTime = FileTime.fromMillis(0)
210 
211     private fun rewriteJar(from: File, to: File/*, filter: Predicate<String>?*/) {
212         Closer.create().use { closer ->
213             val fos = closer.register(FileOutputStream(to))
214             val bos = closer.register(BufferedOutputStream(fos))
215             val zos = closer.register(ZipOutputStream(bos))
216 
217             val fis = closer.register(FileInputStream(from))
218             val bis = closer.register(BufferedInputStream(fis))
219             val zis = closer.register(ZipInputStream(bis))
220 
221             while (true) {
222                 val entry = zis.nextEntry ?: break
223                 val name = entry.name
224                 val newEntry: JarEntry
225 
226                 // Preserve the STORED method of the input entry.
227                 newEntry = if (entry.method == JarEntry.STORED) {
228                     val jarEntry = JarEntry(entry)
229                     jarEntry.size = entry.size
230                     jarEntry.compressedSize = entry.compressedSize
231                     jarEntry.crc = entry.crc
232                     jarEntry
233                 } else {
234                     // Create a new entry so that the compressed len is recomputed.
235                     JarEntry(name)
236                 }
237 
238                 newEntry.lastAccessTime = zeroTime
239                 newEntry.creationTime = zeroTime
240                 newEntry.lastModifiedTime = entry.lastModifiedTime
241 
242                 // add the entry to the jar archive
243                 zos.putNextEntry(newEntry)
244 
245                 // read the content of the entry from the input stream, and write it into the archive.
246                 if (name.endsWith(DOT_CLASS) &&
247                     (name.startsWith("android/annotation/") || name.startsWith("androidx/annotation/")) &&
248                     name.indexOf("$") == -1 &&
249                     !entry.isDirectory
250                 ) {
251                     val bytes = zis.readBytes()
252                     val rewritten = rewriteClass(bytes, name)
253                     if (rewritten != null) {
254                         zos.write(rewritten)
255                     } else {
256                         zos.write(bytes)
257                     }
258                 } else {
259                     zis.copyTo(zos)
260                 }
261 
262                 zos.closeEntry()
263                 zis.closeEntry()
264             }
265         }
266     }
267 }
268