• 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.startsWith("androidx.annotation.") -> return true
131         }
132 
133         // See if the annotation is pointing to an annotation class that is part of the API; if not, skip it.
134         if (codebase != null) {
135             val cls = codebase.findClass(qualifiedName) ?: return true
136             return cls.isAnnotationType() && cls.getRetention() == AnnotationRetention.SOURCE
137         }
138 
139         return false
140     }
141 
142     /** Writes the bytecode for the compiled annotations in the given file such that they are package private */
143     private fun rewriteAnnotations(file: File) {
144         when {
145             file.isDirectory -> file.listFiles()?.forEach { rewriteAnnotations(it) }
146             file.path.endsWith(DOT_CLASS) -> rewriteClassFile(file)
147             file.path.endsWith(DOT_JAR) -> rewriteJar(file)
148         }
149     }
150 
151     private fun rewriteClassFile(file: File) {
152         if (file.name.contains("$")) {
153             return // Not worrying about inner classes
154         }
155         val bytes = file.readBytes()
156         val rewritten = rewriteClass(bytes, file.path) ?: return
157         file.writeBytes(rewritten)
158     }
159 
160     private fun rewriteClass(bytes: ByteArray, path: String): ByteArray? {
161         return try {
162             val reader = ClassReader(bytes)
163             rewriteOuterClass(reader)
164         } catch (ioe: IOException) {
165             error("Could not process " + path + ": " + ioe.localizedMessage)
166         }
167     }
168 
169     private fun rewriteOuterClass(reader: ClassReader): ByteArray? {
170         val classWriter = ClassWriter(ASM6)
171         var skip = true
172         val classVisitor = object : ClassVisitor(ASM6, classWriter) {
173             override fun visit(
174                 version: Int,
175                 access: Int,
176                 name: String,
177                 signature: String?,
178                 superName: String?,
179                 interfaces: Array<out String>?
180             ) {
181                 // Only process public annotations in android.annotation and androidx.annotation
182                 if (access and Opcodes.ACC_PUBLIC != 0 &&
183                     access and Opcodes.ACC_ANNOTATION != 0 &&
184                     (name.startsWith("android/annotation/") || name.startsWith("androidx/annotation/"))
185                 ) {
186                     skip = false
187                     val flagsWithoutPublic = access and Opcodes.ACC_PUBLIC.inv()
188                     super.visit(version, flagsWithoutPublic, name, signature, superName, interfaces)
189                 }
190             }
191         }
192 
193         reader.accept(classVisitor, 0)
194         return if (skip) {
195             null
196         } else {
197             classWriter.toByteArray()
198         }
199     }
200 
201     private fun rewriteJar(file: File) {
202         val temp = File(file.path + ".temp-$PROGRAM_NAME")
203         rewriteJar(file, temp)
204         file.delete()
205         temp.renameTo(file)
206     }
207 
208     private val zeroTime = FileTime.fromMillis(0)
209 
210     private fun rewriteJar(from: File, to: File/*, filter: Predicate<String>?*/) {
211         Closer.create().use { closer ->
212             val fos = closer.register(FileOutputStream(to))
213             val bos = closer.register(BufferedOutputStream(fos))
214             val zos = closer.register(ZipOutputStream(bos))
215 
216             val fis = closer.register(FileInputStream(from))
217             val bis = closer.register(BufferedInputStream(fis))
218             val zis = closer.register(ZipInputStream(bis))
219 
220             while (true) {
221                 val entry = zis.nextEntry ?: break
222                 val name = entry.name
223                 val newEntry: JarEntry
224 
225                 // Preserve the STORED method of the input entry.
226                 newEntry = if (entry.method == JarEntry.STORED) {
227                     val jarEntry = JarEntry(entry)
228                     jarEntry.size = entry.size
229                     jarEntry.compressedSize = entry.compressedSize
230                     jarEntry.crc = entry.crc
231                     jarEntry
232                 } else {
233                     // Create a new entry so that the compressed len is recomputed.
234                     JarEntry(name)
235                 }
236 
237                 newEntry.lastAccessTime = zeroTime
238                 newEntry.creationTime = zeroTime
239                 newEntry.lastModifiedTime = entry.lastModifiedTime
240 
241                 // add the entry to the jar archive
242                 zos.putNextEntry(newEntry)
243 
244                 // read the content of the entry from the input stream, and write it into the archive.
245                 if (name.endsWith(DOT_CLASS) &&
246                     (name.startsWith("android/annotation/") || name.startsWith("androidx/annotation/")) &&
247                     name.indexOf("$") == -1 &&
248                     !entry.isDirectory
249                 ) {
250                     val bytes = zis.readBytes()
251                     val rewritten = rewriteClass(bytes, name)
252                     if (rewritten != null) {
253                         zos.write(rewritten)
254                     } else {
255                         zos.write(bytes)
256                     }
257                 } else {
258                     zis.copyTo(zos)
259                 }
260 
261                 zos.closeEntry()
262                 zis.closeEntry()
263             }
264         }
265     }
266 }
267