blob: 086cf466f953c76f2cc8ca046e163412b366993f [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.build.gradle.tasks
import com.android.SdkConstants
import jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE
import jdk.internal.org.objectweb.asm.Opcodes.ASM5
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.Type
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.Label
import org.objectweb.asm.TypePath
import org.objectweb.asm.signature.SignatureReader
import org.objectweb.asm.signature.SignatureVisitor
import java.io.InputStream
import java.lang.reflect.Modifier.isPublic
/***
* Class responsible for analyzing a compiled class and getting a list of external dependencies
* used by the class. It does that by using class visitors that 'visit' the class components
* (header, fields, annotations and method signatures) and get the class names of the super class,
* interfaces, exceptions, field and method owners, as well as the types used in attributes,
* method returns and method parameters.
*/
class DependenciesAnalyzer {
private val level = ASM5
private val primitives = setOf(
"void",
"boolean",
"byte",
"char",
"short",
"int",
"long",
"float",
"double")
/** Finds all the dependencies in a .class file */
fun findAllDependencies(bytecode: InputStream): List<String> {
return visitClass(bytecode).keys.toList()
}
/** Finds only the dependencies that the .class file exposes in its public components */
fun findPublicDependencies(bytecode: InputStream): List<String> {
return visitClass(bytecode).filter { it.value }.keys.toList()
}
private fun visitClass(bytecode: InputStream): Map<String,Boolean> {
val classReader = ClassReader(bytecode)
val classVisitor = DependenciesClassVisitor(classReader)
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
return classVisitor.classes
}
/** Class Visitor */
inner class DependenciesClassVisitor(val reader: ClassReader): ClassVisitor(level) {
var classes = mutableMapOf<String, Boolean>()
init {
collectClassDependencies()
}
/** Visit the class header (superclass, interfaces) and save the package names
* of all the types found */
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?) {
if (superName != null) {
// superName can be null if what we are analyzing is 'java.lang.Object'
val type = getTypeFromPackageName(superName)
addType(type, access)
}
interfaces?.forEach {
val interfaceType = getTypeFromPackageName(it)
addType(interfaceType, access)
}
if (signature != null) {
addSignature(signature, access)
}
}
/** Visit all the class fields and save the package names of all the types found */
override fun visitField(
access: Int,
name: String?,
desc: String?,
signature: String?,
value: Any?): FieldVisitor {
if (desc != null) {
addTypeName(Type.getType(desc), access)
}
if (signature != null) {
addTypeSignature(signature, access)
}
if (value != null && value is Type) {
addType(value.className, access)
}
return FieldDependenciesVisitor()
}
/** Visit all the method definitions and save the package names of all types found */
override fun visitMethod(
access: Int,
name: String?,
desc: String?,
signature: String?,
exceptions: Array<out String>?): MethodVisitor {
val methodType = Type.getMethodType(desc)
// Return type
val type = methodType.returnType
addTypeName(type, access)
// Parameter types
methodType.argumentTypes.forEach {
addTypeName(it, access)
}
if (signature != null) {
addSignature(signature, access)
}
return MethodDependenciesVisitor()
}
/** Save types found in annotations */
override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor {
addType(Type.getType(desc).className)
return AnnotationDependenciesVisitor()
}
/** Save types found in type annotations */
override fun visitTypeAnnotation(
typeRef: Int,
typePath: TypePath?,
desc: String?,
visible: Boolean): AnnotationVisitor {
addType(Type.getType(desc).className)
return AnnotationDependenciesVisitor()
}
/** Save types found in method bodies by looking in the constant pool table */
private fun collectClassDependencies() {
val charBuffer = CharArray(reader.maxStringLength)
for (i in 1 until reader.itemCount) {
val itemOffset = reader.getItem(i)
if (itemOffset > 0 && reader.readByte(itemOffset - 1) == 7) {
// A CONSTANT_Class entry, read the class descriptor
val classDescriptor = reader.readUTF8(itemOffset, charBuffer)
val type = Type.getObjectType(classDescriptor)
addTypeName(type)
}
}
}
private fun getTypeFromPackageName(packageName: String): String {
return Type.getObjectType(packageName).className
}
private fun addType(type: String) {
addType(type, ACC_PRIVATE)
}
private fun addType(type: String, access: Int) {
if (isPrimitiveType(type)) {
return
}
val className = type.replace(".", "/").plus(SdkConstants.DOT_CLASS)
classes[className] = isPublic(access) || classes[className] ?: false
}
private fun addTypeName(type: Type, access: Int) {
var elementType = type
while(elementType.sort == Type.ARRAY) {
// Find types in arrays
elementType = elementType.elementType
}
if (elementType.sort == Type.OBJECT) {
addType(elementType.className, access)
}
}
private fun addTypeName(type: Type) {
addTypeName(type, ACC_PRIVATE)
}
private fun isPrimitiveType(type: String): Boolean {
return primitives.contains(type)
}
private fun addSignature(sign: String, access: Int) {
SignatureReader(sign).accept(DependencySignatureVisitor(access))
}
private fun addTypeSignature(sign: String, access: Int) {
SignatureReader(sign).acceptType(DependencySignatureVisitor(access))
}
inner class FieldDependenciesVisitor: FieldVisitor(level) {
override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor {
addType(Type.getType(desc).className)
return AnnotationDependenciesVisitor()
}
override fun visitTypeAnnotation(
typeRef: Int,
typePath: TypePath?,
desc: String?,
visible: Boolean): AnnotationVisitor {
addType(Type.getType(desc).className)
return AnnotationDependenciesVisitor()
}
}
inner class MethodDependenciesVisitor: MethodVisitor(level) {
override fun visitLocalVariable(
name: String?,
desc: String?,
signature: String?,
start: Label?,
end: Label?,
index: Int) {
if (desc != null) {
addTypeName(Type.getType(desc))
}
super.visitLocalVariable(name, desc, signature, start, end, index)
}
override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor {
if (desc != null) {
addTypeName(Type.getType(desc))
}
return AnnotationDependenciesVisitor()
}
override fun visitParameterAnnotation(
parameter: Int,
desc: String?,
visible: Boolean): AnnotationVisitor {
addTypeName(Type.getType(desc))
return AnnotationDependenciesVisitor()
}
override fun visitTypeAnnotation(
typeRef: Int,
typePath: TypePath?,
desc: String?,
visible: Boolean): AnnotationVisitor {
addTypeName(Type.getType(desc))
return AnnotationDependenciesVisitor()
}
}
inner class AnnotationDependenciesVisitor: AnnotationVisitor(level) {
override fun visit(name: String?, value: Any?) {
if (value is Type) {
addType(value.className)
}
}
override fun visitArray(name: String?): AnnotationVisitor {
return this
}
override fun visitAnnotation(name: String?, desc: String?): AnnotationVisitor {
addType(Type.getType(desc).className)
return this
}
}
inner class DependencySignatureVisitor(val access: Int): SignatureVisitor(level) {
override fun visitTypeVariable(name: String?) {
if (name != null) {
addType(name, access)
}
}
override fun visitArrayType(): SignatureVisitor {
return this
}
override fun visitClassType(name: String?) {
if (name != null) {
addType(name, access)
}
}
override fun visitInnerClassType(name: String?) {
if (name != null) {
addType(name, access)
}
}
}
}
}