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