mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
CORDA-1785: Update JarFilter to delete classes whose outer class or enclosing method is removed. (#3562)
This commit is contained in:
parent
7e220d317d
commit
d672cba877
@ -18,14 +18,14 @@ private const val DUMMY_PASSES = 1
|
||||
|
||||
private val DECLARES_DEFAULT_VALUE_MASK: Int = DECLARES_DEFAULT_VALUE.toFlags(true).inv()
|
||||
|
||||
internal abstract class Element(val name: String, val descriptor: String) {
|
||||
abstract class Element(val name: String, val descriptor: String) {
|
||||
private var lifetime: Int = DUMMY_PASSES
|
||||
|
||||
open val isExpired: Boolean get() = --lifetime < 0
|
||||
}
|
||||
|
||||
|
||||
internal class MethodElement(name: String, descriptor: String, val access: Int = 0) : Element(name, descriptor) {
|
||||
class MethodElement(name: String, descriptor: String, val access: Int = 0) : Element(name, descriptor) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
@ -66,7 +66,7 @@ internal class MethodElement(name: String, descriptor: String, val access: Int =
|
||||
* A class cannot have two fields with the same name but different types. However,
|
||||
* it can define extension functions and properties.
|
||||
*/
|
||||
internal class FieldElement(name: String, descriptor: String = "?", val extension: String = "()") : Element(name, descriptor) {
|
||||
class FieldElement(name: String, descriptor: String = "?", val extension: String = "()") : Element(name, descriptor) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
|
@ -22,7 +22,7 @@ class FilterTransformer private constructor (
|
||||
private val removeAnnotations: Set<String>,
|
||||
private val deleteAnnotations: Set<String>,
|
||||
private val stubAnnotations: Set<String>,
|
||||
private val unwantedClasses: MutableSet<String>,
|
||||
private val unwantedElements: UnwantedCache,
|
||||
private val unwantedFields: MutableSet<FieldElement>,
|
||||
private val deletedMethods: MutableSet<MethodElement>,
|
||||
private val stubbedMethods: MutableSet<MethodElement>
|
||||
@ -33,7 +33,7 @@ class FilterTransformer private constructor (
|
||||
removeAnnotations: Set<String>,
|
||||
deleteAnnotations: Set<String>,
|
||||
stubAnnotations: Set<String>,
|
||||
unwantedClasses: MutableSet<String>
|
||||
unwantedElements: UnwantedCache
|
||||
) : this(
|
||||
visitor = visitor,
|
||||
logger = logger,
|
||||
@ -41,7 +41,7 @@ class FilterTransformer private constructor (
|
||||
removeAnnotations = removeAnnotations,
|
||||
deleteAnnotations = deleteAnnotations,
|
||||
stubAnnotations = stubAnnotations,
|
||||
unwantedClasses = unwantedClasses,
|
||||
unwantedElements = unwantedElements,
|
||||
unwantedFields = mutableSetOf(),
|
||||
deletedMethods = mutableSetOf(),
|
||||
stubbedMethods = mutableSetOf()
|
||||
@ -57,7 +57,7 @@ class FilterTransformer private constructor (
|
||||
|| stubbedMethods.isNotEmpty()
|
||||
|| super.hasUnwantedElements
|
||||
|
||||
private fun isUnwantedClass(name: String): Boolean = unwantedClasses.contains(name)
|
||||
private fun isUnwantedClass(name: String): Boolean = unwantedElements.containsClass(name)
|
||||
private fun hasDeletedSyntheticMethod(name: String): Boolean = deletedMethods.any { method ->
|
||||
name.startsWith("$className\$${method.visibleName}\$")
|
||||
}
|
||||
@ -69,7 +69,7 @@ class FilterTransformer private constructor (
|
||||
removeAnnotations = removeAnnotations,
|
||||
deleteAnnotations = deleteAnnotations,
|
||||
stubAnnotations = stubAnnotations,
|
||||
unwantedClasses = unwantedClasses,
|
||||
unwantedElements = unwantedElements,
|
||||
unwantedFields = unwantedFields,
|
||||
deletedMethods = deletedMethods,
|
||||
stubbedMethods = stubbedMethods
|
||||
@ -86,7 +86,7 @@ class FilterTransformer private constructor (
|
||||
logger.info("- Removing annotation {}", descriptor)
|
||||
return null
|
||||
} else if (deleteAnnotations.contains(descriptor)) {
|
||||
if (unwantedClasses.add(className)) {
|
||||
if (unwantedElements.addClass(className)) {
|
||||
logger.info("- Identified class {} as unwanted", className)
|
||||
}
|
||||
}
|
||||
@ -110,6 +110,7 @@ class FilterTransformer private constructor (
|
||||
logger.debug("--- method ---> {}", method)
|
||||
if (deletedMethods.contains(method)) {
|
||||
logger.info("- Deleted method {}{}", method.name, method.descriptor)
|
||||
unwantedElements.addMethod(className, method)
|
||||
deletedMethods.remove(method)
|
||||
return null
|
||||
}
|
||||
@ -131,7 +132,7 @@ class FilterTransformer private constructor (
|
||||
override fun visitInnerClass(clsName: String, outerName: String?, innerName: String?, access: Int) {
|
||||
logger.debug("--- inner class {} [outer: {}, inner: {}]", clsName, outerName, innerName)
|
||||
if (isUnwantedClass || hasDeletedSyntheticMethod(clsName)) {
|
||||
if (unwantedClasses.add(clsName)) {
|
||||
if (unwantedElements.addClass(clsName)) {
|
||||
logger.info("- Deleted inner class {}", clsName)
|
||||
}
|
||||
} else if (isUnwantedClass(clsName)) {
|
||||
@ -143,8 +144,10 @@ class FilterTransformer private constructor (
|
||||
|
||||
override fun visitOuterClass(outerName: String, methodName: String?, methodDescriptor: String?) {
|
||||
logger.debug("--- outer class {} [enclosing method {},{}]", outerName, methodName, methodDescriptor)
|
||||
if (isUnwantedClass(outerName)) {
|
||||
logger.info("- Deleted reference to outer class {}", outerName)
|
||||
if (unwantedElements.containsMethod(outerName, methodName, methodDescriptor)) {
|
||||
if (unwantedElements.addClass(className)) {
|
||||
logger.info("- Identified class {} as unwanted by its outer class", className)
|
||||
}
|
||||
} else {
|
||||
super.visitOuterClass(outerName, methodName, methodDescriptor)
|
||||
}
|
||||
@ -180,8 +183,8 @@ class FilterTransformer private constructor (
|
||||
deletedFields = unwantedFields,
|
||||
deletedFunctions = partitioned[false] ?: emptyList(),
|
||||
deletedConstructors = partitioned[true] ?: emptyList(),
|
||||
deletedNestedClasses = unwantedClasses.filter { it.startsWith(prefix) }.map { it.drop(prefix.length) },
|
||||
deletedClasses = unwantedClasses,
|
||||
deletedNestedClasses = unwantedElements.classes.filter { it.startsWith(prefix) }.map { it.drop(prefix.length) },
|
||||
deletedClasses = unwantedElements.classes,
|
||||
handleExtraMethod = ::delete,
|
||||
d1 = d1,
|
||||
d2 = d2)
|
||||
|
@ -138,7 +138,7 @@ open class JarFilterTask : DefaultTask() {
|
||||
}
|
||||
|
||||
private inner class Filter(inFile: File) {
|
||||
private val unwantedClasses: MutableSet<String> = mutableSetOf()
|
||||
private val unwantedElements = UnwantedCache()
|
||||
private val source: Path = inFile.toPath()
|
||||
private val target: Path = toFiltered(inFile).toPath()
|
||||
|
||||
@ -251,7 +251,7 @@ open class JarFilterTask : DefaultTask() {
|
||||
removeAnnotations = descriptorsForRemove,
|
||||
deleteAnnotations = descriptorsForDelete,
|
||||
stubAnnotations = descriptorsForStub,
|
||||
unwantedClasses = unwantedClasses
|
||||
unwantedElements = unwantedElements
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -0,0 +1,46 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import java.util.Collections.unmodifiableMap
|
||||
|
||||
/**
|
||||
* A persistent cache of all of the classes and methods that JarFilter has
|
||||
* removed. This cache belongs to the Gradle task itself and so is shared
|
||||
* by successive filter passes.
|
||||
*
|
||||
* The internal method cache is only required for those classes which are
|
||||
* being kept. When an entire class is declared as "unwanted", any entry
|
||||
* it may have in the method cache is removed.
|
||||
*/
|
||||
class UnwantedCache {
|
||||
private val _classes: MutableSet<String> = mutableSetOf()
|
||||
private val _classMethods: MutableMap<String, MutableSet<MethodElement>> = mutableMapOf()
|
||||
|
||||
val classes: Set<String> get() = _classes
|
||||
val classMethods: Map<String, Set<MethodElement>> get() = unmodifiableMap(_classMethods)
|
||||
|
||||
fun containsClass(className: String): Boolean = _classes.contains(className)
|
||||
|
||||
fun addClass(className: String): Boolean {
|
||||
return _classes.add(className).also { isAdded ->
|
||||
if (isAdded) {
|
||||
_classMethods.remove(className)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addMethod(className: String, method: MethodElement) {
|
||||
if (!containsClass(className)) {
|
||||
_classMethods.getOrPut(className) { mutableSetOf() }.add(method)
|
||||
}
|
||||
}
|
||||
|
||||
private fun containsMethod(className: String, method: MethodElement): Boolean {
|
||||
return _classMethods[className]?.contains(method) ?: false
|
||||
}
|
||||
|
||||
fun containsMethod(className: String, methodName: String?, methodDescriptor: String?): Boolean {
|
||||
return containsClass(className) ||
|
||||
(methodName != null && methodDescriptor != null && containsMethod(className, MethodElement(methodName, methodDescriptor)))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,84 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.isConstructor
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.hamcrest.core.IsNot.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteInnerLambdaTest {
|
||||
companion object {
|
||||
private const val LAMBDA_CLASS = "net.corda.gradle.HasInnerLambda"
|
||||
private const val SIZE = 64
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-inner-lambda")
|
||||
private val constructInt = isConstructor(LAMBDA_CLASS, Int::class)
|
||||
private val constructBytes = isConstructor(LAMBDA_CLASS, ByteArray::class)
|
||||
|
||||
private lateinit var sourceClasses: List<String>
|
||||
private lateinit var filteredClasses: List<String>
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
sourceClasses = testProject.sourceJar.getClassNames(LAMBDA_CLASS)
|
||||
filteredClasses = testProject.filteredJar.getClassNames(LAMBDA_CLASS)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test lambda class is deleted`() {
|
||||
assertThat(sourceClasses)
|
||||
.contains(LAMBDA_CLASS)
|
||||
.hasSize(2)
|
||||
assertThat(filteredClasses).containsExactly(LAMBDA_CLASS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test host class`() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasInt>(LAMBDA_CLASS).apply {
|
||||
getConstructor(Int::class.java).newInstance(SIZE).also { obj ->
|
||||
assertEquals(SIZE, obj.intData())
|
||||
}
|
||||
kotlin.constructors.also { ctors ->
|
||||
assertThat("<init>(Int) not found", ctors, hasItem(constructInt))
|
||||
assertThat("<init>(byte[]) not found", ctors, hasItem(constructBytes))
|
||||
}
|
||||
|
||||
getConstructor(ByteArray::class.java).newInstance(ByteArray(SIZE)).also { obj ->
|
||||
assertEquals(SIZE, obj.intData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasInt>(LAMBDA_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getConstructor(Int::class.java) }
|
||||
kotlin.constructors.also { ctors ->
|
||||
assertThat("<init>(Int) still exists", ctors, not(hasItem(constructInt)))
|
||||
assertThat("<init>(byte[]) not found", ctors, hasItem(constructBytes))
|
||||
}
|
||||
|
||||
getConstructor(ByteArray::class.java).newInstance(ByteArray(SIZE)).also { obj ->
|
||||
assertEquals(SIZE, obj.intData())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -52,7 +52,7 @@ class FieldRemovalTest {
|
||||
removeAnnotations = emptySet(),
|
||||
deleteAnnotations = setOf(Deletable::class.jvmName.descriptor),
|
||||
stubAnnotations = emptySet(),
|
||||
unwantedClasses = mutableSetOf()
|
||||
unwantedElements = UnwantedCache()
|
||||
)
|
||||
}, COMPUTE_MAXS)
|
||||
return bytecode.toClass(type, asType)
|
||||
|
@ -34,7 +34,7 @@ class StaticFieldRemovalTest {
|
||||
removeAnnotations = emptySet(),
|
||||
deleteAnnotations = setOf(Deletable::class.jvmName.descriptor),
|
||||
stubAnnotations = emptySet(),
|
||||
unwantedClasses = mutableSetOf()
|
||||
unwantedElements = UnwantedCache()
|
||||
)
|
||||
}, COMPUTE_MAXS)
|
||||
return bytecode.toClass(type, asType)
|
||||
|
@ -0,0 +1,54 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class UnwantedCacheTest {
|
||||
private companion object {
|
||||
private const val CLASS_NAME = "org.testing.MyClass"
|
||||
private const val LONG_ARG = "(J)V"
|
||||
private const val NO_ARG = "()V"
|
||||
}
|
||||
|
||||
private lateinit var cache: UnwantedCache
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
cache = UnwantedCache()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmptyCache() {
|
||||
assertFalse(cache.containsClass(CLASS_NAME))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, null, null))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "<init>", NO_ARG))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddingClass() {
|
||||
cache.addClass(CLASS_NAME)
|
||||
assertTrue(cache.containsClass(CLASS_NAME))
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, null, null))
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, "<init>", NO_ARG))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddingMethod() {
|
||||
cache.addMethod(CLASS_NAME, MethodElement("<init>", LONG_ARG))
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, "<init>", LONG_ARG))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "<init>", NO_ARG))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "destroy", LONG_ARG))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, null, null))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "nonsense", null))
|
||||
assertFalse(cache.containsClass(CLASS_NAME))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddingMethodFollowedByClass() {
|
||||
cache.addMethod(CLASS_NAME, MethodElement("<init>", LONG_ARG))
|
||||
cache.addClass(CLASS_NAME)
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, "<init>", LONG_ARG))
|
||||
assertEquals(0, cache.classMethods.size)
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '$kotlin_version'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
apply from: 'repositories.gradle'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
kotlin {
|
||||
srcDir files(
|
||||
'../resources/test/delete-inner-lambda/kotlin',
|
||||
'../resources/test/annotations/kotlin'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName = 'delete-inner-lambda'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars jar
|
||||
annotations {
|
||||
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
|
||||
class HasInnerLambda(private val bytes: ByteArray) : HasInt {
|
||||
@DeleteMe
|
||||
constructor(size: Int) : this(ZeroArray { size }.bytes)
|
||||
|
||||
override fun intData() = bytes.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Do NOT inline this lambda!
|
||||
*/
|
||||
class ZeroArray(initialSize: () -> Int) {
|
||||
val bytes: ByteArray = ByteArray(initialSize()) { 0 }
|
||||
}
|
Loading…
Reference in New Issue
Block a user