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