CORDA-1816: Migrate JarFilter plugin to corda-gradle-plugins. (#3651)

This commit is contained in:
Chris Rankin 2018-07-19 16:35:36 +01:00 committed by GitHub
parent 7466463b89
commit fe9b89369f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
163 changed files with 3 additions and 9930 deletions

View File

@ -99,6 +99,7 @@ buildscript {
classpath "net.corda.plugins:cordformation:$gradle_plugins_version"
classpath "net.corda.plugins:cordapp:$gradle_plugins_version"
classpath "net.corda.plugins:api-scanner:$gradle_plugins_version"
classpath "net.corda.plugins:jar-filter:$gradle_plugins_version"
classpath "net.sf.proguard:proguard-gradle:$proguard_version"
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"

View File

@ -4,21 +4,8 @@ buildscript {
ext {
guava_version = constants.getProperty("guavaVersion")
kotlin_version = constants.getProperty("kotlinVersion")
proguard_version = constants.getProperty("proguardVersion")
assertj_version = '3.9.1'
junit_version = '4.12'
asm_version = '6.2'
}
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "net.sf.proguard:proguard-gradle:$proguard_version"
}
}
@ -28,23 +15,9 @@ repositories {
}
allprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
languageVersion = "1.2"
apiVersion = "1.2"
jvmTarget = "1.8"
javaParameters = true // Useful for reflection.
}
}
tasks.withType(Test) {
// Prevent the project from creating temporary files outside of the build directory.
systemProperty 'java.io.tmpdir', buildDir.absolutePath
// Tell the tests where Gradle's current module cache is.
// We need the tests to share this module cache to prevent the
// Gradle Test-Kit from downloading its own copy of Kotlin etc.
systemProperty 'test.gradle.user.home', project.gradle.gradleUserHomeDir
}
}

View File

@ -1,218 +0,0 @@
# JarFilter
Deletes annotated elements at the byte-code level from a JAR of Java/Kotlin code. In the case of Kotlin
code, it also modifies the `@kotlin.Metadata` annotations not to contain any functions, properties or
type aliases that have been deleted. This prevents the Kotlin compiler from successfully compiling against
any elements which no longer exist.
We use this plugin together with ProGuard to generate Corda's `core-deterministic` and `serialization-deterministic`
modules. See [here](../../docs/source/deterministic-modules.rst) for more information.
## Usage
This plugin is automatically available on Gradle's classpath since it lives in Corda's `buildSrc` directory.
You need only `import` the plugin's task classes in the `build.gradle` file and then use them to declare
tasks.
You can enable the tasks' logging output using Gradle's `--info` or `--debug` command-line options.
### The `JarFilter` task
The `JarFilter` task removes unwanted elements from `class` files, namely:
- Deleting both Java methods/fields and Kotlin functions/properties/type aliases.
- Stubbing out methods by replacing the byte-code of their implementations.
- Removing annotations from classes/methods/fields.
It supports the following configuration options:
```gradle
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
// Task(s) whose JAR outputs should be filtered.
jars jar
// The annotations assigned to each filtering role. For example:
annotations {
forDelete = [
"org.testing.DeleteMe"
]
forStub = [
"org.testing.StubMeOut"
]
forRemove = [
"org.testing.RemoveMe"
]
}
// Location for filtered JARs. Defaults to "$buildDir/filtered-libs".
outputDir file(...)
// Whether the timestamps on the JARs' entries should be preserved "as is"
// or set to a platform-independent constant value (1st February 1980).
preserveTimestamps = {true|false}
// The maximum number of times (>= 1) to pass the JAR through the filter.
maxPasses = 5
// Writes more information about each pass of the filter.
verbose = {true|false}
}
```
You can specify as many annotations for each role as you like. The only constraint is that a given
annotation cannot be assigned to more than one role.
#### Removing unwanted default parameter values
It is possible to assign non-deterministic expressions as default values for Kotlin constructors and functions. For
example:
```kotlin
data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID())
```
The Kotlin compiler will generate _two_ constructors in this case:
```
UniqueIdentifier(String?, UUID)
UniqueIdentifier(String?, UUID, Int, DefaultConstructorMarker)
```
The first constructor is the primary constructor that we would expect (and which we'd like to keep), whereas the
second is a public synthetic constructor that Kotlin applications invoke to handle the different combinations of
default parameter values. Unfortunately, this synthetic constructor is therefore also part of the Kotlin ABI and
so we _cannot_ rewrite the class like this to remove the default values:
```kotlin
// THIS REFACTOR WOULD BREAK THE KOTLIN ABI!
data class UniqueIdentifier(val externalId: String?, val id: UUID) {
constructor(externalId: String?) : this(externalId, UUID.randomUUID())
constructor() : this(null)
}
```
The refactored class would have the following constructors, and would require client applications to be recompiled:
```
UniqueIdentifier(String?, UUID)
UniqueIdentifier(String?)
UniqueIdentifier()
```
We therefore need to keep the default constructor parameters in order to preserve the ABI for the unfiltered code,
which in turn means that `JarFilter` will need to delete only the synthetic constructor and leave the primary
constructor intact. However, Kotlin does not currently allow us to annotate _specific_ constructors - see
[KT-22524](https://youtrack.jetbrains.com/issue/KT-22524). Until it does, `JarFilter` will perform an initial
"sanitising" pass over the JAR file to remove any unwanted annotations from the primary constructors. These unwanted
annotations are configured in the `JarFilter` task definition:
```gradle
task jarFilter(type: JarFilterTask) {
...
annotations {
...
forSanitise = [
"org.testing.DeleteMe"
]
}
}
```
This allows us to annotate the `UniqueIdentifier` class like this:
```kotlin
data class UniqueIdentifier @DeleteMe constructor(val externalId: String? = null, val id: UUID = UUID.randomUUID())
```
to generate these constructors:
```
UniqueIdentifier(String?, UUID)
@DeleteMe UniqueIdentifier(String?, UUID, Int, DefaultConstructorMarker)
```
We currently **do not** sanitise annotations from functions with default parameter values, although (in theory) these
may also be non-deterministic. We will need to extend the sanitation pass to include such functions if/when the need
arises. At the moment, deleting such functions _entirely_ is enough, whereas also deleting a primary constructor means
that we can no longer create instances of that class either.
### The `MetaFixer` task
The `MetaFixer` task updates the `@kotlin.Metadata` annotations by removing references to any functions,
constructors, properties or nested classes that no longer exist in the byte-code. This is primarily to
"repair" Kotlin library code that has been processed by ProGuard.
Kotlin type aliases exist only inside `@Metadata` and so are unaffected by this task. Similarly, the
constructors for Kotlin's annotation classes don't exist in the byte-code either because Java annotations
are interfaces really. The `MetaFixer` task will therefore ignore annotations' constructors too.
It supports these configuration options:
```gradle
import net.corda.gradle.jarfilter.MetaFixerTask
task metafix(type: MetaFixerTask) {
// Task(s) whose JAR outputs should be fixed.
jars jar
// Location for fixed JARs. Defaults to "$buildDir/metafixed-libs"
outputDir file(...)
// Tag to be appended to the JAR name. Defaults to "-metafixed".
suffix = "..."
// Whether the timestamps on the JARs' entries should be preserved "as is"
// or set to a platform-independent constant value (1st February 1980).
preserveTimestamps = {true|false}
}
```
## Implementation Details
### Code Coverage
You can generate a JaCoCo code coverage report for the unit tests using:
```bash
$ cd buildSrc
$ ../gradlew jarfilter:jacocoTestReport
```
### Kotlin Metadata
The Kotlin compiler encodes information about each class inside its `@kotlin.Metadata` annotation.
```kotlin
import kotlin.annotation.AnnotationRetention.*
@Retention(RUNTIME)
annotation class Metadata {
val k: Int = 1
val d1: Array<String> = []
val d2: Array<String> = []
// ...
}
```
This is an internal feature of Kotlin which is read by Kotlin Reflection. There is no public API
for writing this information, and the content format of arrays `d1` and `d2` depends upon the
"class kind" `k`. For the kinds that we are interested in, `d1` contains a buffer of ProtoBuf
data and `d2` contains an array of `String` identifiers which the ProtoBuf data refers to by index.
Although ProtoBuf generates functions for both reading and writing the data buffer, the
Kotlin Reflection artifact only contains the functions for reading. This is almost certainly
because the writing functionality has been removed from the `kotlin-reflect` JAR using
ProGuard. However, the complete set of generated ProtoBuf classes is still available in the
`kotlin-compiler-embeddable` JAR. The `jarfilter:kotlin-metadata` module uses ProGuard to
extracts these classes into a new `kotlin-metdata` JAR, discarding any classes that the
ProtoBuf ones do not need and obfuscating any other ones that they do.
The custom `kotlin-metadata` object was originally created as a workaround for
[KT-18621](https://youtrack.jetbrains.com/issue/KT-18621). However, reducing the number of unwanted
classes on the classpath anyway can only be a Good Thing<sup>(TM)</sup>.
At runtime, `JarFilter` decompiles the ProtoBuf buffer into POJOs, deletes the elements that
no longer exist in the byte-code and then recompiles the POJOs into a new ProtoBuf buffer. The
`@Metadata` annotation is then rewritten using this new buffer for `d1` and the _original_ `String`
identifiers for `d2`. While some of these identifiers are very likely no longer used after this,
removing them would also require re-indexing the ProtoBuf data. It is therefore simpler just to
leave them as harmless cruft in the byte-code's constant pool.
The majority of `JarFilter`'s unit tests use Kotlin and Java reflection and so should not be
brittle as Kotlin evolves because `kotlin-reflect` is public API. Also, Kotlin's requirement that
it remain backwards-compatible with itself should imply that the ProtoBuf logic shouldn't change
(much). However, the ProtoBuf classes are still internal to Kotlin and so it _is_ possible that they
will occasionally move between packages. This has already happened for Kotlin 1.2.3x -> 1.2.4x, but
I am hoping this means that they will not move again for a while.
### JARs vs ZIPs
The `JarFilter` and `MetaFixer` tasks _deliberately_ use `ZipFile` and `ZipOutputStream` rather
than `JarInputStream` and `JarOutputStream` when reading and writing their JAR files. This is to
ensure that the original `META-INF/MANIFEST.MF` files are passed through unaltered. Note also that
there is no `ZipInputStream.getComment()` method, and so we need to use `ZipFile` in order to
preserve any JAR comments.
Neither `JarFilter` nor `MetaFixer` should change the order of the entries inside the JAR files.

View File

@ -1,46 +0,0 @@
plugins {
id 'java-gradle-plugin'
id 'jacoco'
}
apply plugin: 'kotlin'
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
gradlePlugin {
plugins {
jarFilterPlugin {
id = 'net.corda.plugins.jar-filter'
implementationClass = 'net.corda.gradle.jarfilter.JarFilterPlugin'
}
}
}
configurations {
jacocoRuntime
}
processTestResources {
filesMatching('**/build.gradle') {
expand(['kotlin_version': kotlin_version])
}
filesMatching('gradle.properties') {
expand(['jacocoAgent': configurations.jacocoRuntime.asPath.replace('\\', '/'),
'buildDir': buildDir])
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation project(':jarfilter:kotlin-metadata')
implementation "org.ow2.asm:asm:$asm_version"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
testImplementation "org.jetbrains.kotlin:kotlin-reflect"
testImplementation "org.assertj:assertj-core:$assertj_version"
testImplementation "junit:junit:$junit_version"
testImplementation project(':jarfilter:unwanteds')
jacocoRuntime "org.jacoco:org.jacoco.agent:${jacoco.toolVersion}:runtime"
}

View File

@ -1,84 +0,0 @@
plugins {
id 'base'
}
description "Kotlin's metadata-handling classes"
repositories {
mavenLocal()
jcenter()
}
configurations {
proguard
runtime
configurations.default.extendsFrom runtime
}
dependencies {
proguard "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlin_version"
proguard "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
runtime "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}
def javaHome = System.getProperty('java.home')
def originalJar = configurations.proguard.files.find { it.name.startsWith("kotlin-compiler-embeddable") }
import proguard.gradle.ProGuardTask
task metadata(type: ProGuardTask) {
injars originalJar, filter: 'META-INF/MANIFEST.MF,META-INF/metadata*.kotlin_module,**.class'
outjars "$buildDir/libs/${project.name}-${kotlin_version}.jar"
libraryjars "$javaHome/lib/rt.jar"
libraryjars "$javaHome/../lib/tools.jar"
configurations.proguard.forEach {
if (originalJar != it) {
libraryjars it.path, filter: '!META-INF/versions/**'
}
}
keepattributes '*'
dontoptimize
verbose
dontwarn 'com.sun.jna.**'
dontwarn 'org.jetbrains.annotations.**'
dontwarn 'org.jetbrains.kotlin.com.intellij.**'
dontwarn 'org.jetbrains.kotlin.com.google.j2objc.annotations.**'
dontwarn 'org.jetbrains.kotlin.com.google.errorprone.annotations.**'
dontnote
keep 'class org.jetbrains.kotlin.load.java.JvmAnnotationNames { *; }'
keep 'class org.jetbrains.kotlin.metadata.ProtoBuf { *; }', includedescriptorclasses: true
keep 'class org.jetbrains.kotlin.metadata.ProtoBuf$* { *; }', includedescriptorclasses: true
keep 'class org.jetbrains.kotlin.metadata.deserialization.** { *; }', includedescriptorclasses: true
keep 'class org.jetbrains.kotlin.metadata.jvm.** { *; }', includedescriptorclasses: true
keep 'class org.jetbrains.kotlin.protobuf.** { *; }', includedescriptorclasses: true
}
def metadataJar = metadata.outputs.files.singleFile
task validate(type: ProGuardTask) {
injars metadataJar
libraryjars "$javaHome/lib/rt.jar"
configurations.runtime.forEach {
libraryjars it.path, filter: '!META-INF/versions/**'
}
keepattributes '*'
dontpreverify
dontobfuscate
dontoptimize
verbose
dontwarn 'org.jetbrains.kotlin.com.google.errorprone.annotations.**'
dontnote
keep 'class *'
}
artifacts {
'default' file: metadataJar, name: project.name, type: 'jar', extension: 'jar', builtBy: metadata
}
defaultTasks "metadata"
metadata.finalizedBy validate

View File

@ -1,181 +0,0 @@
@file:JvmName("Elements")
package net.corda.gradle.jarfilter
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.Flags.*
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
import org.jetbrains.kotlin.metadata.deserialization.returnType
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf
import org.jetbrains.kotlin.metadata.jvm.deserialization.ClassMapperLite
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.objectweb.asm.Opcodes.ACC_SYNTHETIC
import java.util.*
private const val DEFAULT_CONSTRUCTOR_MARKER = "ILkotlin/jvm/internal/DefaultConstructorMarker;"
private const val DEFAULT_FUNCTION_MARKER = "ILjava/lang/Object;"
private const val DUMMY_PASSES = 1
private val DECLARES_DEFAULT_VALUE_MASK: Int = DECLARES_DEFAULT_VALUE.toFlags(true).inv()
abstract class Element(val name: String, val descriptor: String) {
private var lifetime: Int = DUMMY_PASSES
open val isExpired: Boolean get() = --lifetime < 0
}
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
other as MethodElement
return other.name == name && other.descriptor == descriptor
}
override fun hashCode(): Int = Objects.hash(name, descriptor)
override fun toString(): String = "MethodElement[name=$name, descriptor=$descriptor, access=$access]"
override val isExpired: Boolean get() = access == 0 && super.isExpired
val isConstructor: Boolean get() = isObjectConstructor || isClassConstructor
val isClassConstructor: Boolean get() = name == "<clinit>"
val isObjectConstructor: Boolean get() = name == "<init>"
val isVoidFunction: Boolean get() = !isConstructor && descriptor.endsWith(")V")
private val suffix: String
val visibleName: String
val signature: String = name + descriptor
init {
val idx = name.indexOf('$')
visibleName = if (idx == -1) name else name.substring(0, idx)
suffix = if (idx == -1) "" else name.drop(idx + 1)
}
fun isKotlinSynthetic(vararg tags: String): Boolean = (access and ACC_SYNTHETIC) != 0 && tags.contains(suffix)
fun asKotlinNonDefaultConstructor(): MethodElement? {
val markerIdx = descriptor.indexOf(DEFAULT_CONSTRUCTOR_MARKER)
return if (markerIdx >= 0) {
MethodElement(name, descriptor.removeRange(markerIdx, markerIdx + DEFAULT_CONSTRUCTOR_MARKER.length))
} else {
null
}
}
}
/**
* A class cannot have two fields with the same name but different types. However,
* it can define extension functions and properties.
*/
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
other as FieldElement
return other.name == name && other.extension == extension
}
override fun hashCode(): Int = Objects.hash(name, extension)
override fun toString(): String = "FieldElement[name=$name, descriptor=$descriptor, extension=$extension]"
override val isExpired: Boolean get() = descriptor == "?" && super.isExpired
}
val String.extensionType: String get() = substring(0, 1 + indexOf(')'))
/**
* Returns a fully-qualified class name as it would exist
* in the byte-code, e.g. as "a/b/c/ClassName$Nested".
*/
fun NameResolver.getClassInternalName(idx: Int): String
= getQualifiedClassName(idx).replace('.', '$')
/**
* Construct the signatures of the synthetic methods that
* Kotlin would create to handle default parameter values.
*/
fun String.toKotlinDefaultConstructor(): String {
val closer = lastIndexOf(')')
return substring(0, closer) + DEFAULT_CONSTRUCTOR_MARKER + substring(closer)
}
fun String.toKotlinDefaultFunction(classDescriptor: String): String {
val opener = indexOf('(')
val closer = lastIndexOf(')')
return (substring(0, opener) + "\$default("
+ classDescriptor + substring(opener + 1, closer) + DEFAULT_FUNCTION_MARKER
+ substring(closer))
}
/**
* Convert Kotlin getter/setter method data to [MethodElement] objects.
*/
internal fun JvmProtoBuf.JvmPropertySignature.toGetter(nameResolver: NameResolver): MethodElement? {
return if (hasGetter()) { getter?.toMethodElement(nameResolver) } else { null }
}
internal fun JvmProtoBuf.JvmPropertySignature.toSetter(nameResolver: NameResolver): MethodElement? {
return if (hasSetter()) { setter?.toMethodElement(nameResolver) } else { null }
}
internal fun JvmProtoBuf.JvmMethodSignature.toMethodElement(nameResolver: NameResolver)
= MethodElement(nameResolver.getString(name), nameResolver.getString(desc))
/**
* This logic is based heavily on [JvmProtoBufUtil.getJvmFieldSignature].
*/
internal fun JvmProtoBuf.JvmPropertySignature.toFieldElement(property: ProtoBuf.Property, nameResolver: NameResolver, typeTable: TypeTable): FieldElement {
var nameId = property.name
var descId = -1
if (hasField()) {
if (field.hasName()) {
nameId = field.name
}
if (field.hasDesc()) {
descId = field.desc
}
}
val descriptor = if (descId == -1) {
val returnType = property.returnType(typeTable)
if (returnType.hasClassName()) {
ClassMapperLite.mapClass(nameResolver.getQualifiedClassName(returnType.className))
} else {
"?"
}
} else {
nameResolver.getString(descId)
}
return FieldElement(nameResolver.getString(nameId), descriptor)
}
/**
* Rewrites metadata for function and constructor parameters.
*/
internal fun ProtoBuf.Constructor.Builder.updateValueParameters(
updater: (ProtoBuf.ValueParameter) -> ProtoBuf.ValueParameter
): ProtoBuf.Constructor.Builder {
for (idx in 0 until valueParameterList.size) {
setValueParameter(idx, updater(valueParameterList[idx]))
}
return this
}
internal fun ProtoBuf.Function.Builder.updateValueParameters(
updater: (ProtoBuf.ValueParameter) -> ProtoBuf.ValueParameter
): ProtoBuf.Function.Builder {
for (idx in 0 until valueParameterList.size) {
setValueParameter(idx, updater(valueParameterList[idx]))
}
return this
}
internal fun ProtoBuf.ValueParameter.clearDeclaresDefaultValue(): ProtoBuf.ValueParameter {
return if (DECLARES_DEFAULT_VALUE.get(flags)) {
toBuilder().setFlags(flags and DECLARES_DEFAULT_VALUE_MASK).build()
} else {
this
}
}
internal val List<ProtoBuf.ValueParameter>.hasAnyDefaultValues
get() = any { DECLARES_DEFAULT_VALUE.get(it.flags) }

View File

@ -1,356 +0,0 @@
package net.corda.gradle.jarfilter
import org.gradle.api.InvalidUserDataException
import org.gradle.api.logging.Logger
import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.*
/**
* ASM [ClassVisitor] for the JarFilter task that deletes unwanted class elements.
* The unwanted elements have been annotated in advance. Elements that reference
* unwanted elements are also removed to keep the byte-code consistent. Finally,
* the deleted elements are passed to the [MetadataTransformer] so that they can
* be removed from the [kotlin.Metadata] annotation.
*
* This Visitor is applied to the byte-code repeatedly until it has removed
* everything that is no longer wanted.
*/
class FilterTransformer private constructor (
visitor: ClassVisitor,
logger: Logger,
kotlinMetadata: MutableMap<String, List<String>>,
private val removeAnnotations: Set<String>,
private val deleteAnnotations: Set<String>,
private val stubAnnotations: Set<String>,
private val unwantedElements: UnwantedCache,
private val unwantedFields: MutableSet<FieldElement>,
private val deletedMethods: MutableSet<MethodElement>,
private val stubbedMethods: MutableSet<MethodElement>
) : KotlinAfterProcessor(ASM6, visitor, logger, kotlinMetadata), Repeatable<FilterTransformer> {
constructor(
visitor: ClassVisitor,
logger: Logger,
removeAnnotations: Set<String>,
deleteAnnotations: Set<String>,
stubAnnotations: Set<String>,
unwantedElements: UnwantedCache
) : this(
visitor = visitor,
logger = logger,
kotlinMetadata = mutableMapOf(),
removeAnnotations = removeAnnotations,
deleteAnnotations = deleteAnnotations,
stubAnnotations = stubAnnotations,
unwantedElements = unwantedElements,
unwantedFields = mutableSetOf(),
deletedMethods = mutableSetOf(),
stubbedMethods = mutableSetOf()
)
var className: String = "(unknown)"
private set
val isUnwantedClass: Boolean get() = isUnwantedClass(className)
override val hasUnwantedElements: Boolean
get() = unwantedFields.isNotEmpty()
|| deletedMethods.isNotEmpty()
|| stubbedMethods.isNotEmpty()
|| super.hasUnwantedElements
private fun isUnwantedClass(name: String): Boolean = unwantedElements.containsClass(name)
private fun hasDeletedSyntheticMethod(name: String): Boolean = deletedMethods.any { method ->
name.startsWith("$className\$${method.visibleName}\$")
}
override fun recreate(visitor: ClassVisitor) = FilterTransformer(
visitor = visitor,
logger = logger,
kotlinMetadata = kotlinMetadata,
removeAnnotations = removeAnnotations,
deleteAnnotations = deleteAnnotations,
stubAnnotations = stubAnnotations,
unwantedElements = unwantedElements,
unwantedFields = unwantedFields,
deletedMethods = deletedMethods,
stubbedMethods = stubbedMethods
)
override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array<String>?) {
className = clsName
logger.info("Class {}", clsName)
super.visit(version, access, clsName, signature, superName, interfaces)
}
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
if (removeAnnotations.contains(descriptor)) {
logger.info("- Removing annotation {}", descriptor)
return null
} else if (deleteAnnotations.contains(descriptor)) {
if (unwantedElements.addClass(className)) {
logger.info("- Identified class {} as unwanted", className)
}
}
return super.visitAnnotation(descriptor, visible)
}
override fun visitField(access: Int, fieldName: String, descriptor: String, signature: String?, value: Any?): FieldVisitor? {
val field = FieldElement(fieldName, descriptor)
logger.debug("--- field ---> {}", field)
if (unwantedFields.contains(field)) {
logger.info("- Deleted field {},{}", field.name, field.descriptor)
unwantedFields.remove(field)
return null
}
val fv = super.visitField(access, fieldName, descriptor, signature, value) ?: return null
return UnwantedFieldAdapter(fv, field)
}
override fun visitMethod(access: Int, methodName: String, descriptor: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
val method = MethodElement(methodName, descriptor, access)
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
}
/*
* Write the byte-code for the method's prototype, then check whether
* we need to replace the method's body with our "stub" code.
*/
val mv = super.visitMethod(access, methodName, descriptor, signature, exceptions) ?: return null
if (stubbedMethods.contains(method)) {
logger.info("- Stubbed out method {}{}", method.name, method.descriptor)
stubbedMethods.remove(method)
return if (method.isVoidFunction) VoidStubMethodAdapter(mv) else ThrowingStubMethodAdapter(mv)
}
return UnwantedMethodAdapter(mv, method)
}
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 (unwantedElements.addClass(clsName)) {
logger.info("- Deleted inner class {}", clsName)
}
} else if (isUnwantedClass(clsName)) {
logger.info("- Deleted reference to inner class: {}", clsName)
} else {
super.visitInnerClass(clsName, outerName, innerName, access)
}
}
override fun visitOuterClass(outerName: String, methodName: String?, methodDescriptor: String?) {
logger.debug("--- outer class {} [enclosing method {},{}]", outerName, methodName, methodDescriptor)
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)
}
}
override fun visitEnd() {
if (isUnwantedClass) {
/*
* Optimisation: Don't rewrite the Kotlin @Metadata
* annotation if we're going to delete this class.
*/
kotlinMetadata.clear()
}
super.visitEnd()
/*
* Some elements were created based on unreliable information,
* such as Kotlin @Metadata annotations. We cannot rely on
* these actually existing in the bytecode, and so we expire
* them after a fixed number of passes.
*/
deletedMethods.removeIf(MethodElement::isExpired)
unwantedFields.removeIf(FieldElement::isExpired)
}
/**
* Removes the deleted methods and fields from the Kotlin Class metadata.
*/
override fun processClassMetadata(d1: List<String>, d2: List<String>): List<String> {
val partitioned = deletedMethods.groupBy(MethodElement::isConstructor)
val prefix = "$className$"
return ClassMetadataTransformer(
logger = logger,
deletedFields = unwantedFields,
deletedFunctions = partitioned[false] ?: emptyList(),
deletedConstructors = partitioned[true] ?: emptyList(),
deletedNestedClasses = unwantedElements.classes.filter { it.startsWith(prefix) }.map { it.drop(prefix.length) },
deletedClasses = unwantedElements.classes,
handleExtraMethod = ::delete,
d1 = d1,
d2 = d2)
.transform()
}
/**
* Removes the deleted methods and fields from the Kotlin Package metadata.
*/
override fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String> {
return PackageMetadataTransformer(
logger = logger,
deletedFields = unwantedFields,
deletedFunctions = deletedMethods,
handleExtraMethod = ::delete,
d1 = d1,
d2 = d2)
.transform()
}
/**
* Callback function to mark extra methods for deletion.
* This will override a request for stubbing.
*/
private fun delete(method: MethodElement) {
if (deletedMethods.add(method) && stubbedMethods.remove(method)) {
logger.warn("-- method {}{} will be deleted instead of stubbed out",
method.name, method.descriptor)
}
}
/**
* Analyses the field to decide whether it should be deleted.
*/
private inner class UnwantedFieldAdapter(fv: FieldVisitor, private val field: FieldElement) : FieldVisitor(api, fv) {
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
if (removeAnnotations.contains(descriptor)) {
logger.info("- Removing annotation {} from field {},{}", descriptor, field.name, field.descriptor)
return null
} else if (deleteAnnotations.contains(descriptor)) {
if (unwantedFields.add(field)) {
logger.info("- Identified field {},{} as unwanted", field.name, field.descriptor)
}
}
return super.visitAnnotation(descriptor, visible)
}
}
/**
* Analyses the method to decide whether it should be deleted.
*/
private inner class UnwantedMethodAdapter(mv: MethodVisitor, private val method: MethodElement) : MethodVisitor(api, mv) {
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
if (removeAnnotations.contains(descriptor)) {
logger.info("- Removing annotation {} from method {}{}", descriptor, method.name, method.descriptor)
return null
} else if (deleteAnnotations.contains(descriptor)) {
if (deletedMethods.add(method)) {
logger.info("- Identified method {}{} for deletion", method.name, method.descriptor)
}
if (method.isKotlinSynthetic("annotations")) {
val extensionType = method.descriptor.extensionType
if (unwantedFields.add(FieldElement(name = method.visibleName, extension = extensionType))) {
logger.info("-- also identified property or typealias {},{} for deletion", method.visibleName, extensionType)
}
}
} else if (stubAnnotations.contains(descriptor) && (method.access and ACC_ABSTRACT) == 0) {
if (stubbedMethods.add(method)) {
logger.info("- Identified method {}{} for stubbing out", method.name, method.descriptor)
}
}
return super.visitAnnotation(descriptor, visible)
}
override fun visitMethodInsn(opcode: Int, ownerName: String, methodName: String, descriptor: String, isInterface: Boolean) {
if ((isUnwantedClass(ownerName) || (ownerName == className && deletedMethods.contains(MethodElement(methodName, descriptor))))
&& !stubbedMethods.contains(method)) {
if (deletedMethods.add(method)) {
logger.info("- Unwanted invocation of method {},{}{} from method {}{}", ownerName, methodName, descriptor, method.name, method.descriptor)
}
}
super.visitMethodInsn(opcode, ownerName, methodName, descriptor, isInterface)
}
override fun visitFieldInsn(opcode: Int, ownerName: String, fieldName: String, descriptor: String) {
if ((isUnwantedClass(ownerName) || (ownerName == className && unwantedFields.contains(FieldElement(fieldName, descriptor))))
&& !stubbedMethods.contains(method)) {
if (method.isConstructor) {
when (opcode) {
GETFIELD, GETSTATIC -> {
when (descriptor) {
"I", "S", "B", "C", "Z" -> visitIntInsn(BIPUSH, 0)
"J" -> visitInsn(LCONST_0)
"F" -> visitInsn(FCONST_0)
"D" -> visitInsn(DCONST_0)
else -> visitInsn(ACONST_NULL)
}
}
PUTFIELD, PUTSTATIC -> {
when (descriptor) {
"J", "D" -> visitInsn(POP2)
else -> visitInsn(POP)
}
}
else -> throw InvalidUserDataException("Unexpected opcode $opcode")
}
logger.info("- Unwanted reference to field {},{},{} REMOVED from constructor {}{}",
ownerName, fieldName, descriptor, method.name, method.descriptor)
return
} else if (deletedMethods.add(method)) {
logger.info("- Unwanted reference to field {},{},{} from method {}{}",
ownerName, fieldName, descriptor, method.name, method.descriptor)
}
}
super.visitFieldInsn(opcode, ownerName, fieldName, descriptor)
}
}
/**
* Write "stub" byte-code for this method, preserving its other annotations.
* The method's original byte-code is discarded.
*/
private abstract inner class StubbingMethodAdapter(mv: MethodVisitor) : MethodVisitor(api, mv) {
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
return if (stubAnnotations.contains(descriptor)) null else mv.visitAnnotation(descriptor, visible)
}
protected abstract fun writeStubCode()
final override fun visitCode() {
with (mv) {
visitCode()
writeStubCode()
visitMaxs(-1, -1) // Trigger computation of the max values.
visitEnd()
}
// Prevent this visitor from writing any more byte-code.
mv = null
}
}
/**
* Write a method that throws [UnsupportedOperationException] with message "Method has been deleted".
*/
private inner class ThrowingStubMethodAdapter(mv: MethodVisitor) : StubbingMethodAdapter(mv) {
override fun writeStubCode() {
with (mv) {
val throwEx = Label()
visitLabel(throwEx)
visitLineNumber(0, throwEx)
visitTypeInsn(NEW, "java/lang/UnsupportedOperationException")
visitInsn(DUP)
visitLdcInsn("Method has been deleted")
visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "(Ljava/lang/String;)V", false)
visitInsn(ATHROW)
}
}
}
/**
* Write an empty method. Can only be applied to methods that return void.
*/
private inner class VoidStubMethodAdapter(mv: MethodVisitor) : StubbingMethodAdapter(mv) {
override fun writeStubCode() {
mv.visitInsn(RETURN)
}
}
}

View File

@ -1,14 +0,0 @@
@file:Suppress("UNUSED")
package net.corda.gradle.jarfilter
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* This plugin definition is only needed by the tests.
*/
class JarFilterPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.logger.info("Applying JarFilter plugin")
}
}

View File

@ -1,288 +0,0 @@
package net.corda.gradle.jarfilter
import groovy.lang.Closure
import org.gradle.api.DefaultTask
import org.gradle.api.InvalidUserDataException
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.*
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import java.io.Closeable
import java.io.File
import java.io.IOException
import java.nio.file.*
import java.nio.file.StandardCopyOption.*
import java.util.zip.Deflater.BEST_COMPRESSION
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
import kotlin.math.max
@Suppress("Unused", "MemberVisibilityCanBePrivate")
open class JarFilterTask : DefaultTask() {
private companion object {
private const val DEFAULT_MAX_PASSES = 5
}
private val _jars: ConfigurableFileCollection = project.files()
@get:SkipWhenEmpty
@get:InputFiles
val jars: FileCollection get() = _jars
fun setJars(inputs: Any?) {
val files = inputs ?: return
_jars.setFrom(files)
}
fun jars(inputs: Any?) = setJars(inputs)
@get:Input
protected var forDelete: Set<String> = emptySet()
@get:Input
protected var forStub: Set<String> = emptySet()
@get:Input
protected var forRemove: Set<String> = emptySet()
@get:Input
protected var forSanitise: Set<String> = emptySet()
fun annotations(assign: Closure<List<String>>) {
assign.call()
}
@get:Console
var verbose: Boolean = false
@get:Input
var maxPasses: Int = DEFAULT_MAX_PASSES
set(value) {
field = max(value, 1)
}
@get:Input
var preserveTimestamps: Boolean = true
private var _outputDir = project.buildDir.resolve("filtered-libs")
@get:Internal
val outputDir: File get() = _outputDir
fun setOutputDir(d: File?) {
val dir = d ?: return
_outputDir = dir
}
fun outputDir(dir: File?) = setOutputDir(dir)
@get:OutputFiles
val filtered: FileCollection get() = project.files(jars.files.map(this::toFiltered))
private fun toFiltered(source: File) = File(outputDir, source.name.replace(JAR_PATTERN, "-filtered\$1"))
@TaskAction
fun filterJars() {
logger.info("JarFiltering:")
if (forDelete.isNotEmpty()) {
logger.info("- Elements annotated with one of '{}' will be deleted", forDelete.joinToString())
}
if (forStub.isNotEmpty()) {
logger.info("- Methods annotated with one of '{}' will be stubbed out", forStub.joinToString())
}
if (forRemove.isNotEmpty()) {
logger.info("- Annotations '{}' will be removed entirely", forRemove.joinToString())
}
if (forSanitise.isNotEmpty()) {
logger.info("- Annotations '{}' will be removed from primary constructors", forSanitise.joinToString())
}
checkDistinctAnnotations()
try {
jars.forEach { jar ->
logger.info("Filtering {}", jar)
Filter(jar).run()
}
} catch (e: Exception) {
rethrowAsUncheckedException(e)
}
}
private fun checkDistinctAnnotations() {
logger.info("Checking that all annotations are distinct.")
val annotations = forRemove.toHashSet().apply {
addAll(forDelete)
addAll(forStub)
removeAll(forRemove)
}
forDelete.forEach {
if (!annotations.remove(it)) {
failWith("Annotation '$it' also appears in JarFilter 'forDelete' section")
}
}
forStub.forEach {
if (!annotations.remove(it)) {
failWith("Annotation '$it' also appears in JarFilter 'forStub' section")
}
}
if (!annotations.isEmpty()) {
failWith("SHOULDN'T HAPPEN - Martian annotations! '${annotations.joinToString()}'")
}
}
private fun failWith(message: String): Nothing = throw InvalidUserDataException(message)
private fun verbose(format: String, vararg objects: Any) {
if (verbose) {
logger.info(format, *objects)
}
}
private inner class Filter(inFile: File) {
private val unwantedElements = UnwantedCache()
private val source: Path = inFile.toPath()
private val target: Path = toFiltered(inFile).toPath()
private val descriptorsForRemove = toDescriptors(forRemove)
private val descriptorsForDelete = toDescriptors(forDelete)
private val descriptorsForStub = toDescriptors(forStub)
private val descriptorsForSanitising = toDescriptors(forSanitise)
init {
Files.deleteIfExists(target)
}
fun run() {
logger.info("Filtering to: {}", target)
var input = source
try {
if (descriptorsForSanitising.isNotEmpty() && SanitisingPass(input).use { it.run() }) {
input = target.moveToInput()
}
var passes = 1
while (true) {
verbose("Pass {}", passes)
val isModified = FilterPass(input).use { it.run() }
if (!isModified) {
logger.info("No changes after latest pass - exiting.")
break
} else if (++passes > maxPasses) {
break
}
input = target.moveToInput()
}
} catch (e: Exception) {
logger.error("Error filtering '{}' elements from {}", ArrayList(forRemove).apply { addAll(forDelete); addAll(forStub) }, input)
throw e
}
}
private fun Path.moveToInput(): Path {
return Files.move(this, Files.createTempFile(parent, "filter-", ".tmp"), REPLACE_EXISTING).also {
verbose("New input JAR: {}", it)
}
}
private abstract inner class Pass(input: Path): Closeable {
/**
* Use [ZipFile] instead of [java.util.jar.JarInputStream] because
* JarInputStream consumes MANIFEST.MF when it's the first or second entry.
*/
protected val inJar = ZipFile(input.toFile())
protected val outJar = ZipOutputStream(Files.newOutputStream(target))
protected var isModified = false
@Throws(IOException::class)
override fun close() {
inJar.use {
outJar.close()
}
}
abstract fun transform(inBytes: ByteArray): ByteArray
fun run(): Boolean {
outJar.setLevel(BEST_COMPRESSION)
outJar.setComment(inJar.comment)
for (entry in inJar.entries()) {
val entryData = inJar.getInputStream(entry)
if (entry.isDirectory || !entry.name.endsWith(".class")) {
// This entry's byte contents have not changed,
// but may still need to be recompressed.
outJar.putNextEntry(entry.copy().withFileTimestamps(preserveTimestamps))
entryData.copyTo(outJar)
} else {
val classData = transform(entryData.readBytes())
if (classData.isNotEmpty()) {
// This entry's byte contents have almost certainly
// changed, and will be stored compressed.
outJar.putNextEntry(entry.asCompressed().withFileTimestamps(preserveTimestamps))
outJar.write(classData)
}
}
}
return isModified
}
}
private inner class SanitisingPass(input: Path) : Pass(input) {
override fun transform(inBytes: ByteArray): ByteArray {
return ClassWriter(0).let { writer ->
val transformer = SanitisingTransformer(writer, logger, descriptorsForSanitising)
ClassReader(inBytes).accept(transformer, 0)
isModified = isModified or transformer.isModified
writer.toByteArray()
}
}
}
private inner class FilterPass(input: Path) : Pass(input) {
override fun transform(inBytes: ByteArray): ByteArray {
var reader = ClassReader(inBytes)
var writer = ClassWriter(COMPUTE_MAXS)
var transformer = FilterTransformer(
visitor = writer,
logger = logger,
removeAnnotations = descriptorsForRemove,
deleteAnnotations = descriptorsForDelete,
stubAnnotations = descriptorsForStub,
unwantedElements = unwantedElements
)
/*
* First pass: This might not find anything to remove!
*/
reader.accept(transformer, 0)
if (transformer.isUnwantedClass || transformer.hasUnwantedElements) {
isModified = true
do {
/*
* Rewrite the class without any of the unwanted elements.
* If we're deleting the class then make sure we identify all of
* its inner classes too, for the next filter pass to delete.
*/
reader = ClassReader(writer.toByteArray())
writer = ClassWriter(COMPUTE_MAXS)
transformer = transformer.recreate(writer)
reader.accept(transformer, 0)
} while (!transformer.isUnwantedClass && transformer.hasUnwantedElements)
}
return if (transformer.isUnwantedClass) {
// The entire class is unwanted, so don't write it out.
logger.info("Deleting class {}", transformer.className)
byteArrayOf()
} else {
writer.toByteArray()
}
}
}
}
}

View File

@ -1,159 +0,0 @@
package net.corda.gradle.jarfilter
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.*
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassVisitor
/**
* Kotlin support: Loads the ProtoBuf data from the [kotlin.Metadata] annotation.
*/
abstract class KotlinAwareVisitor(
api: Int,
visitor: ClassVisitor,
protected val logger: Logger,
protected val kotlinMetadata: MutableMap<String, List<String>>
) : ClassVisitor(api, visitor) {
private companion object {
/** See [org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind]. */
private const val KOTLIN_CLASS = 1
private const val KOTLIN_FILE = 2
private const val KOTLIN_SYNTHETIC = 3
private const val KOTLIN_MULTIFILE_PART = 5
}
private var classKind: Int = 0
open val hasUnwantedElements: Boolean get() = kotlinMetadata.isNotEmpty()
protected open val level: LogLevel = LogLevel.INFO
protected abstract fun processClassMetadata(d1: List<String>, d2: List<String>): List<String>
protected abstract fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String>
protected abstract fun processKotlinAnnotation()
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
val av = super.visitAnnotation(descriptor, visible) ?: return null
return if (descriptor == METADATA_DESC) KotlinMetadataAdaptor(av) else av
}
protected fun processMetadata() {
if (kotlinMetadata.isNotEmpty()) {
logger.log(level, "- Examining Kotlin @Metadata[k={}]", classKind)
val d1 = kotlinMetadata.remove(METADATA_DATA_FIELD_NAME)
val d2 = kotlinMetadata.remove(METADATA_STRINGS_FIELD_NAME)
if (d1 != null && d1.isNotEmpty() && d2 != null) {
processMetadata(d1, d2).apply {
if (isNotEmpty()) {
kotlinMetadata[METADATA_DATA_FIELD_NAME] = this
kotlinMetadata[METADATA_STRINGS_FIELD_NAME] = d2
}
}
}
}
}
private fun processMetadata(d1: List<String>, d2: List<String>): List<String> {
return when (classKind) {
KOTLIN_CLASS -> processClassMetadata(d1, d2)
KOTLIN_FILE, KOTLIN_MULTIFILE_PART -> processPackageMetadata(d1, d2)
KOTLIN_SYNTHETIC -> {
logger.log(level,"-- synthetic class ignored")
emptyList()
}
else -> {
/*
* For class-kind=4 (i.e. "multi-file"), we currently
* expect d1=[list of multi-file-part classes], d2=null.
*/
logger.log(level,"-- unsupported class-kind {}", classKind)
emptyList()
}
}
}
private inner class KotlinMetadataAdaptor(av: AnnotationVisitor): AnnotationVisitor(api, av) {
override fun visit(name: String?, value: Any?) {
if (name == KIND_FIELD_NAME) {
classKind = value as Int
}
super.visit(name, value)
}
override fun visitArray(name: String): AnnotationVisitor? {
val av = super.visitArray(name)
if (av != null) {
val data = kotlinMetadata.remove(name) ?: return ArrayAccumulator(av, name)
logger.debug("-- rewrote @Metadata.{}[{}]", name, data.size)
data.forEach { av.visit(null, it) }
av.visitEnd()
}
return null
}
override fun visitEnd() {
super.visitEnd()
processKotlinAnnotation()
}
}
private inner class ArrayAccumulator(av: AnnotationVisitor, private val name: String) : AnnotationVisitor(api, av) {
private val data: MutableList<String> = mutableListOf()
override fun visit(name: String?, value: Any?) {
super.visit(name, value)
data.add(value as String)
}
override fun visitEnd() {
super.visitEnd()
kotlinMetadata[name] = data
logger.debug("-- read @Metadata.{}[{}]", name, data.size)
}
}
}
/**
* Loads the ProtoBuf data from the [kotlin.Metadata] annotation, or
* writes new ProtoBuf data that was created during a previous pass.
*/
abstract class KotlinAfterProcessor(
api: Int,
visitor: ClassVisitor,
logger: Logger,
kotlinMetadata: MutableMap<String, List<String>>
) : KotlinAwareVisitor(api, visitor, logger, kotlinMetadata) {
/**
* Process the metadata once we have finished visiting the class.
* This will allow us to rewrite the [kotlin.Metadata] annotation
* in the next visit.
*/
override fun visitEnd() {
super.visitEnd()
processMetadata()
}
/**
* Do nothing immediately after we have parsed [kotlin.Metadata].
*/
final override fun processKotlinAnnotation() {}
}
/**
* Loads the ProtoBuf data from the [kotlin.Metadata] annotation
* and then processes it before visiting the rest of the class.
*/
abstract class KotlinBeforeProcessor(
api: Int,
visitor: ClassVisitor,
logger: Logger,
kotlinMetadata: MutableMap<String, List<String>>
) : KotlinAwareVisitor(api, visitor, logger, kotlinMetadata) {
/**
* Process the ProtoBuf data as soon as we have parsed [kotlin.Metadata].
*/
final override fun processKotlinAnnotation() = processMetadata()
}

View File

@ -1,128 +0,0 @@
package net.corda.gradle.jarfilter
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.logging.Logger
import org.gradle.api.tasks.*
import java.io.Closeable
import java.io.File
import java.io.IOException
import java.nio.file.*
import java.util.zip.Deflater.BEST_COMPRESSION
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
@Suppress("Unused", "MemberVisibilityCanBePrivate")
open class MetaFixerTask : DefaultTask() {
private val _jars: ConfigurableFileCollection = project.files()
@get:SkipWhenEmpty
@get:InputFiles
val jars: FileCollection
get() = _jars
fun setJars(inputs: Any?) {
val files = inputs ?: return
_jars.setFrom(files)
}
fun jars(inputs: Any?) = setJars(inputs)
private var _outputDir = project.buildDir.resolve("metafixer-libs")
@get:Internal
val outputDir: File
get() = _outputDir
fun setOutputDir(d: File?) {
val dir = d ?: return
_outputDir = dir
}
fun outputDir(dir: File?) = setOutputDir(dir)
private var _suffix: String = "-metafixed"
@get:Input
val suffix: String get() = _suffix
fun setSuffix(input: String?) {
_suffix = input ?: return
}
fun suffix(suffix: String?) = setSuffix(suffix)
@get:Input
var preserveTimestamps: Boolean = true
@TaskAction
fun fixMetadata() {
logger.info("Fixing Kotlin @Metadata")
try {
jars.forEach { jar ->
logger.info("Reading from {}", jar)
MetaFix(jar).use { it.run() }
}
} catch (e: Exception) {
rethrowAsUncheckedException(e)
}
}
@get:OutputFiles
val metafixed: FileCollection get() = project.files(jars.files.map(this::toMetaFixed))
private fun toMetaFixed(source: File) = File(outputDir, source.name.replace(JAR_PATTERN, "$suffix\$1"))
private inner class MetaFix(inFile: File) : Closeable {
/**
* Use [ZipFile] instead of [java.util.jar.JarInputStream] because
* JarInputStream consumes MANIFEST.MF when it's the first or second entry.
*/
private val target: Path = toMetaFixed(inFile).toPath()
private val inJar = ZipFile(inFile)
private val outJar: ZipOutputStream
init {
// Default options for newOutputStream() are CREATE, TRUNCATE_EXISTING.
outJar = ZipOutputStream(Files.newOutputStream(target)).apply {
setLevel(BEST_COMPRESSION)
}
}
@Throws(IOException::class)
override fun close() {
inJar.use {
outJar.close()
}
}
fun run() {
logger.info("Writing to {}", target)
outJar.setComment(inJar.comment)
val classNames = inJar.entries().asSequence().namesEndingWith(".class")
for (entry in inJar.entries()) {
val entryData = inJar.getInputStream(entry)
if (entry.isDirectory || !entry.name.endsWith(".class")) {
// This entry's byte contents have not changed,
// but may still need to be recompressed.
outJar.putNextEntry(entry.copy().withFileTimestamps(preserveTimestamps))
entryData.copyTo(outJar)
} else {
// This entry's byte contents have almost certainly
// changed, and will be stored compressed.
val classData = entryData.readBytes().fixMetadata(logger, classNames)
outJar.putNextEntry(entry.asCompressed().withFileTimestamps(preserveTimestamps))
outJar.write(classData)
}
}
}
}
private fun Sequence<ZipEntry>.namesEndingWith(suffix: String): Set<String> {
return filter { it.name.endsWith(suffix) }.map { it.name.dropLast(suffix.length) }.toSet()
}
}
fun ByteArray.fixMetadata(logger: Logger, classNames: Set<String>): ByteArray
= execute({ writer -> MetaFixerVisitor(writer, logger, classNames) })

View File

@ -1,278 +0,0 @@
package net.corda.gradle.jarfilter
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.ProtoBuf.Class.Kind.*
import org.jetbrains.kotlin.metadata.deserialization.Flags.*
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.*
import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.EXTENSION_REGISTRY
import org.jetbrains.kotlin.protobuf.ExtensionRegistryLite
import org.jetbrains.kotlin.protobuf.MessageLite
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
/**
* Base class for aligning the contents of [kotlin.Metadata] annotations
* with the contents of the host byte-code.
* This is used by [MetaFixerVisitor] for [MetaFixerTask].
*/
internal abstract class MetaFixerTransformer<out T : MessageLite>(
private val logger: Logger,
private val actualFields: Collection<FieldElement>,
private val actualMethods: Collection<String>,
private val actualNestedClasses: Collection<String>,
private val actualClasses: Collection<String>,
d1: List<String>,
d2: List<String>,
parser: (InputStream, ExtensionRegistryLite) -> T
) {
private val stringTableTypes: StringTableTypes
protected val nameResolver: NameResolver
protected val message: T
protected abstract val typeTable: TypeTable
protected open val classDescriptor: String = ""
protected open val classKind: ProtoBuf.Class.Kind? = null
protected abstract val properties: MutableList<ProtoBuf.Property>
protected abstract val functions: MutableList<ProtoBuf.Function>
protected abstract val constructors: MutableList<ProtoBuf.Constructor>
protected open val nestedClassNames: MutableList<Int> get() = throw UnsupportedOperationException("No nestedClassNames")
protected open val sealedSubclassNames: MutableList<Int> get() = throw UnsupportedOperationException("No sealedSubclassNames")
init {
val input = ByteArrayInputStream(BitEncoding.decodeBytes(d1.toTypedArray()))
stringTableTypes = StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY)
nameResolver = JvmNameResolver(stringTableTypes, d2.toTypedArray())
message = parser(input, EXTENSION_REGISTRY)
}
abstract fun rebuild(): T
private fun filterNestedClasses(): Int {
if (classKind == null) return 0
var count = 0
var idx = 0
while (idx < nestedClassNames.size) {
val nestedClassName = nameResolver.getString(nestedClassNames[idx])
if (actualNestedClasses.contains(nestedClassName)) {
++idx
} else {
logger.info("-- removing nested class: {}", nestedClassName)
nestedClassNames.removeAt(idx)
++count
}
}
return count
}
private fun filterSealedSubclassNames(): Int {
if (classKind == null) return 0
var count = 0
var idx = 0
while (idx < sealedSubclassNames.size) {
val sealedSubclassName = nameResolver.getClassInternalName(sealedSubclassNames[idx])
if (actualClasses.contains(sealedSubclassName)) {
++idx
} else {
logger.info("-- removing sealed subclass: {}", sealedSubclassName)
sealedSubclassNames.removeAt(idx)
++count
}
}
return count
}
private fun filterFunctions(): Int {
var count = 0
var idx = 0
removed@ while (idx < functions.size) {
val function = functions[idx]
val signature = JvmProtoBufUtil.getJvmMethodSignature(function, nameResolver, typeTable)
if (signature != null) {
if (!actualMethods.contains(signature)) {
logger.info("-- removing method: {}", signature)
functions.removeAt(idx)
++count
continue@removed
} else if (function.valueParameterList.hasAnyDefaultValues
&& !actualMethods.contains(signature.toKotlinDefaultFunction(classDescriptor))) {
logger.info("-- removing default parameter values: {}", signature)
functions[idx] = function.toBuilder()
.updateValueParameters(ProtoBuf.ValueParameter::clearDeclaresDefaultValue)
.build()
++count
}
}
++idx
}
return count
}
private fun filterConstructors(): Int {
var count = 0
var idx = 0
removed@ while (idx < constructors.size) {
val constructor = constructors[idx]
val signature = JvmProtoBufUtil.getJvmConstructorSignature(constructor, nameResolver, typeTable)
if (signature != null) {
if (!actualMethods.contains(signature)) {
logger.info("-- removing constructor: {}", signature)
constructors.removeAt(idx)
++count
continue@removed
} else if (constructor.valueParameterList.hasAnyDefaultValues
&& !actualMethods.contains(signature.toKotlinDefaultConstructor())) {
logger.info("-- removing default parameter values: {}", signature)
constructors[idx] = constructor.toBuilder()
.updateValueParameters(ProtoBuf.ValueParameter::clearDeclaresDefaultValue)
.build()
++count
}
}
++idx
}
return count
}
private fun filterProperties(): Int {
var count = 0
var idx = 0
removed@ while (idx < properties.size) {
val property = properties[idx]
val signature = property.getExtensionOrNull(propertySignature)
if (signature != null) {
val field = signature.toFieldElement(property, nameResolver, typeTable)
val getterMethod = signature.toGetter(nameResolver)
/**
* A property annotated with [JvmField] will use a field instead of a getter method.
* But properties without [JvmField] will also usually have a backing field. So we only
* remove a property that has either lost its getter method, or never had a getter method
* and has lost its field.
*
* Having said that, we cannot remove [JvmField] properties from a companion object class
* because these properties are implemented as static fields on the companion's host class.
*/
val isValidProperty = if (getterMethod == null) {
actualFields.contains(field) || classKind == COMPANION_OBJECT
} else {
actualMethods.contains(getterMethod.signature)
}
if (!isValidProperty) {
logger.info("-- removing property: {},{}", field.name, field.descriptor)
properties.removeAt(idx)
++count
continue@removed
}
}
++idx
}
return count
}
fun transform(): List<String> {
var count = filterProperties() + filterFunctions() + filterNestedClasses() + filterSealedSubclassNames()
if (classKind != ANNOTATION_CLASS) {
count += filterConstructors()
}
if (count == 0) {
return emptyList()
}
val bytes = ByteArrayOutputStream()
stringTableTypes.writeDelimitedTo(bytes)
rebuild().writeTo(bytes)
return BitEncoding.encodeBytes(bytes.toByteArray()).toList()
}
}
/**
* Aligns a [kotlin.Metadata] annotation containing a [ProtoBuf.Class] object
* in its [d1][kotlin.Metadata.d1] field with the byte-code of its host class.
*/
internal class ClassMetaFixerTransformer(
logger: Logger,
actualFields: Collection<FieldElement>,
actualMethods: Collection<String>,
actualNestedClasses: Collection<String>,
actualClasses: Collection<String>,
d1: List<String>,
d2: List<String>
) : MetaFixerTransformer<ProtoBuf.Class>(
logger,
actualFields,
actualMethods,
actualNestedClasses,
actualClasses,
d1,
d2,
ProtoBuf.Class::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val classDescriptor = "L${nameResolver.getClassInternalName(message.fqName)};"
override val classKind: ProtoBuf.Class.Kind = CLASS_KIND.get(message.flags)
override val properties = mutableList(message.propertyList)
override val functions = mutableList(message.functionList)
override val constructors = mutableList(message.constructorList)
override val nestedClassNames = mutableList(message.nestedClassNameList)
override val sealedSubclassNames= mutableList(message.sealedSubclassFqNameList)
override fun rebuild(): ProtoBuf.Class = message.toBuilder().apply {
clearConstructor().addAllConstructor(constructors)
clearFunction().addAllFunction(functions)
if (nestedClassNames.size != nestedClassNameCount) {
clearNestedClassName().addAllNestedClassName(nestedClassNames)
}
if (sealedSubclassNames.size != sealedSubclassFqNameCount) {
clearSealedSubclassFqName().addAllSealedSubclassFqName(sealedSubclassNames)
}
if (properties.size != propertyCount) {
clearProperty().addAllProperty(properties)
}
}.build()
}
/**
* Aligns a [kotlin.Metadata] annotation containing a [ProtoBuf.Package] object
* in its [d1][kotlin.Metadata.d1] field with the byte-code of its host class.
*/
internal class PackageMetaFixerTransformer(
logger: Logger,
actualFields: Collection<FieldElement>,
actualMethods: Collection<String>,
d1: List<String>,
d2: List<String>
) : MetaFixerTransformer<ProtoBuf.Package>(
logger,
actualFields,
actualMethods,
emptyList(),
emptyList(),
d1,
d2,
ProtoBuf.Package::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val properties = mutableList(message.propertyList)
override val functions = mutableList(message.functionList)
override val constructors = mutableListOf<ProtoBuf.Constructor>()
override fun rebuild(): ProtoBuf.Package = message.toBuilder().apply {
clearFunction().addAllFunction(functions)
if (properties.size != propertyCount) {
clearProperty().addAllProperty(properties)
}
}.build()
}

View File

@ -1,76 +0,0 @@
package net.corda.gradle.jarfilter
import org.gradle.api.logging.Logger
import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.*
/**
* ASM [ClassVisitor] for the MetaFixer task. This visitor inventories every function,
* property and inner class within the byte-code and then passes this information to
* the [MetaFixerTransformer].
*/
class MetaFixerVisitor private constructor(
visitor: ClassVisitor,
logger: Logger,
kotlinMetadata: MutableMap<String, List<String>>,
private val classNames: Set<String>,
private val fields: MutableSet<FieldElement>,
private val methods: MutableSet<String>,
private val nestedClasses: MutableSet<String>
) : KotlinAfterProcessor(ASM6, visitor, logger, kotlinMetadata), Repeatable<MetaFixerVisitor> {
constructor(visitor: ClassVisitor, logger: Logger, classNames: Set<String>)
: this(visitor, logger, mutableMapOf(), classNames, mutableSetOf(), mutableSetOf(), mutableSetOf())
override fun recreate(visitor: ClassVisitor) = MetaFixerVisitor(visitor, logger, kotlinMetadata, classNames, fields, methods, nestedClasses)
private var className: String = "(unknown)"
override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array<String>?) {
className = clsName
logger.info("Class {}", clsName)
super.visit(version, access, clsName, signature, superName, interfaces)
}
override fun visitField(access: Int, fieldName: String, descriptor: String, signature: String?, value: Any?): FieldVisitor? {
if (fields.add(FieldElement(fieldName, descriptor))) {
logger.info("- field {},{}", fieldName, descriptor)
}
return super.visitField(access, fieldName, descriptor, signature, value)
}
override fun visitMethod(access: Int, methodName: String, descriptor: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
if (methods.add(methodName + descriptor)) {
logger.info("- method {}{}", methodName, descriptor)
}
return super.visitMethod(access, methodName, descriptor, signature, exceptions)
}
override fun visitInnerClass(clsName: String, outerName: String?, innerName: String?, access: Int) {
if (outerName == className && innerName != null && nestedClasses.add(innerName)) {
logger.info("- inner class {}", clsName)
}
return super.visitInnerClass(clsName, outerName, innerName, access)
}
override fun processClassMetadata(d1: List<String>, d2: List<String>): List<String> {
return ClassMetaFixerTransformer(
logger = logger,
actualFields = fields,
actualMethods = methods,
actualNestedClasses = nestedClasses,
actualClasses = classNames,
d1 = d1,
d2 = d2)
.transform()
}
override fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String> {
return PackageMetaFixerTransformer(
logger = logger,
actualFields = fields,
actualMethods = methods,
d1 = d1,
d2 = d2)
.transform()
}
}

View File

@ -1,319 +0,0 @@
package net.corda.gradle.jarfilter
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.Flags.*
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.*
import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.EXTENSION_REGISTRY
import org.jetbrains.kotlin.protobuf.ExtensionRegistryLite
import org.jetbrains.kotlin.protobuf.MessageLite
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
/**
* Base class for removing unwanted elements from [kotlin.Metadata] annotations.
* This is used by [FilterTransformer] for [JarFilterTask].
*/
internal abstract class MetadataTransformer<out T : MessageLite>(
private val logger: Logger,
private val deletedFields: Collection<FieldElement>,
private val deletedFunctions: Collection<MethodElement>,
private val deletedConstructors: Collection<MethodElement>,
private val deletedNestedClasses: Collection<String>,
private val deletedClasses: Collection<String>,
private val handleExtraMethod: (MethodElement) -> Unit,
d1: List<String>,
d2: List<String>,
parser: (InputStream, ExtensionRegistryLite) -> T
) {
private val stringTableTypes: StringTableTypes
protected val nameResolver: NameResolver
protected val message: T
protected abstract val typeTable: TypeTable
protected open val className: String get() = throw UnsupportedOperationException("No className")
protected open val nestedClassNames: MutableList<Int> get() = throw UnsupportedOperationException("No nestedClassNames")
protected open val sealedSubclassNames: MutableList<Int> get() = throw UnsupportedOperationException("No sealedSubclassNames")
protected abstract val properties: MutableList<ProtoBuf.Property>
protected abstract val functions: MutableList<ProtoBuf.Function>
protected open val constructors: MutableList<ProtoBuf.Constructor> get() = throw UnsupportedOperationException("No constructors")
protected abstract val typeAliases: MutableList<ProtoBuf.TypeAlias>
init {
val input = ByteArrayInputStream(BitEncoding.decodeBytes(d1.toTypedArray()))
stringTableTypes = StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY)
nameResolver = JvmNameResolver(stringTableTypes, d2.toTypedArray())
message = parser(input, EXTENSION_REGISTRY)
}
abstract fun rebuild(): T
fun transform(): List<String> {
val count = (
filterProperties()
+ filterFunctions()
+ filterConstructors()
+ filterNestedClasses()
+ filterTypeAliases()
+ filterSealedSubclasses()
)
if (count == 0) {
return emptyList()
}
val bytes = ByteArrayOutputStream()
stringTableTypes.writeDelimitedTo(bytes)
rebuild().writeTo(bytes)
return BitEncoding.encodeBytes(bytes.toByteArray()).toList()
}
private fun filterNestedClasses(): Int {
if (deletedNestedClasses.isEmpty()) return 0
var count = 0
var idx = 0
while (idx < nestedClassNames.size) {
val nestedClassName = nameResolver.getString(nestedClassNames[idx])
if (deletedNestedClasses.contains(nestedClassName)) {
logger.info("-- removing nested class: {}", nestedClassName)
nestedClassNames.removeAt(idx)
++count
} else {
++idx
}
}
return count
}
private fun filterConstructors(): Int = deletedConstructors.count(::filterConstructor)
private fun filterConstructor(deleted: MethodElement): Boolean {
/*
* Constructors with the default parameter marker are synthetic and DO NOT have
* entries in the metadata. So we construct an element for the "primary" one
* that it was synthesised for, and which we DO expect to find.
*/
val deletedPrimary = deleted.asKotlinNonDefaultConstructor()
for (idx in 0 until constructors.size) {
val constructor = constructors[idx]
val signature = JvmProtoBufUtil.getJvmConstructorSignature(constructor, nameResolver, typeTable)
if (signature == deleted.signature) {
if (IS_SECONDARY.get(constructor.flags)) {
logger.info("-- removing constructor: {}", deleted.signature)
} else {
logger.warn("Removing primary constructor: {}{}", className, deleted.descriptor)
}
constructors.removeAt(idx)
return true
} else if (signature == deletedPrimary?.signature) {
constructors[idx] = constructor.toBuilder()
.updateValueParameters(ProtoBuf.ValueParameter::clearDeclaresDefaultValue)
.build()
logger.info("-- removing default parameter values: {}", signature)
return true
}
}
return false
}
private fun filterFunctions(): Int = deletedFunctions.count(::filterFunction)
private fun filterFunction(deleted: MethodElement): Boolean {
for (idx in 0 until functions.size) {
val function = functions[idx]
if (nameResolver.getString(function.name) == deleted.name) {
val signature = JvmProtoBufUtil.getJvmMethodSignature(function, nameResolver, typeTable)
if (signature == deleted.signature) {
logger.info("-- removing function: {}", deleted.signature)
functions.removeAt(idx)
return true
}
}
}
return false
}
private fun filterProperties(): Int = deletedFields.count(::filterProperty)
private fun filterProperty(deleted: FieldElement): Boolean {
for (idx in 0 until properties.size) {
val property = properties[idx]
val signature = property.getExtensionOrNull(propertySignature) ?: continue
val field = signature.toFieldElement(property, nameResolver, typeTable)
if (field.name.toVisible() == deleted.name) {
// Check that this property's getter has the correct descriptor.
// If it doesn't then we have the wrong property here.
val getter = signature.toGetter(nameResolver)
if (getter != null) {
if (!getter.descriptor.startsWith(deleted.extension)) {
continue
}
deleteExtra(getter)
}
signature.toSetter(nameResolver)?.apply(::deleteExtra)
logger.info("-- removing property: {},{}", field.name, field.descriptor)
properties.removeAt(idx)
return true
}
}
return false
}
private fun deleteExtra(func: MethodElement) {
if (!deletedFunctions.contains(func)) {
logger.info("-- identified extra method {} for deletion", func.signature)
handleExtraMethod(func)
filterFunction(func)
}
}
private fun filterTypeAliases(): Int {
if (deletedFields.isEmpty()) return 0
var count = 0
var idx = 0
while (idx < typeAliases.size) {
val aliasName = nameResolver.getString(typeAliases[idx].name)
if (deletedFields.any { it.name == aliasName && it.extension == "()" }) {
logger.info("-- removing typealias: {}", aliasName)
typeAliases.removeAt(idx)
++count
} else {
++idx
}
}
return count
}
private fun filterSealedSubclasses(): Int {
if (deletedClasses.isEmpty()) return 0
var count = 0
var idx = 0
while (idx < sealedSubclassNames.size) {
val subclassName = nameResolver.getClassInternalName(sealedSubclassNames[idx])
if (deletedClasses.contains(subclassName)) {
logger.info("-- removing sealed subclass: {}", subclassName)
sealedSubclassNames.removeAt(idx)
++count
} else {
++idx
}
}
return count
}
/**
* Removes any Kotlin suffix, e.g. "$delegate" or "$annotations".
*/
private fun String.toVisible(): String {
val idx = indexOf('$')
return if (idx == -1) this else substring(0, idx)
}
}
/**
* Removes elements from a [kotlin.Metadata] annotation that contains
* a [ProtoBuf.Class] object in its [d1][kotlin.Metadata.d1] field.
*/
internal class ClassMetadataTransformer(
logger: Logger,
deletedFields: Collection<FieldElement>,
deletedFunctions: Collection<MethodElement>,
deletedConstructors: Collection<MethodElement>,
deletedNestedClasses: Collection<String>,
deletedClasses: Collection<String>,
handleExtraMethod: (MethodElement) -> Unit,
d1: List<String>,
d2: List<String>
) : MetadataTransformer<ProtoBuf.Class>(
logger,
deletedFields,
deletedFunctions,
deletedConstructors,
deletedNestedClasses,
deletedClasses,
handleExtraMethod,
d1,
d2,
ProtoBuf.Class::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val className = nameResolver.getClassInternalName(message.fqName)
override val nestedClassNames = mutableList(message.nestedClassNameList)
override val sealedSubclassNames = mutableList(message.sealedSubclassFqNameList)
override val properties = mutableList(message.propertyList)
override val functions = mutableList(message.functionList)
override val constructors = mutableList(message.constructorList)
override val typeAliases = mutableList(message.typeAliasList)
override fun rebuild(): ProtoBuf.Class = message.toBuilder().apply {
clearConstructor().addAllConstructor(constructors)
if (nestedClassNames.size != nestedClassNameCount) {
clearNestedClassName().addAllNestedClassName(nestedClassNames)
}
if (functions.size != functionCount) {
clearFunction().addAllFunction(functions)
}
if (properties.size != propertyCount) {
clearProperty().addAllProperty(properties)
}
if (typeAliases.size != typeAliasCount) {
clearTypeAlias().addAllTypeAlias(typeAliases)
}
if (sealedSubclassNames.size != sealedSubclassFqNameCount) {
clearSealedSubclassFqName().addAllSealedSubclassFqName(sealedSubclassNames)
}
}.build()
}
/**
* Removes elements from a [kotlin.Metadata] annotation that contains
* a [ProtoBuf.Package] object in its [d1][kotlin.Metadata.d1] field.
*/
internal class PackageMetadataTransformer(
logger: Logger,
deletedFields: Collection<FieldElement>,
deletedFunctions: Collection<MethodElement>,
handleExtraMethod: (MethodElement) -> Unit,
d1: List<String>,
d2: List<String>
) : MetadataTransformer<ProtoBuf.Package>(
logger,
deletedFields,
deletedFunctions,
emptyList(),
emptyList(),
emptyList(),
handleExtraMethod,
d1,
d2,
ProtoBuf.Package::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val properties = mutableList(message.propertyList)
override val functions = mutableList(message.functionList)
override val typeAliases = mutableList(message.typeAliasList)
override fun rebuild(): ProtoBuf.Package = message.toBuilder().apply {
if (functions.size != functionCount) {
clearFunction().addAllFunction(functions)
}
if (properties.size != propertyCount) {
clearProperty().addAllProperty(properties)
}
if (typeAliases.size != typeAliasCount) {
clearTypeAlias().addAllTypeAlias(typeAliases)
}
}.build()
}

View File

@ -1,8 +0,0 @@
package net.corda.gradle.jarfilter
import org.objectweb.asm.ClassVisitor
interface Repeatable<T : ClassVisitor> {
fun recreate(visitor: ClassVisitor): T
val hasUnwantedElements: Boolean
}

View File

@ -1,81 +0,0 @@
package net.corda.gradle.jarfilter
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.Flags.*
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.*
import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.EXTENSION_REGISTRY
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.getJvmConstructorSignature
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.*
import java.io.ByteArrayInputStream
/**
* This is (hopefully?!) a temporary solution for classes with [JvmOverloads] constructors.
* We need to be able to annotate ONLY the secondary constructors for such classes, but Kotlin
* will apply any annotation to all constructors equally. Nor can we replace the overloaded
* constructor with individual constructors because this will break ABI compatibility. (Kotlin
* generates a synthetic public constructor to handle default parameter values.)
*
* This transformer identifies a class's primary constructor and removes all of its unwanted annotations.
* It will become superfluous when Kotlin allows us to target only the secondary constructors with our
* filtering annotations in the first place.
*/
class SanitisingTransformer(visitor: ClassVisitor, logger: Logger, private val unwantedAnnotations: Set<String>)
: KotlinBeforeProcessor(ASM6, visitor, logger, mutableMapOf()) {
var isModified: Boolean = false
private set
override val level: LogLevel = LogLevel.DEBUG
private var className: String = "(unknown)"
private var primaryConstructor: MethodElement? = null
override fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String> = emptyList()
override fun processClassMetadata(d1: List<String>, d2: List<String>): List<String> {
val input = ByteArrayInputStream(BitEncoding.decodeBytes(d1.toTypedArray()))
val stringTableTypes = StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY)
val nameResolver = JvmNameResolver(stringTableTypes, d2.toTypedArray())
val message = ProtoBuf.Class.parseFrom(input, EXTENSION_REGISTRY)
val typeTable = TypeTable(message.typeTable)
for (constructor in message.constructorList) {
if (!IS_SECONDARY.get(constructor.flags)) {
val signature = getJvmConstructorSignature(constructor, nameResolver, typeTable) ?: break
primaryConstructor = MethodElement("<init>", signature.drop("<init>".length))
logger.log(level, "Class {} has primary constructor {}", className, signature)
break
}
}
return emptyList()
}
override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array<String>?) {
className = clsName
super.visit(version, access, clsName, signature, superName, interfaces)
}
override fun visitMethod(access: Int, methodName: String, descriptor: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
val method = MethodElement(methodName, descriptor, access)
val mv = super.visitMethod(access, methodName, descriptor, signature, exceptions) ?: return null
return if (method == primaryConstructor) SanitisingMethodAdapter(mv, method) else mv
}
private inner class SanitisingMethodAdapter(mv: MethodVisitor, private val method: MethodElement) : MethodVisitor(api, mv) {
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
if (unwantedAnnotations.contains(descriptor)) {
logger.info("Sanitising annotation {} from method {}.{}{}", descriptor, className, method.name, method.descriptor)
isModified = true
return null
}
return super.visitAnnotation(descriptor, visible)
}
}
}

View File

@ -1,46 +0,0 @@
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)))
}
}

View File

@ -1,92 +0,0 @@
@file:JvmName("Utils")
package net.corda.gradle.jarfilter
import org.gradle.api.GradleException
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import java.nio.file.attribute.FileTime
import java.util.*
import java.util.Calendar.FEBRUARY
import java.util.zip.ZipEntry
import java.util.zip.ZipEntry.DEFLATED
import java.util.zip.ZipEntry.STORED
import kotlin.math.max
import kotlin.text.RegexOption.*
internal val JAR_PATTERN = "(\\.jar)$".toRegex(IGNORE_CASE)
// Use the same constant file timestamp as Gradle.
private val CONSTANT_TIME: FileTime = FileTime.fromMillis(
GregorianCalendar(1980, FEBRUARY, 1).apply { timeZone = TimeZone.getTimeZone("UTC") }.timeInMillis
)
internal fun rethrowAsUncheckedException(e: Exception): Nothing
= throw (e as? RuntimeException) ?: GradleException(e.message ?: "", e)
/**
* Recreates a [ZipEntry] object. The entry's byte contents
* will be compressed automatically, and its CRC, size and
* compressed size fields populated.
*/
internal fun ZipEntry.asCompressed(): ZipEntry {
return ZipEntry(name).also { entry ->
entry.lastModifiedTime = lastModifiedTime
lastAccessTime?.also { at -> entry.lastAccessTime = at }
creationTime?.also { ct -> entry.creationTime = ct }
entry.comment = comment
entry.method = DEFLATED
entry.extra = extra
}
}
internal fun ZipEntry.copy(): ZipEntry {
return if (method == STORED) ZipEntry(this) else asCompressed()
}
internal fun ZipEntry.withFileTimestamps(preserveTimestamps: Boolean): ZipEntry {
if (!preserveTimestamps) {
lastModifiedTime = CONSTANT_TIME
lastAccessTime?.apply { lastAccessTime = CONSTANT_TIME }
creationTime?.apply { creationTime = CONSTANT_TIME }
}
return this
}
internal fun <T : Any> mutableList(c: Collection<T>): MutableList<T> = ArrayList(c)
/**
* Converts Java class names to Java descriptors.
*/
internal fun toDescriptors(classNames: Iterable<String>): Set<String> {
return classNames.map(String::descriptor).toSet()
}
internal val String.toPathFormat: String get() = replace('.', '/')
internal val String.descriptor: String get() = "L$toPathFormat;"
/**
* Performs the given number of passes of the repeatable visitor over the byte-code.
* Used by [MetaFixerVisitor], but also by some of the test visitors.
*/
internal fun <T> ByteArray.execute(visitor: (ClassVisitor) -> T, flags: Int = 0, passes: Int = 2): ByteArray
where T : ClassVisitor,
T : Repeatable<T> {
var bytecode = this
var writer = ClassWriter(flags)
var transformer = visitor(writer)
var count = max(passes, 1)
while (--count >= 0) {
ClassReader(bytecode).accept(transformer, 0)
bytecode = writer.toByteArray()
if (!transformer.hasUnwantedElements) break
writer = ClassWriter(flags)
transformer = transformer.recreate(writer)
}
return bytecode
}

View File

@ -1,69 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.*
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.hamcrest.core.IsNot.not
import org.junit.Assert.*
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import java.lang.reflect.Modifier.*
import kotlin.reflect.full.declaredFunctions
import kotlin.test.assertFailsWith
class AbstractFunctionTest {
companion object {
private const val FUNCTION_CLASS = "net.corda.gradle.AbstractFunctions"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "abstract-function")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteAbstractFunction() {
val longFunction = isFunction("toDelete", Long::class, Long::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("toDelete", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
assertThat("toDelete(J) not found", kotlin.declaredFunctions, hasItem(longFunction))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod("toDelete", Long::class.java) }
assertThat("toDelete(J) still exists", kotlin.declaredFunctions, not(hasItem(longFunction)))
}
}
}
@Test
fun cannotStubAbstractFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("toStubOut", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("toStubOut", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
}
}
}
}

View File

@ -1,188 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.*
import net.corda.gradle.unwanted.*
import org.assertj.core.api.Assertions.*
import org.hamcrest.core.IsCollectionContaining.*
import org.hamcrest.core.IsNot.*
import org.junit.Assert.*
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.reflect.full.declaredMemberFunctions
import kotlin.reflect.full.declaredMemberProperties
import kotlin.test.assertFailsWith
class DeleteAndStubTests {
companion object {
private const val VAR_PROPERTY_CLASS = "net.corda.gradle.HasVarPropertyForDeleteAndStub"
private const val VAL_PROPERTY_CLASS = "net.corda.gradle.HasValPropertyForDeleteAndStub"
private const val DELETED_FUN_CLASS = "net.corda.gradle.DeletedFunctionInsideStubbed"
private const val DELETED_VAR_CLASS = "net.corda.gradle.DeletedVarInsideStubbed"
private const val DELETED_VAL_CLASS = "net.corda.gradle.DeletedValInsideStubbed"
private const val DELETED_PKG_CLASS = "net.corda.gradle.DeletePackageWithStubbed"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-and-stub")
private val stringVal = isProperty("stringVal", String::class)
private val longVar = isProperty("longVar", Long::class)
private val getStringVal = isMethod("getStringVal", String::class.java)
private val getLongVar = isMethod("getLongVar", Long::class.java)
private val setLongVar = isMethod("setLongVar", Void.TYPE, Long::class.java)
private val stringData = isFunction("stringData", String::class)
private val unwantedFun = isFunction("unwantedFun", String::class, String::class)
private val unwantedVar = isProperty("unwantedVar", String::class)
private val unwantedVal = isProperty("unwantedVal", String::class)
private val stringDataJava = isMethod("stringData", String::class.java)
private val getUnwantedVal = isMethod("getUnwantedVal", String::class.java)
private val getUnwantedVar = isMethod("getUnwantedVar", String::class.java)
private val setUnwantedVar = isMethod("setUnwantedVar", Void.TYPE, String::class.java)
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteValProperty() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasStringVal>(VAL_PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.stringVal)
}
assertThat("stringVal not found", kotlin.declaredMemberProperties, hasItem(stringVal))
assertThat("getStringVal() not found", kotlin.javaDeclaredMethods, hasItem(getStringVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasStringVal>(VAL_PROPERTY_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
assertThat("stringVal still exists", kotlin.declaredMemberProperties, not(hasItem(stringVal)))
assertThat("getStringVal() still exists", kotlin.javaDeclaredMethods, not(hasItem(getStringVal)))
}
}
}
@Test
fun deleteVarProperty() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasLongVar>(VAR_PROPERTY_CLASS).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { obj ->
assertEquals(BIG_NUMBER, obj.longVar)
}
assertThat("longVar not found", kotlin.declaredMemberProperties, hasItem(longVar))
assertThat("getLongVar() not found", kotlin.javaDeclaredMethods, hasItem(getLongVar))
assertThat("setLongVar() not found", kotlin.javaDeclaredMethods, hasItem(setLongVar))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLongVar>(VAR_PROPERTY_CLASS).apply {
assertNotNull(getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER))
assertThat("longVar still exists", kotlin.declaredMemberProperties, not(hasItem(longVar)))
assertThat("getLongVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(getLongVar)))
assertThat("setLongVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(setLongVar)))
}
}
}
@Test
fun deletedFunctionInsideStubbed() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(DELETED_FUN_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.stringData())
assertEquals(MESSAGE, (obj as HasUnwantedFun).unwantedFun(MESSAGE))
}
assertThat("unwantedFun not found", kotlin.declaredMemberFunctions, hasItem(unwantedFun))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(DELETED_FUN_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertFailsWith<UnsupportedOperationException> { obj.stringData() }.also { ex ->
assertThat(ex).hasMessage("Method has been deleted")
}
assertFailsWith<AbstractMethodError> { (obj as HasUnwantedFun).unwantedFun(MESSAGE) }
}
assertThat("unwantedFun still exists", kotlin.declaredMemberFunctions, not(hasItem(unwantedFun)))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
}
}
}
@Test
fun deletedVarInsideStubbed() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(DELETED_VAR_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.stringData())
(obj as HasUnwantedVar).also {
assertEquals(DEFAULT_MESSAGE, it.unwantedVar)
it.unwantedVar = MESSAGE
assertEquals(MESSAGE, it.unwantedVar)
}
}
assertThat("unwantedVar not found", kotlin.declaredMemberProperties, hasItem(unwantedVar))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(DELETED_VAR_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
assertThat("unwantedVar still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
assertThat("getUnwantedVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVar)))
assertThat("setUnwantedVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(setUnwantedVar)))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
assertThat("stringData() not found", kotlin.javaDeclaredMethods, hasItem(stringDataJava))
}
}
}
@Test
fun deletedValInsideStubbed() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(DELETED_VAL_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.stringData())
assertEquals(MESSAGE, (obj as HasUnwantedVal).unwantedVal)
}
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(DELETED_VAL_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
assertThat("getUnwantedVal() still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
assertThat("stringData() not found", kotlin.javaDeclaredMethods, hasItem(stringDataJava))
}
}
}
@Test
fun deletePackageWithStubbed() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(DELETED_PKG_CLASS).apply {
getDeclaredMethod("stubbed", String::class.java).also { method ->
assertEquals("[$MESSAGE]", method.invoke(null, MESSAGE))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
assertFailsWith<ClassNotFoundException> { cl.load<Any>(DELETED_PKG_CLASS) }
}
}
}

View File

@ -1,165 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.*
import net.corda.gradle.unwanted.HasAll
import net.corda.gradle.unwanted.HasInt
import net.corda.gradle.unwanted.HasLong
import net.corda.gradle.unwanted.HasString
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.hamcrest.core.IsNot.not
import org.junit.Assert.*
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.jvm.kotlin
import kotlin.reflect.full.primaryConstructor
import kotlin.test.assertFailsWith
class DeleteConstructorTest {
companion object {
private const val STRING_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryStringConstructorToDelete"
private const val LONG_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryLongConstructorToDelete"
private const val INT_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryIntConstructorToDelete"
private const val SECONDARY_CONSTRUCTOR_CLASS = "net.corda.gradle.HasConstructorToDelete"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-constructor")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteConstructorWithLongParameter() {
val longConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, Long::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
assertEquals(BIG_NUMBER, it.longData())
}
assertThat("<init>(J) not found", kotlin.constructors, hasItem(longConstructor))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(Long::class.java) }
assertThat("<init>(J) still exists", kotlin.constructors, not(hasItem(longConstructor)))
assertNotNull("primary constructor missing", kotlin.primaryConstructor)
}
}
}
@Test
fun deleteConstructorWithStringParameter() {
val stringConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, String::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertEquals(MESSAGE, it.stringData())
}
assertThat("<init>(String) not found", kotlin.constructors, hasItem(stringConstructor))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(String::class.java) }
assertThat("<init>(String) still exists", kotlin.constructors, not(hasItem(stringConstructor)))
assertNotNull("primary constructor missing", kotlin.primaryConstructor)
}
}
}
@Test
fun showUnannotatedConstructorIsUnaffected() {
val intConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, Int::class)
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasAll>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
assertEquals(NUMBER, it.intData())
assertEquals(NUMBER.toLong(), it.longData())
assertEquals("<nothing>", it.stringData())
}
assertThat("<init>(Int) not found", kotlin.constructors, hasItem(intConstructor))
assertNotNull("primary constructor missing", kotlin.primaryConstructor)
}
}
}
@Test
fun deletePrimaryConstructorWithStringParameter() {
val stringConstructor = isConstructor(STRING_PRIMARY_CONSTRUCTOR_CLASS, String::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertEquals(MESSAGE, it.stringData())
}
assertThat("<init>(String) not found", kotlin.constructors, hasItem(stringConstructor))
assertThat("primary constructor missing", kotlin.primaryConstructor!!, stringConstructor)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(String::class.java) }
assertThat("<init>(String) still exists", kotlin.constructors, not(hasItem(stringConstructor)))
assertNull("primary constructor still exists", kotlin.primaryConstructor)
}
}
}
@Test
fun deletePrimaryConstructorWithLongParameter() {
val longConstructor = isConstructor(LONG_PRIMARY_CONSTRUCTOR_CLASS, Long::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
assertEquals(BIG_NUMBER, it.longData())
}
assertThat("<init>(J) not found", kotlin.constructors, hasItem(longConstructor))
assertThat("primary constructor missing", kotlin.primaryConstructor!!, longConstructor)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(Long::class.java) }
assertThat("<init>(J) still exists", kotlin.constructors, not(hasItem(longConstructor)))
assertNull("primary constructor still exists", kotlin.primaryConstructor)
}
}
}
@Test
fun deletePrimaryConstructorWithIntParameter() {
val intConstructor = isConstructor(INT_PRIMARY_CONSTRUCTOR_CLASS, Int::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
assertEquals(NUMBER, it.intData())
}
assertThat("<init>(I) not found", kotlin.constructors, hasItem(intConstructor))
assertThat("primary constructor missing", kotlin.primaryConstructor!!, intConstructor)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(Int::class.java) }
assertThat("<init>(I) still exists", kotlin.constructors, not(hasItem(intConstructor)))
assertNull("primary constructor still exists", kotlin.primaryConstructor)
}
}
}
}

View File

@ -1,52 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.isMethod
import net.corda.gradle.jarfilter.matcher.isProperty
import net.corda.gradle.jarfilter.matcher.javaDeclaredMethods
import net.corda.gradle.unwanted.HasUnwantedVal
import org.hamcrest.core.IsCollectionContaining.*
import org.hamcrest.core.IsNot.*
import org.junit.Assert.*
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.reflect.full.declaredMemberExtensionProperties
import kotlin.reflect.full.declaredMemberProperties
class DeleteExtensionValPropertyTest {
companion object {
private const val PROPERTY_CLASS = "net.corda.gradle.HasValExtension"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-extension-val")
private val unwantedVal = isProperty("unwantedVal", String::class)
private val getUnwantedVal = isMethod("getUnwantedVal", String::class.java, List::class.java)
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteExtensionProperty() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
assertThat("List.unwantedVal not found", kotlin.declaredMemberExtensionProperties, hasItem(unwantedVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
assertThat("List.unwantedVal still exists", kotlin.declaredMemberExtensionProperties, not(hasItem(unwantedVal)))
}
}
}
}

View File

@ -1,99 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.*
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.hamcrest.core.IsNot.not
import org.junit.Assert.*
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.reflect.full.declaredMemberProperties
import kotlin.test.assertFailsWith
class DeleteFieldTest {
companion object {
private const val STRING_FIELD_CLASS = "net.corda.gradle.HasStringFieldToDelete"
private const val INTEGER_FIELD_CLASS = "net.corda.gradle.HasIntFieldToDelete"
private const val LONG_FIELD_CLASS = "net.corda.gradle.HasLongFieldToDelete"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-field")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteStringField() {
val stringField = isProperty("stringField", String::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(STRING_FIELD_CLASS).apply {
val obj: Any = getDeclaredConstructor(String::class.java).newInstance(MESSAGE)
getDeclaredField("stringField").also { field ->
assertEquals(MESSAGE, field.get(obj))
}
assertThat("stringField not found", kotlin.declaredMemberProperties, hasItem(stringField))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(STRING_FIELD_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
assertFailsWith<NoSuchFieldException> { getDeclaredField("stringField") }
assertThat("stringField still exists", kotlin.declaredMemberProperties, not(hasItem(stringField)))
}
}
}
@Test
fun deleteLongField() {
val longField = isProperty("longField", Long::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(LONG_FIELD_CLASS).apply {
val obj: Any = getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
getDeclaredField("longField").also { field ->
assertEquals(BIG_NUMBER, field.get(obj))
}
assertThat("longField not found", kotlin.declaredMemberProperties, hasItem(longField))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(LONG_FIELD_CLASS).apply {
assertNotNull(getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER))
assertFailsWith<NoSuchFieldException> { getDeclaredField("longField") }
assertThat("longField still exists", kotlin.declaredMemberProperties, not(hasItem(longField)))
}
}
}
@Test
fun deleteIntegerField() {
val intField = isProperty("intField", Int::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(INTEGER_FIELD_CLASS).apply {
val obj: Any = getDeclaredConstructor(Int::class.java).newInstance(NUMBER)
getDeclaredField("intField").also { field ->
assertEquals(NUMBER, field.get(obj))
}
assertThat("intField not found", kotlin.declaredMemberProperties, hasItem(intField))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(INTEGER_FIELD_CLASS).apply {
assertNotNull(getDeclaredConstructor(Int::class.java).newInstance(NUMBER))
assertFailsWith<NoSuchFieldException> { getDeclaredField("intField") }
assertThat("intField still exists", kotlin.declaredMemberProperties, not(hasItem(intField)))
}
}
}
}

View File

@ -1,81 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.*
import net.corda.gradle.unwanted.HasString
import net.corda.gradle.unwanted.HasUnwantedFun
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.hamcrest.core.IsNot.not
import org.junit.Assert.*
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.jvm.kotlin
import kotlin.reflect.full.declaredFunctions
import kotlin.test.assertFailsWith
class DeleteFunctionTest {
companion object {
private const val FUNCTION_CLASS = "net.corda.gradle.HasFunctionToDelete"
private const val INDIRECT_CLASS = "net.corda.gradle.HasIndirectFunctionToDelete"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-function")
private val unwantedFun = isFunction("unwantedFun", String::class, String::class)
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
newInstance().also {
assertEquals(MESSAGE, it.unwantedFun(MESSAGE))
}
assertThat("unwantedFun(String) not found", kotlin.declaredFunctions, hasItem(unwantedFun))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
newInstance().also {
assertFailsWith<AbstractMethodError> { it.unwantedFun(MESSAGE) }
}
assertThat("unwantedFun(String) still exists", kotlin.declaredFunctions, not(hasItem(unwantedFun)))
}
}
}
@Test
fun deleteIndirectFunction() {
val stringData = isFunction("stringData", String::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedFun>(INDIRECT_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertEquals(MESSAGE, it.unwantedFun(MESSAGE))
assertEquals(MESSAGE, (it as HasString).stringData())
}
assertThat("unwantedFun(String) not found", kotlin.declaredFunctions, hasItem(unwantedFun))
assertThat("stringData() not found", kotlin.declaredFunctions, hasItem(stringData))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedFun>(INDIRECT_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertFailsWith<AbstractMethodError> { it.unwantedFun(MESSAGE) }
assertFailsWith<AbstractMethodError> { (it as HasString).stringData() }
}
assertThat("unwantedFun(String) still exists", kotlin.declaredFunctions, not(hasItem(unwantedFun)))
assertThat("stringData still exists", kotlin.declaredFunctions, not(hasItem(stringData)))
}
}
}
}

View File

@ -1,84 +0,0 @@
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())
}
}
}
}
}

View File

@ -1,71 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.*
import net.corda.gradle.unwanted.HasUnwantedVal
import org.assertj.core.api.Assertions.*
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.reflect.full.declaredMemberProperties
import kotlin.test.assertFailsWith
class DeleteLazyTest {
companion object {
private const val LAZY_VAL_CLASS = "net.corda.gradle.HasLazyVal"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-lazy")
private val unwantedVal = isProperty("unwantedVal", String::class)
private val getUnwantedVal = isMethod("getUnwantedVal", String::class.java)
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(LAZY_VAL_CLASS)
filteredClasses = testProject.filteredJar.getClassNames(LAZY_VAL_CLASS)
}
}
@Test
fun deletedClasses() {
assertThat(sourceClasses).contains(LAZY_VAL_CLASS)
assertThat(filteredClasses).containsExactly(LAZY_VAL_CLASS)
}
@Test
fun deleteLazyVal() {
assertThat(sourceClasses).anyMatch { it.contains("\$unwantedVal\$") }
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVal>(LAZY_VAL_CLASS).apply {
getConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVal)
}
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVal>(LAZY_VAL_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getConstructor(String::class.java) }
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
}
}
}
}

View File

@ -1,90 +0,0 @@
package net.corda.gradle.jarfilter
import org.assertj.core.api.Assertions.*
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 DeleteMultiFileTest {
companion object {
private const val MULTIFILE_CLASS = "net.corda.gradle.HasMultiData"
private const val STRING_METHOD = "stringToDelete"
private const val LONG_METHOD = "longToDelete"
private const val INT_METHOD = "intToDelete"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-multifile")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteStringFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
getMethod(STRING_METHOD, String::class.java).also { method ->
method.invoke(null, MESSAGE).also { result ->
assertThat(result)
.isInstanceOf(String::class.java)
.isEqualTo(MESSAGE)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod(STRING_METHOD, String::class.java) }
}
}
}
@Test
fun deleteLongFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
getMethod(LONG_METHOD, Long::class.java).also { method ->
method.invoke(null, BIG_NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Long::class.javaObjectType)
.isEqualTo(BIG_NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod(LONG_METHOD, Long::class.java) }
}
}
}
@Test
fun deleteIntFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
getMethod(INT_METHOD, Int::class.java).also { method ->
method.invoke(null, NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Int::class.javaObjectType)
.isEqualTo(NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod(INT_METHOD, Int::class.java) }
}
}
}
}

View File

@ -1,90 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.classMetadata
import net.corda.gradle.jarfilter.matcher.isClass
import org.assertj.core.api.Assertions.*
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.hamcrest.core.IsNot.not
import org.junit.Assert.*
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 DeleteNestedClassTest {
companion object {
private const val HOST_CLASS = "net.corda.gradle.HasNestedClasses"
private const val KEPT_CLASS = "$HOST_CLASS\$OneToKeep"
private const val DELETED_CLASS = "$HOST_CLASS\$OneToThrowAway"
private const val SEALED_CLASS = "net.corda.gradle.SealedClass"
private const val WANTED_SUBCLASS = "$SEALED_CLASS\$Wanted"
private const val UNWANTED_SUBCLASS = "$SEALED_CLASS\$Unwanted"
private val keptClass = isClass(KEPT_CLASS)
private val deletedClass = isClass(DELETED_CLASS)
private val wantedSubclass = isClass(WANTED_SUBCLASS)
private val unwantedSubclass = isClass(UNWANTED_SUBCLASS)
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-nested-class")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteNestedClass() {
classLoaderFor(testProject.sourceJar).use { cl ->
val deleted = cl.load<Any>(DELETED_CLASS)
val kept = cl.load<Any>(KEPT_CLASS)
cl.load<Any>(HOST_CLASS).apply {
assertThat(declaredClasses).containsExactlyInAnyOrder(deleted, kept)
assertThat("OneToThrowAway class is missing", kotlin.nestedClasses, hasItem(deletedClass))
assertThat("OneToKeep class is missing", kotlin.nestedClasses, hasItem(keptClass))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
assertFailsWith<ClassNotFoundException> { cl.load<Any>(DELETED_CLASS) }
val kept = cl.load<Any>(KEPT_CLASS)
cl.load<Any>(HOST_CLASS).apply {
assertThat(declaredClasses).containsExactly(kept)
assertThat("OneToThrowAway class still exists", kotlin.nestedClasses, not(hasItem(deletedClass)))
assertThat("OneToKeep class is missing", kotlin.nestedClasses, hasItem(keptClass))
}
}
}
@Test
fun deleteFromSealedClass() {
classLoaderFor(testProject.sourceJar).use { cl ->
val unwanted = cl.load<Any>(UNWANTED_SUBCLASS)
val wanted = cl.load<Any>(WANTED_SUBCLASS)
cl.load<Any>(SEALED_CLASS).apply {
assertTrue(kotlin.isSealed)
assertThat(declaredClasses).containsExactlyInAnyOrder(wanted, unwanted)
assertThat("Wanted class is missing", kotlin.nestedClasses, hasItem(wantedSubclass))
assertThat("Unwanted class is missing", kotlin.nestedClasses, hasItem(unwantedSubclass))
assertThat(classMetadata.sealedSubclasses).containsExactlyInAnyOrder(WANTED_SUBCLASS, UNWANTED_SUBCLASS)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
assertFailsWith<ClassNotFoundException> { cl.load<Any>(UNWANTED_SUBCLASS) }
val wanted = cl.load<Any>(WANTED_SUBCLASS)
cl.load<Any>(SEALED_CLASS).apply {
assertTrue(kotlin.isSealed)
assertThat(declaredClasses).containsExactly(wanted)
assertThat("Unwanted class still exists", kotlin.nestedClasses, not(hasItem(unwantedSubclass)))
assertThat("Wanted class is missing", kotlin.nestedClasses, hasItem(wantedSubclass))
assertThat(classMetadata.sealedSubclasses).containsExactly(WANTED_SUBCLASS)
}
}
}
}

View File

@ -1,89 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.unwanted.HasUnwantedFun
import org.assertj.core.api.Assertions.*
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 DeleteObjectTest {
companion object {
private const val OBJECT_CLASS = "net.corda.gradle.HasObjects"
private const val UNWANTED_OBJ_METHOD = "getUnwantedObj"
private const val UNWANTED_OBJ_FIELD = "unwantedObj"
private const val UNWANTED_FUN_METHOD = "unwantedFun"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-object")
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(OBJECT_CLASS)
filteredClasses = testProject.filteredJar.getClassNames(OBJECT_CLASS)
}
}
@Test
fun deletedClasses() {
assertThat(sourceClasses).contains(OBJECT_CLASS)
assertThat(filteredClasses).containsExactly(OBJECT_CLASS)
}
@Test
fun deleteObject() {
assertThat(sourceClasses).anyMatch { it.contains("\$unwantedObj\$") }
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(OBJECT_CLASS).apply {
getDeclaredMethod(UNWANTED_OBJ_METHOD).also { method ->
(method.invoke(null) as HasUnwantedFun).also { obj ->
assertEquals(MESSAGE, obj.unwantedFun(MESSAGE))
}
}
getDeclaredField(UNWANTED_OBJ_FIELD).also { field ->
assertFalse(field.isAccessible)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(OBJECT_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod(UNWANTED_OBJ_METHOD) }
assertFailsWith<NoSuchFieldException> { getDeclaredField(UNWANTED_OBJ_FIELD) }
}
}
}
@Test
fun deleteFunctionWithObject() {
assertThat(sourceClasses).anyMatch { it.contains("\$unwantedFun\$") }
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(OBJECT_CLASS).apply {
getDeclaredMethod(UNWANTED_FUN_METHOD).also { method ->
assertEquals("<default-value>", method.invoke(null))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(OBJECT_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod(UNWANTED_FUN_METHOD) }
}
}
}
}

View File

@ -1,56 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.classMetadata
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.*
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
/**
* Sealed classes can have non-nested subclasses, so long as those subclasses
* are declared in the same file as the sealed class. Check that the metadata
* is still updated correctly in this case.
*/
class DeleteSealedSubclassTest {
companion object {
private const val SEALED_CLASS = "net.corda.gradle.SealedBaseClass"
private const val WANTED_SUBCLASS = "net.corda.gradle.WantedSubclass"
private const val UNWANTED_SUBCLASS = "net.corda.gradle.UnwantedSubclass"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-sealed-subclass")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteUnwantedSubclass() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(WANTED_SUBCLASS)
cl.load<Any>(UNWANTED_SUBCLASS)
cl.load<Any>(SEALED_CLASS).apply {
assertTrue(kotlin.isSealed)
assertThat(classMetadata.sealedSubclasses)
.containsExactlyInAnyOrder(WANTED_SUBCLASS, UNWANTED_SUBCLASS)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(WANTED_SUBCLASS)
assertFailsWith<ClassNotFoundException> { cl.load<Any>(UNWANTED_SUBCLASS) }
cl.load<Any>(SEALED_CLASS).apply {
assertTrue(kotlin.isSealed)
assertThat(classMetadata.sealedSubclasses)
.containsExactly(WANTED_SUBCLASS)
}
}
}
}

View File

@ -1,74 +0,0 @@
package net.corda.gradle.jarfilter
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.assertEquals
import kotlin.test.assertFailsWith
class DeleteStaticFieldTest {
companion object {
private const val PROPERTY_CLASS = "net.corda.gradle.StaticFieldsToDelete"
private const val DEFAULT_BIG_NUMBER: Long = 123456789L
private const val DEFAULT_NUMBER: Int = 123456
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-static-field")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteStringField() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredField("stringField")
assertEquals(DEFAULT_MESSAGE, getter.get(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchFieldException> { getDeclaredField("stringField") }
}
}
}
@Test
fun deleteLongField() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredField("longField")
assertEquals(DEFAULT_BIG_NUMBER, getter.get(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchFieldException> { getDeclaredField("longField") }
}
}
}
@Test
fun deleteIntField() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredField("intField")
assertEquals(DEFAULT_NUMBER, getter.get(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchFieldException> { getDeclaredField("intField") }
}
}
}
}

View File

@ -1,87 +0,0 @@
package net.corda.gradle.jarfilter
import org.assertj.core.api.Assertions.*
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 DeleteStaticFunctionTest {
companion object {
private const val FUNCTION_CLASS = "net.corda.gradle.StaticFunctionsToDelete"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-static-function")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteStringFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("unwantedStringToDelete", String::class.java).also { method ->
method.invoke(null, MESSAGE).also { result ->
assertThat(result)
.isInstanceOf(String::class.java)
.isEqualTo(MESSAGE)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod("unwantedStringToDelete", String::class.java) }
}
}
}
@Test
fun deleteLongFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("unwantedLongToDelete", Long::class.java).also { method ->
method.invoke(null, BIG_NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Long::class.javaObjectType)
.isEqualTo(BIG_NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod("unwantedLongToDelete", Long::class.java) }
}
}
}
@Test
fun deleteIntFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("unwantedIntToDelete", Int::class.java).also { method ->
method.invoke(null, NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Int::class.javaObjectType)
.isEqualTo(NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod("unwantedIntToDelete", Int::class.java) }
}
}
}
}

View File

@ -1,91 +0,0 @@
package net.corda.gradle.jarfilter
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.assertEquals
import kotlin.test.assertFailsWith
class DeleteStaticValPropertyTest {
companion object {
private const val PROPERTY_CLASS = "net.corda.gradle.StaticValToDelete"
private const val DEFAULT_BIG_NUMBER: Long = 123456789L
private const val DEFAULT_NUMBER: Int = 123456
private object LocalBlob
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-static-val")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteStringVal() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getStringVal")
assertEquals(DEFAULT_MESSAGE, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getStringVal") }
}
}
}
@Test
fun deleteLongVal() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getLongVal")
assertEquals(DEFAULT_BIG_NUMBER, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getLongVal") }
}
}
}
@Test
fun deleteIntVal() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getIntVal")
assertEquals(DEFAULT_NUMBER, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getIntVal") }
}
}
}
@Test
fun deleteMemberVal() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getMemberVal", Any::class.java)
assertEquals(LocalBlob, getter.invoke(null, LocalBlob))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getMemberVal", Any::class.java) }
}
}
}
}

View File

@ -1,106 +0,0 @@
package net.corda.gradle.jarfilter
import org.junit.Assert.*
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 DeleteStaticVarPropertyTest {
companion object {
private const val PROPERTY_CLASS = "net.corda.gradle.StaticVarToDelete"
private const val DEFAULT_BIG_NUMBER: Long = 123456789L
private const val DEFAULT_NUMBER: Int = 123456
private object LocalBlob
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-static-var")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteStringVar() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getStringVar")
val setter = getDeclaredMethod("setStringVar", String::class.java)
assertEquals(DEFAULT_MESSAGE, getter.invoke(null))
setter.invoke(null, MESSAGE)
assertEquals(MESSAGE, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getStringVar") }
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setStringVar", String::class.java) }
}
}
}
@Test
fun deleteLongVar() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getLongVar")
val setter = getDeclaredMethod("setLongVar", Long::class.java)
assertEquals(DEFAULT_BIG_NUMBER, getter.invoke(null))
setter.invoke(null, BIG_NUMBER)
assertEquals(BIG_NUMBER, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getLongVar") }
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setLongVar", Long::class.java) }
}
}
}
@Test
fun deleteIntVar() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getIntVar")
val setter = getDeclaredMethod("setIntVar", Int::class.java)
assertEquals(DEFAULT_NUMBER, getter.invoke(null))
setter.invoke(null, NUMBER)
assertEquals(NUMBER, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getIntVar") }
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setIntVar", Int::class.java) }
}
}
}
@Test
fun deleteMemberVar() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getMemberVar", Any::class.java)
val setter = getDeclaredMethod("setMemberVar", Any::class.java, Any::class.java)
assertEquals(LocalBlob, getter.invoke(null, LocalBlob))
setter.invoke(null, LocalBlob, LocalBlob)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getMemberVar", Any::class.java) }
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setMemberVar", Any::class.java, Any::class.java) }
}
}
}
}

View File

@ -1,48 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.fileMetadata
import org.assertj.core.api.Assertions.assertThat
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
class DeleteTypeAliasFromFileTest {
companion object {
private const val TYPEALIAS_CLASS = "net.corda.gradle.FileWithTypeAlias"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-file-typealias")
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(TYPEALIAS_CLASS)
filteredClasses = testProject.filteredJar.getClassNames(TYPEALIAS_CLASS)
}
}
@Test
fun deleteTypeAlias() {
classLoaderFor(testProject.sourceJar).use { cl ->
val metadata = cl.load<Any>(TYPEALIAS_CLASS).fileMetadata
assertThat(metadata.typeAliasNames)
.containsExactlyInAnyOrder("FileWantedType", "FileUnwantedType")
}
classLoaderFor(testProject.filteredJar).use { cl ->
val metadata = cl.load<Any>(TYPEALIAS_CLASS).fileMetadata
assertThat(metadata.typeAliasNames)
.containsExactly("FileWantedType")
}
}
}

View File

@ -1,102 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.*
import net.corda.gradle.unwanted.HasUnwantedVal
import org.hamcrest.core.IsCollectionContaining.*
import org.hamcrest.core.IsNot.*
import org.junit.Assert.*
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.reflect.full.declaredMemberProperties
import kotlin.test.assertFailsWith
class DeleteValPropertyTest {
companion object {
private const val PROPERTY_CLASS = "net.corda.gradle.HasValPropertyForDelete"
private const val GETTER_CLASS = "net.corda.gradle.HasValGetterForDelete"
private const val JVM_FIELD_CLASS = "net.corda.gradle.HasValJvmFieldForDelete"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-val-property")
private val unwantedVal = isProperty("unwantedVal", String::class)
private val getUnwantedVal = isMethod("getUnwantedVal", String::class.java)
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteProperty() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVal)
}
assertFalse(getDeclaredField("unwantedVal").isAccessible)
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertFailsWith<AbstractMethodError> { obj.unwantedVal }
}
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVal") }
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
}
}
}
@Test
fun deleteGetter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVal>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVal)
}
assertFalse(getDeclaredField("unwantedVal").isAccessible)
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVal>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertFailsWith<AbstractMethodError> { obj.unwantedVal }
}
assertFalse(getDeclaredField("unwantedVal").isAccessible)
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
}
}
}
@Test
fun deleteJvmField() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(JVM_FIELD_CLASS).apply {
val obj = getDeclaredConstructor(String::class.java).newInstance(MESSAGE)
getDeclaredField("unwantedVal").also { field ->
assertEquals(MESSAGE, field.get(obj))
}
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(JVM_FIELD_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVal") }
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
}
}
}
}

View File

@ -1,141 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.*
import net.corda.gradle.unwanted.HasUnwantedVar
import org.hamcrest.core.IsCollectionContaining.*
import org.hamcrest.core.IsNot.*
import org.junit.Assert.*
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.reflect.full.declaredMemberProperties
import kotlin.test.assertFailsWith
class DeleteVarPropertyTest {
companion object {
private const val PROPERTY_CLASS = "net.corda.gradle.HasUnwantedVarPropertyForDelete"
private const val GETTER_CLASS = "net.corda.gradle.HasUnwantedGetForDelete"
private const val SETTER_CLASS = "net.corda.gradle.HasUnwantedSetForDelete"
private const val JVM_FIELD_CLASS = "net.corda.gradle.HasVarJvmFieldForDelete"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "delete-var-property")
private val unwantedVar = isProperty("unwantedVar", String::class)
private val getUnwantedVar = isMethod("getUnwantedVar", String::class.java)
private val setUnwantedVar = isMethod("setUnwantedVar", Void.TYPE, String::class.java)
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteProperty() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVar>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
obj.unwantedVar = MESSAGE
assertEquals(MESSAGE, obj.unwantedVar)
}
assertFalse(getDeclaredField("unwantedVar").isAccessible)
assertThat("unwantedVar not found", kotlin.declaredMemberProperties, hasItem(unwantedVar))
assertThat("getUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVar))
assertThat("setUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(setUnwantedVar))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVar>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertFailsWith<AbstractMethodError> { obj.unwantedVar }
assertFailsWith<AbstractMethodError> { obj.unwantedVar = MESSAGE }
}
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVar") }
assertThat("unwantedVar still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
assertThat("getUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVar)))
assertThat("setUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(setUnwantedVar)))
}
}
}
@Test
fun deleteGetter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVar)
}
assertFalse(getDeclaredField("unwantedVar").isAccessible)
assertThat("getUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVar))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertFailsWith<AbstractMethodError> { obj.unwantedVar }
}
assertFalse(getDeclaredField("unwantedVar").isAccessible)
assertThat("getUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVar)))
}
}
}
@Test
fun deleteSetter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
obj.unwantedVar = MESSAGE
assertEquals(MESSAGE, obj.unwantedVar)
}
getDeclaredField("unwantedVar").also { field ->
assertFalse(field.isAccessible)
}
assertThat("setUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(setUnwantedVar))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
assertFailsWith<AbstractMethodError> { obj.unwantedVar = MESSAGE }
}
getDeclaredField("unwantedVar").also { field ->
assertFalse(field.isAccessible)
}
assertThat("setUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(setUnwantedVar)))
}
}
}
@Test
fun deleteJvmField() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(JVM_FIELD_CLASS).apply {
val obj: Any = getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE)
getDeclaredField("unwantedVar").also { field ->
assertEquals(DEFAULT_MESSAGE, field.get(obj))
field.set(obj, MESSAGE)
assertEquals(MESSAGE, field.get(obj))
}
assertThat("unwantedVar not found", kotlin.declaredMemberProperties, hasItem(unwantedVar))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(JVM_FIELD_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE))
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVar") }
assertThat("unwantedVar still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
}
}
}
}

View File

@ -1,103 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.bytecode
import net.corda.gradle.jarfilter.asm.resourceName
import org.assertj.core.api.Assertions.*
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.nio.file.Files
import java.nio.file.Path
import java.util.jar.Attributes.Name.MANIFEST_VERSION
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import java.util.zip.CRC32
import java.util.zip.Deflater.NO_COMPRESSION
import java.util.zip.ZipEntry
import java.util.zip.ZipEntry.*
/**
* Creates a dummy jar containing the following:
* - META-INF/MANIFEST.MF
* - A compressed class file
* - A compressed binary non-class file
* - An uncompressed text file
* - A directory entry
*
* The compression level is set to NO_COMPRESSION
* in order to force the Gradle task to compress
* the entries properly.
*/
class DummyJar(
private val projectDir: TemporaryFolder,
private val testClass: Class<*>,
private val name: String
) : TestRule {
private companion object {
private const val DATA_SIZE = 512
private fun uncompressed(name: String, data: ByteArray) = ZipEntry(name).apply {
method = STORED
compressedSize = data.size.toLong()
size = data.size.toLong()
crc = CRC32().let { crc ->
crc.update(data)
crc.value
}
}
private fun compressed(name: String) = ZipEntry(name).apply { method = DEFLATED }
private fun directoryOf(type: Class<*>)
= directory(type.`package`.name.toPathFormat + '/')
private fun directory(name: String) = ZipEntry(name).apply {
method = STORED
compressedSize = 0
size = 0
crc = 0
}
}
private lateinit var _path: Path
val path: Path get() = _path
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
val manifest = Manifest().apply {
mainAttributes.also { main ->
main[MANIFEST_VERSION] = "1.0"
}
}
_path = projectDir.pathOf("$name.jar")
JarOutputStream(Files.newOutputStream(_path), manifest).use { jar ->
jar.setComment(testClass.name)
jar.setLevel(NO_COMPRESSION)
// One directory entry (stored)
jar.putNextEntry(directoryOf(testClass))
// One compressed class file
jar.putNextEntry(compressed(testClass.resourceName))
jar.write(testClass.bytecode)
// One compressed non-class file
jar.putNextEntry(compressed("binary.dat"))
jar.write(arrayOfJunk(DATA_SIZE))
// One uncompressed text file
val text = """Jar: ${_path.toAbsolutePath()}
Class: ${testClass.name}
""".toByteArray()
jar.putNextEntry(uncompressed("comment.txt", text))
jar.write(text)
}
assertThat(_path).isRegularFile()
base.evaluate()
}
}
}
}

View File

@ -1,8 +0,0 @@
@file:JvmName("EmptyPackage")
@file:Suppress("UNUSED")
package net.corda.gradle.jarfilter
/*
* We need to put something in here so that Kotlin will create a class file.
*/
const val PLACEHOLDER = 0

View File

@ -1,32 +0,0 @@
package net.corda.gradle.jarfilter
import org.junit.Assert.*
import org.junit.Test
class FieldElementTest {
private companion object {
private const val DESCRIPTOR = "Ljava.lang.String;"
}
@Test
fun testFieldsMatchByNameOnly() {
val elt = FieldElement(name = "fieldName", descriptor = DESCRIPTOR)
assertEquals(FieldElement(name = "fieldName"), elt)
}
@Test
fun testFieldWithDescriptorDoesNotExpire() {
val elt = FieldElement(name = "fieldName", descriptor = DESCRIPTOR)
assertFalse(elt.isExpired)
assertFalse(elt.isExpired)
assertFalse(elt.isExpired)
}
@Test
fun testFieldWithoutDescriptorDoesExpire() {
val elt = FieldElement(name = "fieldName")
assertFalse(elt.isExpired)
assertTrue(elt.isExpired)
assertTrue(elt.isExpired)
}
}

View File

@ -1,212 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.annotations.Deletable
import net.corda.gradle.jarfilter.asm.bytecode
import net.corda.gradle.jarfilter.asm.toClass
import net.corda.gradle.jarfilter.matcher.isProperty
import org.gradle.api.logging.Logger
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.hamcrest.core.IsEqual.equalTo
import org.hamcrest.core.IsNot.not
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertThat
import org.junit.Test
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.jvmName
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
/**
* Demonstrate that we can still instantiate objects, even after we've deleted
* one of their properties. (Check we haven't blown the constructor away!)
*/
class FieldRemovalTest {
companion object {
private val logger: Logger = StdOutLogging(FieldRemovalTest::class)
private const val SHORT_NUMBER = 999.toShort()
private const val BYTE_NUMBER = 99.toByte()
private const val BIG_FLOATING_POINT = 9999999.9999
private const val FLOATING_POINT = 9999.99f
private val objectField = isProperty(equalTo("objectField"), equalTo("T"))
private val longField = isProperty("longField", Long::class)
private val intField = isProperty("intField", Int::class)
private val shortField = isProperty("shortField", Short::class)
private val byteField = isProperty("byteField", Byte::class)
private val charField = isProperty("charField", Char::class)
private val booleanField = isProperty("booleanField", Boolean::class)
private val doubleField = isProperty("doubleField", Double::class)
private val floatField = isProperty("floatField", Float::class)
private val arrayField = isProperty("arrayField", ByteArray::class)
}
private inline fun <reified T: R, reified R: Any> transform(): Class<out R> = transform(T::class.java, R::class.java)
private fun <T: R, R: Any> transform(type: Class<in T>, asType: Class<out R>): Class<out R> {
val bytecode = type.bytecode.execute({ writer ->
FilterTransformer(
visitor = writer,
logger = logger,
removeAnnotations = emptySet(),
deleteAnnotations = setOf(Deletable::class.jvmName.descriptor),
stubAnnotations = emptySet(),
unwantedElements = UnwantedCache()
)
}, COMPUTE_MAXS)
return bytecode.toClass(type, asType)
}
@Test
fun removeObject() {
val sourceField = SampleGenericField(MESSAGE)
assertEquals(MESSAGE, sourceField.objectField)
assertThat("objectField not found", sourceField::class.declaredMemberProperties, hasItem(objectField))
val targetField = transform<SampleGenericField<String>, HasGenericField<String>>()
.getDeclaredConstructor(Any::class.java).newInstance(MESSAGE)
assertFailsWith<AbstractMethodError> { targetField.objectField }
assertFailsWith<AbstractMethodError> { targetField.objectField = "New Value" }
assertThat("objectField still exists", targetField::class.declaredMemberProperties, not(hasItem(objectField)))
}
@Test
fun removeLong() {
val sourceField = SampleLongField(BIG_NUMBER)
assertEquals(BIG_NUMBER, sourceField.longField)
assertThat("longField not found", sourceField::class.declaredMemberProperties, hasItem(longField))
val targetField = transform<SampleLongField, HasLongField>()
.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
assertFailsWith<AbstractMethodError> { targetField.longField }
assertFailsWith<AbstractMethodError> { targetField.longField = 10L }
assertThat("longField still exists", targetField::class.declaredMemberProperties, not(hasItem(longField)))
}
@Test
fun removeInt() {
val sourceField = SampleIntField(NUMBER)
assertEquals(NUMBER, sourceField.intField)
assertThat("intField not found", sourceField::class.declaredMemberProperties, hasItem(intField))
val targetField = transform<SampleIntField, HasIntField>()
.getDeclaredConstructor(Int::class.java).newInstance(NUMBER)
assertFailsWith<AbstractMethodError> { targetField.intField }
assertFailsWith<AbstractMethodError> { targetField.intField = 100 }
assertThat("intField still exists", targetField::class.declaredMemberProperties, not(hasItem(intField)))
}
@Test
fun removeShort() {
val sourceField = SampleShortField(SHORT_NUMBER)
assertEquals(SHORT_NUMBER, sourceField.shortField)
assertThat("shortField not found", sourceField::class.declaredMemberProperties, hasItem(shortField))
val targetField = transform<SampleShortField, HasShortField>()
.getDeclaredConstructor(Short::class.java).newInstance(SHORT_NUMBER)
assertFailsWith<AbstractMethodError> { targetField.shortField }
assertFailsWith<AbstractMethodError> { targetField.shortField = 15 }
assertThat("shortField still exists", targetField::class.declaredMemberProperties, not(hasItem(shortField)))
}
@Test
fun removeByte() {
val sourceField = SampleByteField(BYTE_NUMBER)
assertEquals(BYTE_NUMBER, sourceField.byteField)
assertThat("byteField not found", sourceField::class.declaredMemberProperties, hasItem(byteField))
val targetField = transform<SampleByteField, HasByteField>()
.getDeclaredConstructor(Byte::class.java).newInstance(BYTE_NUMBER)
assertFailsWith<AbstractMethodError> { targetField.byteField }
assertFailsWith<AbstractMethodError> { targetField.byteField = 16 }
assertThat("byteField still exists", targetField::class.declaredMemberProperties, not(hasItem(byteField)))
}
@Test
fun removeBoolean() {
val sourceField = SampleBooleanField(true)
assertTrue(sourceField.booleanField)
assertThat("booleanField not found", sourceField::class.declaredMemberProperties, hasItem(booleanField))
val targetField = transform<SampleBooleanField, HasBooleanField>()
.getDeclaredConstructor(Boolean::class.java).newInstance(true)
assertFailsWith<AbstractMethodError> { targetField.booleanField }
assertFailsWith<AbstractMethodError> { targetField.booleanField = false }
assertThat("booleanField still exists", targetField::class.declaredMemberProperties, not(hasItem(booleanField)))
}
@Test
fun removeChar() {
val sourceField = SampleCharField('?')
assertEquals('?', sourceField.charField)
assertThat("charField not found", sourceField::class.declaredMemberProperties, hasItem(charField))
val targetField = transform<SampleCharField, HasCharField>()
.getDeclaredConstructor(Char::class.java).newInstance('?')
assertFailsWith<AbstractMethodError> { targetField.charField }
assertFailsWith<AbstractMethodError> { targetField.charField = 'A' }
assertThat("charField still exists", targetField::class.declaredMemberProperties, not(hasItem(charField)))
}
@Test
fun removeDouble() {
val sourceField = SampleDoubleField(BIG_FLOATING_POINT)
assertEquals(BIG_FLOATING_POINT, sourceField.doubleField)
assertThat("doubleField not found", sourceField::class.declaredMemberProperties, hasItem(doubleField))
val targetField = transform<SampleDoubleField, HasDoubleField>()
.getDeclaredConstructor(Double::class.java).newInstance(BIG_FLOATING_POINT)
assertFailsWith<AbstractMethodError> { targetField.doubleField }
assertFailsWith<AbstractMethodError> { targetField.doubleField = 12345.678 }
assertThat("doubleField still exists", targetField::class.declaredMemberProperties, not(hasItem(doubleField)))
}
@Test
fun removeFloat() {
val sourceField = SampleFloatField(FLOATING_POINT)
assertEquals(FLOATING_POINT, sourceField.floatField)
assertThat("floatField not found", sourceField::class.declaredMemberProperties, hasItem(floatField))
val targetField = transform<SampleFloatField, HasFloatField>()
.getDeclaredConstructor(Float::class.java).newInstance(FLOATING_POINT)
assertFailsWith<AbstractMethodError> { targetField.floatField }
assertFailsWith<AbstractMethodError> { targetField.floatField = 123.45f }
assertThat("floatField still exists", targetField::class.declaredMemberProperties, not(hasItem(floatField)))
}
@Test
fun removeArray() {
val sourceField = SampleArrayField(byteArrayOf())
assertArrayEquals(byteArrayOf(), sourceField.arrayField)
assertThat("arrayField not found", sourceField::class.declaredMemberProperties, hasItem(arrayField))
val targetField = transform<SampleArrayField, HasArrayField>()
.getDeclaredConstructor(ByteArray::class.java).newInstance(byteArrayOf())
assertFailsWith<AbstractMethodError> { targetField.arrayField }
assertFailsWith<AbstractMethodError> { targetField.arrayField = byteArrayOf(0x35, 0x73) }
assertThat("arrayField still exists", targetField::class.declaredMemberProperties, not(hasItem(arrayField)))
}
}
interface HasGenericField<T> { var objectField: T }
interface HasLongField { var longField: Long }
interface HasIntField { var intField: Int }
interface HasShortField { var shortField: Short }
interface HasByteField { var byteField: Byte }
interface HasBooleanField { var booleanField: Boolean }
interface HasCharField { var charField: Char }
interface HasFloatField { var floatField: Float }
interface HasDoubleField { var doubleField: Double }
interface HasArrayField { var arrayField: ByteArray }
internal class SampleGenericField<T>(@Deletable override var objectField: T) : HasGenericField<T>
internal class SampleLongField(@Deletable override var longField: Long) : HasLongField
internal class SampleIntField(@Deletable override var intField: Int) : HasIntField
internal class SampleShortField(@Deletable override var shortField: Short) : HasShortField
internal class SampleByteField(@Deletable override var byteField: Byte) : HasByteField
internal class SampleBooleanField(@Deletable override var booleanField: Boolean) : HasBooleanField
internal class SampleCharField(@Deletable override var charField: Char) : HasCharField
internal class SampleFloatField(@Deletable override var floatField: Float) : HasFloatField
internal class SampleDoubleField(@Deletable override var doubleField: Double) : HasDoubleField
internal class SampleArrayField(@Deletable override var arrayField: ByteArray) : HasArrayField

View File

@ -1,61 +0,0 @@
package net.corda.gradle.jarfilter
import org.junit.Assert.*
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import java.lang.reflect.Modifier.*
import kotlin.test.assertFailsWith
class InterfaceFunctionTest {
companion object {
private const val FUNCTION_CLASS = "net.corda.gradle.InterfaceFunctions"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "interface-function")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteInterfaceFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("toDelete", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod("toDelete", Long::class.java) }
}
}
}
@Test
fun cannotStubInterfaceFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("toStubOut", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("toStubOut", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
}
}
}
}

View File

@ -1,272 +0,0 @@
package net.corda.gradle.jarfilter
import org.assertj.core.api.Assertions.*
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.BuildTask
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome.*
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
class JarFilterConfigurationTest {
private companion object {
private const val AMBIGUOUS = "net.corda.gradle.jarfilter.Ambiguous"
private const val DELETE = "net.corda.gradle.jarfilter.DeleteMe"
private const val REMOVE = "net.corda.gradle.jarfilter.RemoveMe"
private const val STUB = "net.corda.gradle.jarfilter.StubMeOut"
}
@Rule
@JvmField
val testProjectDir = TemporaryFolder()
private lateinit var output: String
@Before
fun setup() {
testProjectDir.installResource("gradle.properties")
}
@Test
fun checkNoJarMeansNoSource() {
val result = gradleProject("""
plugins {
id 'java'
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
annotations {
forDelete = ["$DELETE"]
}
}
""").build()
output = result.output
println(output)
val jarFilter = result.forTask("jarFilter")
assertEquals(NO_SOURCE, jarFilter.outcome)
}
@Test
fun checkWithMissingJar() {
val result = gradleProject("""
plugins {
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars = file('does-not-exist.jar')
}
""").buildAndFail()
output = result.output
println(output)
assertThat(output).containsSubsequence(
"Caused by: org.gradle.api.GradleException:",
"Caused by: java.io.FileNotFoundException:"
)
val jarFilter = result.forTask("jarFilter")
assertEquals(FAILED, jarFilter.outcome)
}
@Test
fun checkSameAnnotationForRemoveAndDelete() {
val result = gradleProject("""
plugins {
id 'java'
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars = jar
annotations {
forDelete = ["$AMBIGUOUS"]
forRemove = ["$AMBIGUOUS"]
}
}
""").buildAndFail()
output = result.output
println(output)
assertThat(output).containsSequence(
"Caused by: org.gradle.api.InvalidUserDataException: Annotation 'net.corda.gradle.jarfilter.Ambiguous' also appears in JarFilter 'forDelete' section"
)
val jarFilter = result.forTask("jarFilter")
assertEquals(FAILED, jarFilter.outcome)
}
@Test
fun checkSameAnnotationForRemoveAndStub() {
val result = gradleProject("""
plugins {
id 'java'
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars = jar
annotations {
forStub = ["$AMBIGUOUS"]
forRemove = ["$AMBIGUOUS"]
}
}
""").buildAndFail()
output = result.output
println(output)
assertThat(output).containsSequence(
"Caused by: org.gradle.api.InvalidUserDataException: Annotation 'net.corda.gradle.jarfilter.Ambiguous' also appears in JarFilter 'forStub' section"
)
val jarFilter = result.forTask("jarFilter")
assertEquals(FAILED, jarFilter.outcome)
}
@Test
fun checkSameAnnotationForStubAndDelete() {
val result = gradleProject("""
plugins {
id 'java'
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars = jar
annotations {
forStub = ["$AMBIGUOUS"]
forDelete = ["$AMBIGUOUS"]
}
}
""").buildAndFail()
output = result.output
println(output)
assertThat(output).containsSequence(
"Caused by: org.gradle.api.InvalidUserDataException: Annotation 'net.corda.gradle.jarfilter.Ambiguous' also appears in JarFilter 'forStub' section"
)
val jarFilter = result.forTask("jarFilter")
assertEquals(FAILED, jarFilter.outcome)
}
@Test
fun checkSameAnnotationForStubAndDeleteAndRemove() {
val result = gradleProject("""
plugins {
id 'java'
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars = jar
annotations {
forStub = ["$AMBIGUOUS"]
forDelete = ["$AMBIGUOUS"]
forRemove = ["$AMBIGUOUS"]
}
}
""").buildAndFail()
output = result.output
println(output)
assertThat(output).containsSequence(
"Caused by: org.gradle.api.InvalidUserDataException: Annotation 'net.corda.gradle.jarfilter.Ambiguous' also appears in JarFilter 'forDelete' section"
)
val jarFilter = result.forTask("jarFilter")
assertEquals(FAILED, jarFilter.outcome)
}
@Test
fun checkRepeatedAnnotationForDelete() {
val result = gradleProject("""
plugins {
id 'java'
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars = jar
annotations {
forDelete = ["$DELETE", "$DELETE"]
}
}
""").build()
output = result.output
println(output)
val jarFilter = result.forTask("jarFilter")
assertEquals(SUCCESS, jarFilter.outcome)
}
@Test
fun checkRepeatedAnnotationForStub() {
val result = gradleProject("""
plugins {
id 'java'
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars = jar
annotations {
forStub = ["$STUB", "$STUB"]
}
}
""").build()
output = result.output
println(output)
val jarFilter = result.forTask("jarFilter")
assertEquals(SUCCESS, jarFilter.outcome)
}
@Test
fun checkRepeatedAnnotationForRemove() {
val result = gradleProject("""
plugins {
id 'java'
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars = jar
annotations {
forRemove = ["$REMOVE", "$REMOVE"]
}
}
""").build()
output = result.output
println(output)
val jarFilter = result.forTask("jarFilter")
assertEquals(SUCCESS, jarFilter.outcome)
}
private fun gradleProject(script: String): GradleRunner {
testProjectDir.newFile("build.gradle").writeText(script)
return GradleRunner.create()
.withProjectDir(testProjectDir.root)
.withArguments(getBasicArgsForTasks("jarFilter"))
.withPluginClasspath()
}
private fun BuildResult.forTask(name: String): BuildTask {
return task(":$name") ?: throw AssertionError("No outcome for $name task")
}
}

View File

@ -1,56 +0,0 @@
package net.corda.gradle.jarfilter
import org.assertj.core.api.Assertions.*
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome.*
import org.junit.Assert.*
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.io.FileNotFoundException
import java.nio.file.Path
class JarFilterProject(private val projectDir: TemporaryFolder, private val name: String) : TestRule {
private var _sourceJar: Path? = null
val sourceJar: Path get() = _sourceJar ?: throw FileNotFoundException("Input not found")
private var _filteredJar: Path? = null
val filteredJar: Path get() = _filteredJar ?: throw FileNotFoundException("Output not found")
private var _output: String = ""
val output: String get() = _output
override fun apply(statement: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
projectDir.installResources(
"$name/build.gradle",
"repositories.gradle",
"gradle.properties",
"settings.gradle"
)
val result = GradleRunner.create()
.withProjectDir(projectDir.root)
.withArguments(getGradleArgsForTasks("jarFilter"))
.withPluginClasspath()
.build()
_output = result.output
println(output)
val jarFilter = result.task(":jarFilter")
?: throw AssertionError("No outcome for jarFilter task")
assertEquals(SUCCESS, jarFilter.outcome)
_sourceJar = projectDir.pathOf("build", "libs", "$name.jar")
assertThat(sourceJar).isRegularFile()
_filteredJar = projectDir.pathOf("build", "filtered-libs", "$name-filtered.jar")
assertThat(filteredJar).isRegularFile()
statement.evaluate()
}
}
}
}

View File

@ -1,107 +0,0 @@
package net.corda.gradle.jarfilter
import org.assertj.core.api.Assertions.*
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome.*
import org.junit.Assert.*
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import org.junit.runners.model.Statement
import java.nio.file.Path
import java.nio.file.attribute.FileTime
import java.util.*
import java.util.Calendar.FEBRUARY
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
class JarFilterTimestampTest {
companion object {
private val testProjectDir = TemporaryFolder()
private val sourceJar = DummyJar(testProjectDir, JarFilterTimestampTest::class.java, "timestamps")
private val CONSTANT_TIME: FileTime = FileTime.fromMillis(
GregorianCalendar(1980, FEBRUARY, 1).apply {
timeZone = TimeZone.getTimeZone("UTC")
}.timeInMillis
)
private lateinit var filteredJar: Path
private lateinit var output: String
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(sourceJar)
.around(createTestProject())
private fun createTestProject() = TestRule { base, _ ->
object : Statement() {
override fun evaluate() {
testProjectDir.installResource("gradle.properties")
testProjectDir.newFile("build.gradle").writeText("""
plugins {
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars file("${sourceJar.path.toUri()}")
preserveTimestamps = false
}
""")
val result = GradleRunner.create()
.withProjectDir(testProjectDir.root)
.withArguments(getGradleArgsForTasks("jarFilter"))
.withPluginClasspath()
.build()
output = result.output
println(output)
val metafix = result.task(":jarFilter")
?: throw AssertionError("No outcome for jarFilter task")
assertEquals(SUCCESS, metafix.outcome)
filteredJar = testProjectDir.pathOf("build", "filtered-libs", "timestamps-filtered.jar")
assertThat(filteredJar).isRegularFile()
base.evaluate()
}
}
}
private val ZipEntry.methodName: String get() = if (method == ZipEntry.STORED) "Stored" else "Deflated"
}
@Test
fun fileTimestampsAreRemoved() {
var directoryCount = 0
var classCount = 0
var otherCount = 0
ZipFile(filteredJar.toFile()).use { jar ->
for (entry in jar.entries()) {
println("Entry: ${entry.name}")
println("- ${entry.methodName} (${entry.size} size / ${entry.compressedSize} compressed) bytes")
assertThat(entry.lastModifiedTime).isEqualTo(CONSTANT_TIME)
assertThat(entry.lastAccessTime).isNull()
assertThat(entry.creationTime).isNull()
if (entry.isDirectory) {
++directoryCount
} else if (entry.name.endsWith(".class")) {
++classCount
} else {
++otherCount
}
}
}
assertThat(directoryCount).isGreaterThan(0)
assertThat(classCount).isGreaterThan(0)
assertThat(otherCount).isGreaterThan(0)
}
}

View File

@ -1,47 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.bytecode
import net.corda.gradle.jarfilter.asm.toClass
import net.corda.gradle.jarfilter.matcher.isConstructor
import org.gradle.api.logging.Logger
import org.hamcrest.core.IsCollectionContaining.*
import org.junit.Assert.*
import org.junit.Test
class MetaFixAnnotationTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixAnnotationTest::class)
private val defaultCon = isConstructor(
returnType = SimpleAnnotation::class
)
private val valueCon = isConstructor(
returnType = AnnotationWithValue::class,
parameters = *arrayOf(String::class)
)
}
@Test
fun testSimpleAnnotation() {
val sourceClass = SimpleAnnotation::class.java
assertThat("<init>() not found", sourceClass.kotlin.constructors, hasItem(defaultCon))
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = sourceClass.bytecode.fixMetadata(logger, pathsOf(SimpleAnnotation::class))
.toClass<SimpleAnnotation, Any>()
assertThat("<init>() not found", fixedClass.kotlin.constructors, hasItem(defaultCon))
}
@Test
fun testAnnotationWithValue() {
val sourceClass = AnnotationWithValue::class.java
assertThat("<init>(String) not found", sourceClass.kotlin.constructors, hasItem(valueCon))
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = sourceClass.bytecode.fixMetadata(logger, pathsOf(AnnotationWithValue::class))
.toClass<AnnotationWithValue, Any>()
assertThat("<init>(String) not found", fixedClass.kotlin.constructors, hasItem(valueCon))
}
}
annotation class AnnotationWithValue(val str: String)
annotation class SimpleAnnotation

View File

@ -1,79 +0,0 @@
package net.corda.gradle.jarfilter
import org.assertj.core.api.Assertions.*
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.BuildTask
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome.*
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
class MetaFixConfigurationTests {
@Rule
@JvmField
val testProjectDir = TemporaryFolder()
private lateinit var output: String
@Before
fun setup() {
testProjectDir.installResource("gradle.properties")
}
@Test
fun checkNoJarMeansNoSource() {
val result = gradleProject("""
plugins {
id 'java'
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.MetaFixerTask
task metafix(type: MetaFixerTask)
""").build()
output = result.output
println(output)
val metafix = result.forTask("metafix")
assertEquals(NO_SOURCE, metafix.outcome)
}
@Test
fun checkWithMissingJar() {
val result = gradleProject("""
plugins {
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.MetaFixerTask
task metafix(type: MetaFixerTask) {
jars = file('does-not-exist.jar')
}
""").buildAndFail()
output = result.output
println(output)
assertThat(output).containsSubsequence(
"Caused by: org.gradle.api.GradleException:",
"Caused by: java.io.FileNotFoundException:"
)
val metafix = result.forTask("metafix")
assertEquals(FAILED, metafix.outcome)
}
private fun gradleProject(script: String): GradleRunner {
testProjectDir.newFile("build.gradle").writeText(script)
return GradleRunner.create()
.withProjectDir(testProjectDir.root)
.withArguments(getBasicArgsForTasks("metafix"))
.withPluginClasspath()
}
private fun BuildResult.forTask(name: String): BuildTask {
return task(":$name") ?: throw AssertionError("No outcome for $name task")
}
}

View File

@ -1,110 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.recodeMetadataFor
import net.corda.gradle.jarfilter.asm.toClass
import net.corda.gradle.jarfilter.matcher.isConstructor
import net.corda.gradle.unwanted.HasAll
import org.assertj.core.api.Assertions.*
import org.gradle.api.logging.Logger
import org.hamcrest.core.IsCollectionContaining.*
import org.junit.Assert.*
import org.junit.BeforeClass
import org.junit.Test
import kotlin.reflect.full.primaryConstructor
class MetaFixConstructorDefaultParameterTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixConstructorDefaultParameterTest::class)
private val primaryCon
= isConstructor(WithConstructorParameters::class, Long::class, Int::class, String::class)
private val secondaryCon
= isConstructor(WithConstructorParameters::class, Char::class, String::class)
lateinit var sourceClass: Class<out HasAll>
lateinit var fixedClass: Class<out HasAll>
@BeforeClass
@JvmStatic
fun setup() {
val bytecode = recodeMetadataFor<WithConstructorParameters, MetadataTemplate>()
sourceClass = bytecode.toClass<WithConstructorParameters, HasAll>()
fixedClass = bytecode.fixMetadata(logger, pathsOf(WithConstructorParameters::class))
.toClass<WithConstructorParameters, HasAll>()
}
}
@Test
fun `test source constructor has optional parameters`() {
with(sourceClass.kotlin.constructors) {
assertThat(size).isEqualTo(2)
assertThat("source primary constructor missing", this, hasItem(primaryCon))
assertThat("source secondary constructor missing", this, hasItem(secondaryCon))
}
val sourcePrimary = sourceClass.kotlin.primaryConstructor
?: throw AssertionError("source primary constructor missing")
sourcePrimary.call(BIG_NUMBER, NUMBER, MESSAGE).apply {
assertThat(longData()).isEqualTo(BIG_NUMBER)
assertThat(intData()).isEqualTo(NUMBER)
assertThat(stringData()).isEqualTo(MESSAGE)
}
val sourceSecondary = sourceClass.kotlin.constructors.firstOrNull { it != sourcePrimary }
?: throw AssertionError("source secondary constructor missing")
sourceSecondary.call('X', MESSAGE).apply {
assertThat(stringData()).isEqualTo("X$MESSAGE")
}
assertTrue("All source parameters should have defaults", sourcePrimary.hasAllOptionalParameters)
}
@Test
fun `test fixed constructors exist`() {
with(fixedClass.kotlin.constructors) {
assertThat(size).isEqualTo(2)
assertThat("fixed primary constructor missing", this, hasItem(primaryCon))
assertThat("fixed secondary constructor missing", this, hasItem(secondaryCon))
}
}
@Test
fun `test fixed primary constructor has mandatory parameters`() {
val fixedPrimary = fixedClass.kotlin.primaryConstructor
?: throw AssertionError("fixed primary constructor missing")
assertTrue("All fixed parameters should be mandatory", fixedPrimary.hasAllMandatoryParameters)
}
@Test
fun `test fixed secondary constructor still has optional parameters`() {
val fixedSecondary = (fixedClass.kotlin.constructors - fixedClass.kotlin.primaryConstructor).firstOrNull()
?: throw AssertionError("fixed secondary constructor missing")
assertTrue("Some fixed parameters should be optional", fixedSecondary.hasAnyOptionalParameters)
}
class MetadataTemplate(
private val longData: Long = 0,
private val intData: Int = 0,
private val message: String = DEFAULT_MESSAGE
) : HasAll {
@Suppress("UNUSED")
constructor(prefix: Char, message: String = DEFAULT_MESSAGE) : this(message = prefix + message)
override fun longData(): Long = longData
override fun intData(): Int = intData
override fun stringData(): String = message
}
}
class WithConstructorParameters(
private val longData: Long,
private val intData: Int,
private val message: String
) : HasAll {
@Suppress("UNUSED")
constructor(prefix: Char, message: String = DEFAULT_MESSAGE) : this(0, 0, prefix + message)
override fun longData(): Long = longData
override fun intData(): Int = intData
override fun stringData(): String = message
}

View File

@ -1,53 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.unwanted.*
import net.corda.gradle.jarfilter.asm.*
import net.corda.gradle.jarfilter.matcher.*
import org.gradle.api.logging.Logger
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.hamcrest.core.IsNot.not
import org.junit.Assert.*
import org.junit.Test
import kotlin.jvm.kotlin
class MetaFixConstructorTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixConstructorTest::class)
private val unwantedCon = isConstructor(WithConstructor::class, Int::class, Long::class)
private val wantedCon = isConstructor(WithConstructor::class, Long::class)
}
@Test
fun testConstructorRemovedFromMetadata() {
val bytecode = recodeMetadataFor<WithConstructor, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithConstructor, HasLong>()
// Check that the unwanted constructor has been successfully
// added to the metadata, and that the class is valid.
val sourceObj = sourceClass.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
assertEquals(BIG_NUMBER, sourceObj.longData())
with(sourceClass.kotlin.constructors) {
assertThat("<init>(Int,Long) not found", this, hasItem(unwantedCon))
assertThat("<init>(Long) not found", this, hasItem(wantedCon))
}
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithConstructor::class)).toClass<WithConstructor, HasLong>()
val fixedObj = fixedClass.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
assertEquals(BIG_NUMBER, fixedObj.longData())
with(fixedClass.kotlin.constructors) {
assertThat("<init>(Int,Long) still exists", this, not(hasItem(unwantedCon)))
assertThat("<init>(Long) not found", this, hasItem(wantedCon))
}
}
class MetadataTemplate(private val longData: Long) : HasLong {
@Suppress("UNUSED_PARAMETER", "UNUSED")
constructor(intData: Int, longData: Long) : this(longData)
override fun longData(): Long = longData
}
}
class WithConstructor(private val longData: Long) : HasLong {
override fun longData(): Long = longData
}

View File

@ -1,96 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.recodeMetadataFor
import net.corda.gradle.jarfilter.asm.toClass
import net.corda.gradle.jarfilter.matcher.isFunction
import org.assertj.core.api.Assertions.*
import org.gradle.api.logging.Logger
import org.hamcrest.core.IsCollectionContaining.*
import org.junit.Assert.*
import org.junit.BeforeClass
import org.junit.Test
import kotlin.reflect.KFunction
import kotlin.reflect.full.declaredFunctions
class MetaFixFunctionDefaultParameterTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixFunctionDefaultParameterTest::class)
private val hasMandatoryParams
= isFunction("hasMandatoryParams", String::class, Long::class, Int::class, String::class)
private val hasOptionalParams
= isFunction("hasOptionalParams", String::class, String::class)
lateinit var sourceClass: Class<out Any>
lateinit var fixedClass: Class<out Any>
@BeforeClass
@JvmStatic
fun setup() {
val bytecode = recodeMetadataFor<WithFunctionParameters, MetadataTemplate>()
sourceClass = bytecode.toClass<WithFunctionParameters, Any>()
fixedClass = bytecode.fixMetadata(logger, pathsOf(WithFunctionParameters::class))
.toClass<WithFunctionParameters, Any>()
}
}
@Test
fun `test source functions have default parameters`() {
with(sourceClass.kotlin.declaredFunctions) {
assertThat(size).isEqualTo(2)
assertThat("source mandatory parameters missing", this, hasItem(hasMandatoryParams))
assertThat("source optional parameters missing", this, hasItem(hasOptionalParams))
}
val sourceUnwanted = sourceClass.kotlin.declaredFunctions.findOrFail("hasMandatoryParams")
assertThat(sourceUnwanted.call(sourceClass.newInstance(), BIG_NUMBER, NUMBER, MESSAGE))
.isEqualTo("Long: $BIG_NUMBER, Int: $NUMBER, String: $MESSAGE")
assertTrue("All source parameters should be optional", sourceUnwanted.hasAllOptionalParameters)
val sourceWanted = sourceClass.kotlin.declaredFunctions.findOrFail("hasOptionalParams")
assertThat(sourceWanted.call(sourceClass.newInstance(), MESSAGE))
.isEqualTo(MESSAGE)
assertTrue("All source parameters should be optional", sourceWanted.hasAllOptionalParameters)
}
@Test
fun `test fixed functions exist`() {
with(fixedClass.kotlin.declaredFunctions) {
assertThat(size).isEqualTo(2)
assertThat("fixed mandatory parameters missing", this, hasItem(hasMandatoryParams))
assertThat("fixed optional parameters missing", this, hasItem(hasOptionalParams))
}
}
@Test
fun `test unwanted default parameters are removed`() {
val fixedMandatory = fixedClass.kotlin.declaredFunctions.findOrFail("hasMandatoryParams")
assertTrue("All fixed parameters should be mandatory", fixedMandatory.hasAllMandatoryParameters)
}
@Test
fun `test wanted default parameters are kept`() {
val fixedOptional = fixedClass.kotlin.declaredFunctions.findOrFail("hasOptionalParams")
assertTrue("All fixed parameters should be optional", fixedOptional.hasAllOptionalParameters)
}
@Suppress("UNUSED")
abstract class MetadataTemplate {
abstract fun hasMandatoryParams(longData: Long = 0, intData: Int = 0, message: String = DEFAULT_MESSAGE): String
abstract fun hasOptionalParams(message: String = DEFAULT_MESSAGE): String
}
private fun <T> Iterable<KFunction<T>>.findOrFail(name: String): KFunction<T> {
return find { it.name == name } ?: throw AssertionError("$name missing")
}
}
@Suppress("UNUSED")
class WithFunctionParameters {
fun hasMandatoryParams(longData: Long, intData: Int, message: String): String {
return "Long: $longData, Int: $intData, String: $message"
}
fun hasOptionalParams(message: String = DEFAULT_MESSAGE): String = message
}

View File

@ -1,53 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.unwanted.*
import net.corda.gradle.jarfilter.asm.*
import net.corda.gradle.jarfilter.matcher.*
import org.gradle.api.logging.Logger
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.hamcrest.core.IsNot.not
import org.junit.Assert.*
import org.junit.Test
import kotlin.jvm.kotlin
import kotlin.reflect.full.declaredFunctions
class MetaFixFunctionTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixFunctionTest::class)
private val longData = isFunction("longData", Long::class)
private val unwantedFun = isFunction("unwantedFun", String::class, String::class)
}
@Test
fun testFunctionRemovedFromMetadata() {
val bytecode = recodeMetadataFor<WithFunction, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithFunction, HasLong>()
// Check that the unwanted function has been successfully
// added to the metadata, and that the class is valid.
val sourceObj = sourceClass.newInstance()
assertEquals(BIG_NUMBER, sourceObj.longData())
with(sourceClass.kotlin.declaredFunctions) {
assertThat("unwantedFun(String) not found", this, hasItem(unwantedFun))
assertThat("longData not found", this, hasItem(longData))
}
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithFunction::class)).toClass<WithFunction, HasLong>()
val fixedObj = fixedClass.newInstance()
assertEquals(BIG_NUMBER, fixedObj.longData())
with(fixedClass.kotlin.declaredFunctions) {
assertThat("unwantedFun(String) still exists", this, not(hasItem(unwantedFun)))
assertThat("longData not found", this, hasItem(longData))
}
}
class MetadataTemplate : HasLong {
override fun longData(): Long = 0
@Suppress("UNUSED") fun unwantedFun(str: String): String = "UNWANTED[$str]"
}
}
class WithFunction : HasLong {
override fun longData(): Long = BIG_NUMBER
}

View File

@ -1,57 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.*
import org.assertj.core.api.Assertions.*
import org.gradle.api.logging.Logger
import org.junit.Test
import kotlin.reflect.jvm.jvmName
/**
* Kotlin reflection will attempt to validate the nested classes stored in the [kotlin.Metadata]
* annotation rather than just reporting what is there, which means that it can tell us nothing
* about what the MetaFixer task has done.
*/
class MetaFixNestedClassTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixNestedClassTest::class)
private val WANTED_CLASS: String = WithNestedClass.Wanted::class.jvmName
private val UNWANTED_CLASS: String = "${WithNestedClass::class.jvmName}\$Unwanted"
}
@Test
fun testNestedClassRemovedFromMetadata() {
val bytecode = recodeMetadataFor<WithNestedClass, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithNestedClass, Any>()
assertThat(sourceClass.classMetadata.nestedClasses).containsExactlyInAnyOrder(WANTED_CLASS, UNWANTED_CLASS)
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithNestedClass::class, WithNestedClass.Wanted::class))
.toClass<WithNestedClass, Any>()
assertThat(fixedClass.classMetadata.nestedClasses).containsExactly(WANTED_CLASS)
}
@Test
fun testAllNestedClassesRemovedFromMetadata() {
val bytecode = recodeMetadataFor<WithoutNestedClass, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithoutNestedClass, Any>()
assertThat(sourceClass.classMetadata.nestedClasses)
.containsExactlyInAnyOrder("${WithoutNestedClass::class.jvmName}\$Wanted", "${WithoutNestedClass::class.jvmName}\$Unwanted")
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithoutNestedClass::class))
.toClass<WithoutNestedClass, Any>()
assertThat(fixedClass.classMetadata.nestedClasses).isEmpty()
}
@Suppress("UNUSED")
class MetadataTemplate {
class Wanted
class Unwanted
}
}
class WithNestedClass {
class Wanted
}
class WithoutNestedClass

View File

@ -1,39 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.metadataAs
import net.corda.gradle.jarfilter.asm.toClass
import org.gradle.api.logging.Logger
import org.junit.BeforeClass
import org.junit.Test
import kotlin.reflect.full.declaredFunctions
import kotlin.test.assertFailsWith
/**
* These tests cannot actually "test" anything until Kotlin reflection
* supports package metadata. Until then, we can only execute the code
* paths to ensure they don't throw any exceptions.
*/
class MetaFixPackageDefaultParameterTest {
companion object {
private const val TEMPLATE_CLASS = "net.corda.gradle.jarfilter.template.PackageWithDefaultParameters"
private const val DEFAULT_PARAMETERS_CLASS = "net.corda.gradle.jarfilter.PackageWithDefaultParameters"
private val logger: Logger = StdOutLogging(MetaFixPackageDefaultParameterTest::class)
lateinit var sourceClass: Class<out Any>
lateinit var fixedClass: Class<out Any>
@BeforeClass
@JvmStatic
fun setup() {
val defaultParametersClass = Class.forName(DEFAULT_PARAMETERS_CLASS)
val bytecode = defaultParametersClass.metadataAs(Class.forName(TEMPLATE_CLASS))
sourceClass = bytecode.toClass(defaultParametersClass, Any::class.java)
fixedClass = bytecode.fixMetadata(logger, setOf(DEFAULT_PARAMETERS_CLASS)).toClass(sourceClass, Any::class.java)
}
}
@Test
fun `test package functions`() {
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredFunctions }
}
}

View File

@ -1,66 +0,0 @@
@file:JvmName("PackageTemplate")
@file:Suppress("UNUSED")
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.*
import net.corda.gradle.jarfilter.matcher.*
import org.gradle.api.logging.Logger
import org.junit.BeforeClass
import org.junit.Test
import kotlin.jvm.kotlin
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.full.declaredMembers
import kotlin.test.assertFailsWith
/**
* These tests cannot actually "test" anything until Kotlin reflection
* supports package metadata. Until then, we can only execute the code
* paths to ensure they don't throw any exceptions.
*/
class MetaFixPackageTest {
companion object {
private const val TEMPLATE_CLASS = "net.corda.gradle.jarfilter.PackageTemplate"
private const val EMPTY_CLASS = "net.corda.gradle.jarfilter.EmptyPackage"
private val logger: Logger = StdOutLogging(MetaFixPackageTest::class)
private val staticVal = isProperty("templateVal", Long::class)
private val staticVar = isProperty("templateVar", Int::class)
private val staticFun = isFunction("templateFun", String::class)
private lateinit var sourceClass: Class<out Any>
private lateinit var fixedClass: Class<out Any>
@BeforeClass
@JvmStatic
fun setup() {
val emptyClass = Class.forName(EMPTY_CLASS)
val bytecode = emptyClass.metadataAs(Class.forName(TEMPLATE_CLASS))
sourceClass = bytecode.toClass(emptyClass, Any::class.java)
fixedClass = bytecode.fixMetadata(logger, setOf(EMPTY_CLASS)).toClass(sourceClass, Any::class.java)
}
}
@Test
fun testPackageFunction() {
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredFunctions }
//assertThat("templateFun() not found", sourceClass.kotlin.declaredFunctions, hasItem(staticFun))
//assertThat("templateFun() still exists", fixedClass.kotlin.declaredFunctions, not(hasItem(staticFun)))
}
@Test
fun testPackageVal() {
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredMembers }
//assertThat("templateVal not found", sourceClass.kotlin.declaredMembers, hasItem(staticVal))
//assertThat("templateVal still exists", fixedClass.kotlin.declaredMembers, not(hasItem(staticVal)))
}
@Test
fun testPackageVar() {
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredMembers }
//assertThat("templateVar not found", sourceClass.kotlin.declaredMembers, hasItem(staticVar))
//assertThat("templateVar still exists", fixedClass.kotlin.declaredMembers, not(hasItem(staticVar)))
}
}
internal fun templateFun(): String = MESSAGE
internal const val templateVal: Long = BIG_NUMBER
internal var templateVar: Int = NUMBER

View File

@ -1,57 +0,0 @@
package net.corda.gradle.jarfilter
import org.assertj.core.api.Assertions.*
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome.*
import org.junit.Assert.*
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.io.FileNotFoundException
import java.nio.file.Path
@Suppress("UNUSED")
class MetaFixProject(private val projectDir: TemporaryFolder, private val name: String) : TestRule {
private var _sourceJar: Path? = null
val sourceJar: Path get() = _sourceJar ?: throw FileNotFoundException("Input not found")
private var _metafixedJar: Path? = null
val metafixedJar: Path get() = _metafixedJar ?: throw FileNotFoundException("Output not found")
private var _output: String = ""
val output: String get() = _output
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
projectDir.installResources(
"$name/build.gradle",
"repositories.gradle",
"gradle.properties",
"settings.gradle"
)
val result = GradleRunner.create()
.withProjectDir(projectDir.root)
.withArguments(getGradleArgsForTasks("metafix"))
.withPluginClasspath()
.build()
_output = result.output
println(output)
val metafix = result.task(":metafix")
?: throw AssertionError("No outcome for metafix task")
assertEquals(SUCCESS, metafix.outcome)
_sourceJar = projectDir.pathOf("build", "libs", "$name.jar")
assertThat(sourceJar).isRegularFile()
_metafixedJar = projectDir.pathOf("build", "metafixer-libs", "$name-metafixed.jar")
assertThat(metafixedJar).isRegularFile()
base.evaluate()
}
}
}
}

View File

@ -1,37 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.asm.*
import org.assertj.core.api.Assertions.*
import org.gradle.api.logging.Logger
import org.junit.Test
import kotlin.reflect.jvm.jvmName
class MetaFixSealedClassTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixSealedClassTest::class)
private val UNWANTED_CLASS: String = "${MetaSealedClass::class.jvmName}\$Unwanted"
private val WANTED_CLASS: String = MetaSealedClass.Wanted::class.jvmName
}
@Test
fun testSealedSubclassRemovedFromMetadata() {
val bytecode = recodeMetadataFor<MetaSealedClass, MetadataTemplate>()
val sourceClass = bytecode.toClass<MetaSealedClass, Any>()
assertThat(sourceClass.classMetadata.sealedSubclasses).containsExactlyInAnyOrder(UNWANTED_CLASS, WANTED_CLASS)
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(MetaSealedClass::class, MetaSealedClass.Wanted::class))
.toClass<MetaSealedClass, Any>()
assertThat(fixedClass.classMetadata.sealedSubclasses).containsExactly(WANTED_CLASS)
}
@Suppress("UNUSED")
sealed class MetadataTemplate {
class Wanted : MetadataTemplate()
class Unwanted : MetadataTemplate()
}
}
sealed class MetaSealedClass {
class Wanted : MetaSealedClass()
}

View File

@ -1,108 +0,0 @@
package net.corda.gradle.jarfilter
import org.assertj.core.api.Assertions.*
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome.*
import org.junit.Assert.*
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import org.junit.runners.model.Statement
import java.nio.file.Path
import java.nio.file.attribute.FileTime
import java.util.*
import java.util.Calendar.FEBRUARY
import java.util.zip.ZipEntry
import java.util.zip.ZipEntry.*
import java.util.zip.ZipFile
class MetaFixTimestampTest {
companion object {
private val testProjectDir = TemporaryFolder()
private val sourceJar = DummyJar(testProjectDir, MetaFixTimestampTest::class.java, "timestamps")
private val CONSTANT_TIME: FileTime = FileTime.fromMillis(
GregorianCalendar(1980, FEBRUARY, 1).apply {
timeZone = TimeZone.getTimeZone("UTC")
}.timeInMillis
)
private lateinit var metafixedJar: Path
private lateinit var output: String
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(sourceJar)
.around(createTestProject())
private fun createTestProject() = TestRule { base, _ ->
object : Statement() {
override fun evaluate() {
testProjectDir.installResource("gradle.properties")
testProjectDir.newFile("build.gradle").writeText("""
plugins {
id 'net.corda.plugins.jar-filter'
}
import net.corda.gradle.jarfilter.MetaFixerTask
task metafix(type: MetaFixerTask) {
jars file("${sourceJar.path.toUri()}")
preserveTimestamps = false
}
""")
val result = GradleRunner.create()
.withProjectDir(testProjectDir.root)
.withArguments(getGradleArgsForTasks("metafix"))
.withPluginClasspath()
.build()
output = result.output
println(output)
val metafix = result.task(":metafix")
?: throw AssertionError("No outcome for metafix task")
assertEquals(SUCCESS, metafix.outcome)
metafixedJar = testProjectDir.pathOf("build", "metafixer-libs", "timestamps-metafixed.jar")
assertThat(metafixedJar).isRegularFile()
base.evaluate()
}
}
}
private val ZipEntry.methodName: String get() = if (method == STORED) "Stored" else "Deflated"
}
@Test
fun fileTimestampsAreRemoved() {
var directoryCount = 0
var classCount = 0
var otherCount = 0
ZipFile(metafixedJar.toFile()).use { jar ->
for (entry in jar.entries()) {
println("Entry: ${entry.name}")
println("- ${entry.methodName} (${entry.size} size / ${entry.compressedSize} compressed) bytes")
assertThat(entry.lastModifiedTime).isEqualTo(CONSTANT_TIME)
assertThat(entry.lastAccessTime).isNull()
assertThat(entry.creationTime).isNull()
if (entry.isDirectory) {
++directoryCount
} else if (entry.name.endsWith(".class")) {
++classCount
} else {
++otherCount
}
}
}
assertThat(directoryCount).isGreaterThan(0)
assertThat(classCount).isGreaterThan(0)
assertThat(otherCount).isGreaterThan(0)
}
}

View File

@ -1,49 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.unwanted.*
import net.corda.gradle.jarfilter.asm.*
import net.corda.gradle.jarfilter.matcher.*
import org.gradle.api.logging.Logger
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.hamcrest.core.IsNot.not
import org.junit.Assert.*
import org.junit.Test
import kotlin.jvm.kotlin
import kotlin.reflect.full.declaredMemberProperties
class MetaFixValPropertyTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixValPropertyTest::class)
private val unwantedVal = isProperty("unwantedVal", String::class)
private val intVal = isProperty("intVal", Int::class)
}
@Test
fun testPropertyRemovedFromMetadata() {
val bytecode = recodeMetadataFor<WithValProperty, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithValProperty, HasIntVal>()
// Check that the unwanted property has been successfully
// added to the metadata, and that the class is valid.
val sourceObj = sourceClass.newInstance()
assertEquals(NUMBER, sourceObj.intVal)
assertThat("unwantedVal not found", sourceClass.kotlin.declaredMemberProperties, hasItem(unwantedVal))
assertThat("intVal not found", sourceClass.kotlin.declaredMemberProperties, hasItem(intVal))
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithValProperty::class)).toClass<WithValProperty, HasIntVal>()
val fixedObj = fixedClass.newInstance()
assertEquals(NUMBER, fixedObj.intVal)
assertThat("unwantedVal still exists", fixedClass.kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
assertThat("intVal not found", fixedClass.kotlin.declaredMemberProperties, hasItem(intVal))
}
class MetadataTemplate : HasIntVal {
override val intVal: Int = 0
@Suppress("UNUSED") val unwantedVal: String = "UNWANTED"
}
}
class WithValProperty : HasIntVal {
override val intVal: Int = NUMBER
}

View File

@ -1,49 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.unwanted.*
import net.corda.gradle.jarfilter.asm.*
import net.corda.gradle.jarfilter.matcher.*
import org.gradle.api.logging.Logger
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.hamcrest.core.IsNot.not
import org.junit.Assert.*
import org.junit.Test
import kotlin.jvm.kotlin
import kotlin.reflect.full.declaredMemberProperties
class MetaFixVarPropertyTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixVarPropertyTest::class)
private val unwantedVar = isProperty("unwantedVar", String::class)
private val intVar = isProperty("intVar", Int::class)
}
@Test
fun testPropertyRemovedFromMetadata() {
val bytecode = recodeMetadataFor<WithVarProperty, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithVarProperty, HasIntVar>()
// Check that the unwanted property has been successfully
// added to the metadata, and that the class is valid.
val sourceObj = sourceClass.newInstance()
assertEquals(NUMBER, sourceObj.intVar)
assertThat("unwantedVar not found", sourceClass.kotlin.declaredMemberProperties, hasItem(unwantedVar))
assertThat("intVar not found", sourceClass.kotlin.declaredMemberProperties, hasItem(intVar))
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithVarProperty::class)).toClass<WithVarProperty, HasIntVar>()
val fixedObj = fixedClass.newInstance()
assertEquals(NUMBER, fixedObj.intVar)
assertThat("unwantedVar still exists", fixedClass.kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
assertThat("intVar not found", fixedClass.kotlin.declaredMemberProperties, hasItem(intVar))
}
class MetadataTemplate : HasIntVar {
override var intVar: Int = 0
@Suppress("UNUSED") var unwantedVar: String = "UNWANTED"
}
}
class WithVarProperty : HasIntVar {
override var intVar: Int = NUMBER
}

View File

@ -1,88 +0,0 @@
package net.corda.gradle.jarfilter
import org.junit.Assert.*
import org.junit.Test
import org.objectweb.asm.Opcodes.*
class MethodElementTest {
private companion object {
private const val DESCRIPTOR = "()Ljava.lang.String;"
}
@Test
fun testMethodsMatchByNameAndDescriptor() {
val elt = MethodElement(
name = "getThing",
descriptor = DESCRIPTOR,
access = ACC_PUBLIC or ACC_ABSTRACT or ACC_FINAL
)
assertEquals(MethodElement(name="getThing", descriptor=DESCRIPTOR), elt)
assertNotEquals(MethodElement(name="getOther", descriptor=DESCRIPTOR), elt)
assertNotEquals(MethodElement(name="getThing", descriptor="()J"), elt)
}
@Test
fun testBasicMethodVisibleName() {
val elt = MethodElement(
name = "getThing",
descriptor = DESCRIPTOR,
access = ACC_PUBLIC
)
assertEquals("getThing", elt.visibleName)
}
@Test
fun testMethodVisibleNameWithSuffix() {
val elt = MethodElement(
name = "getThing\$extra",
descriptor = DESCRIPTOR,
access = ACC_PUBLIC
)
assertEquals("getThing", elt.visibleName)
}
@Test
fun testSyntheticMethodSuffix() {
val elt = MethodElement(
name = "getThing\$extra",
descriptor = DESCRIPTOR,
access = ACC_PUBLIC or ACC_SYNTHETIC
)
assertTrue(elt.isKotlinSynthetic("extra"))
assertFalse(elt.isKotlinSynthetic("something"))
assertTrue(elt.isKotlinSynthetic("extra", "something"))
}
@Test
fun testPublicMethodSuffix() {
val elt = MethodElement(
name = "getThing\$extra",
descriptor = DESCRIPTOR,
access = ACC_PUBLIC
)
assertFalse(elt.isKotlinSynthetic("extra"))
}
@Test
fun testMethodDoesNotExpire() {
val elt = MethodElement(
name = "getThing\$extra",
descriptor = DESCRIPTOR,
access = ACC_PUBLIC
)
assertFalse(elt.isExpired)
assertFalse(elt.isExpired)
assertFalse(elt.isExpired)
}
@Test
fun testArtificialMethodDoesExpire() {
val elt = MethodElement(
name = "getThing\$extra",
descriptor = DESCRIPTOR
)
assertFalse(elt.isExpired)
assertTrue(elt.isExpired)
assertTrue(elt.isExpired)
}
}

View File

@ -1,12 +0,0 @@
@file:JvmName("PackageWithDefaultParameters")
@file:Suppress("UNUSED")
package net.corda.gradle.jarfilter
/**
* Example package functions, one with default parameter values and one without.
* We will rewrite this class's metadata so that it expects both functions to
* have default parameter values, and then ask the [MetaFixerTask] to fix it.
*/
fun hasDefaultParameters(intData: Int=0, message: String=DEFAULT_MESSAGE): String = "$message: intData=$intData"
fun hasMandatoryParameters(longData: Long, message: String): String = "$message: longData=$longData"

View File

@ -1,176 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.unwanted.HasUnwantedFun
import net.corda.gradle.unwanted.HasUnwantedVal
import net.corda.gradle.unwanted.HasUnwantedVar
import org.junit.Assert.*
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
class RemoveAnnotationsTest {
companion object {
private const val ANNOTATED_CLASS = "net.corda.gradle.HasUnwantedAnnotations"
private const val REMOVE_ME_CLASS = "net.corda.gradle.jarfilter.RemoveMe"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "remove-annotations")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteFromClass() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
assertNotNull(getAnnotation(removeMe))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
assertNull(getAnnotation(removeMe))
}
}
}
@Test
fun deleteFromDefaultConstructor() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getDeclaredConstructor().also { con ->
assertNotNull(con.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getDeclaredConstructor().also { con ->
assertNull(con.getAnnotation(removeMe))
}
}
}
}
@Test
fun deleteFromPrimaryConstructor() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getDeclaredConstructor(Long::class.java, String::class.java).also { con ->
assertNotNull(con.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getDeclaredConstructor(Long::class.java, String::class.java).also { con ->
assertNull(con.getAnnotation(removeMe))
}
}
}
}
@Test
fun deleteFromField() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getField("longField").also { field ->
assertNotNull(field.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getField("longField").also { field ->
assertNull(field.getAnnotation(removeMe))
}
}
}
}
@Test
fun deleteFromMethod() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedFun>(ANNOTATED_CLASS).apply {
getMethod("unwantedFun", String::class.java).also { method ->
assertNotNull(method.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedFun>(ANNOTATED_CLASS).apply {
getMethod("unwantedFun", String::class.java).also { method ->
assertNull(method.getAnnotation(removeMe))
}
}
}
}
@Test
fun deleteFromValProperty() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedVal>(ANNOTATED_CLASS).apply {
getMethod("getUnwantedVal").also { method ->
assertNotNull(method.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedVal>(ANNOTATED_CLASS).apply {
getMethod("getUnwantedVal").also { method ->
assertNull(method.getAnnotation(removeMe))
}
}
}
}
@Test
fun deleteFromVarProperty() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedVar>(ANNOTATED_CLASS).apply {
getMethod("getUnwantedVar").also { method ->
assertNotNull(method.getAnnotation(removeMe))
}
getMethod("setUnwantedVar", String::class.java).also { method ->
assertNotNull(method.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedVar>(ANNOTATED_CLASS).apply {
getMethod("getUnwantedVar").also { method ->
assertNull(method.getAnnotation(removeMe))
}
getMethod("setUnwantedVar", String::class.java).also { method ->
assertNull(method.getAnnotation(removeMe))
}
}
}
}
}

View File

@ -1,234 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.*
import net.corda.gradle.unwanted.HasInt
import net.corda.gradle.unwanted.HasLong
import net.corda.gradle.unwanted.HasString
import org.assertj.core.api.Assertions.*
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.junit.Assert.*
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.jvm.kotlin
import kotlin.reflect.full.primaryConstructor
import kotlin.test.assertFailsWith
class SanitiseDeleteConstructorTest {
companion object {
private const val COMPLEX_CONSTRUCTOR_CLASS = "net.corda.gradle.HasOverloadedComplexConstructorToDelete"
private const val COUNT_INITIAL_OVERLOADED = 1
private const val COUNT_INITIAL_MULTIPLE = 2
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "sanitise-delete-constructor")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteOverloadedLongConstructor() = checkClassWithLongParameter(
"net.corda.gradle.HasOverloadedLongConstructorToDelete",
COUNT_INITIAL_OVERLOADED
)
@Test
fun deleteMultipleLongConstructor() = checkClassWithLongParameter(
"net.corda.gradle.HasMultipleLongConstructorsToDelete",
COUNT_INITIAL_MULTIPLE
)
private fun checkClassWithLongParameter(longClass: String, initialCount: Int) {
val longConstructor = isConstructor(longClass, Long::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasLong>(longClass).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
assertEquals(BIG_NUMBER, it.longData())
}
kotlin.constructors.apply {
assertThat("<init>(J) not found", this, hasItem(longConstructor))
assertEquals(initialCount, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(noArg.callBy(emptyMap()).longData()).isEqualTo(0)
assertThat(newInstance().longData()).isEqualTo(0)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLong>(longClass).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
assertEquals(BIG_NUMBER, it.longData())
}
kotlin.constructors.apply {
assertThat("<init>(J) not found", this, hasItem(longConstructor))
assertEquals(1, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
assertNull("no-arg constructor exists", kotlin.noArgConstructor)
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor() }
}
}
}
@Test
fun deleteOverloadedIntConstructor() = checkClassWithIntParameter(
"net.corda.gradle.HasOverloadedIntConstructorToDelete",
COUNT_INITIAL_OVERLOADED
)
@Test
fun deleteMultipleIntConstructor() = checkClassWithIntParameter(
"net.corda.gradle.HasMultipleIntConstructorsToDelete",
COUNT_INITIAL_MULTIPLE
)
private fun checkClassWithIntParameter(intClass: String, initialCount: Int) {
val intConstructor = isConstructor(intClass, Int::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasInt>(intClass).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
assertEquals(NUMBER, it.intData())
}
kotlin.constructors.apply {
assertThat("<init>(I) not found", this, hasItem(intConstructor))
assertEquals(initialCount, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(noArg.callBy(emptyMap()).intData()).isEqualTo(0)
assertThat(newInstance().intData()).isEqualTo(0)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasInt>(intClass).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
assertEquals(NUMBER, it.intData())
}
kotlin.constructors.apply {
assertThat("<init>(I) not found", this, hasItem(intConstructor))
assertEquals(1, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
assertNull("no-arg constructor exists", kotlin.noArgConstructor)
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor() }
}
}
}
@Test
fun deleteOverloadedStringConstructor() = checkClassWithStringParameter(
"net.corda.gradle.HasOverloadedStringConstructorToDelete",
COUNT_INITIAL_OVERLOADED
)
@Test
fun deleteMultipleStringConstructor() = checkClassWithStringParameter(
"net.corda.gradle.HasMultipleStringConstructorsToDelete",
COUNT_INITIAL_MULTIPLE
)
private fun checkClassWithStringParameter(stringClass: String, initialCount: Int) {
val stringConstructor = isConstructor(stringClass, String::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(stringClass).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertEquals(MESSAGE, it.stringData())
}
kotlin.constructors.apply {
assertThat("<init>(String) not found", this, hasItem(stringConstructor))
assertEquals(initialCount, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(noArg.callBy(emptyMap()).stringData()).isEqualTo(DEFAULT_MESSAGE)
assertThat(newInstance().stringData()).isEqualTo(DEFAULT_MESSAGE)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(stringClass).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertEquals(MESSAGE, it.stringData())
}
kotlin.constructors.apply {
assertThat("<init>(String) not found", this, hasItem(stringConstructor))
assertEquals(1, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
assertNull("no-arg constructor exists", kotlin.noArgConstructor)
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor() }
}
}
}
@Test
fun deleteOverloadedComplexConstructor() {
val complexConstructor = isConstructor(COMPLEX_CONSTRUCTOR_CLASS, Int::class, String::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(COMPLEX_CONSTRUCTOR_CLASS).apply {
kotlin.constructors.apply {
assertThat("<init>(Int,String) not found", this, hasItem(complexConstructor))
assertEquals(1, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
primary.call(NUMBER, MESSAGE).also { complex ->
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
assertThat((complex as HasInt).intData()).isEqualTo(NUMBER)
}
primary.callBy(mapOf(primary.parameters[1] to MESSAGE)).also { complex ->
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
assertThat((complex as HasInt).intData()).isEqualTo(0)
}
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { complex ->
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
assertThat((complex as HasInt).intData()).isEqualTo(0)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(COMPLEX_CONSTRUCTOR_CLASS).apply {
kotlin.constructors.apply {
assertThat("<init>(Int,String) not found", this, hasItem(complexConstructor))
assertEquals(1, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
primary.call(NUMBER, MESSAGE).also { complex ->
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
assertThat((complex as HasInt).intData()).isEqualTo(NUMBER)
}
assertThat(assertFailsWith<IllegalArgumentException> { primary.callBy(mapOf(primary.parameters[1] to MESSAGE)) })
.hasMessageContaining("No argument provided for a required parameter")
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(String::class.java) }
}
}
}
}

View File

@ -1,250 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.matcher.*
import net.corda.gradle.unwanted.HasInt
import net.corda.gradle.unwanted.HasLong
import net.corda.gradle.unwanted.HasString
import org.assertj.core.api.Assertions.*
import org.hamcrest.core.IsCollectionContaining.hasItem
import org.junit.Assert.*
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import java.lang.reflect.InvocationTargetException
import kotlin.jvm.kotlin
import kotlin.reflect.full.primaryConstructor
import kotlin.test.assertFailsWith
class SanitiseStubConstructorTest {
companion object {
private const val COMPLEX_CONSTRUCTOR_CLASS = "net.corda.gradle.HasOverloadedComplexConstructorToStub"
private const val COUNT_OVERLOADED = 1
private const val COUNT_MULTIPLE = 2
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "sanitise-stub-constructor")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun stubOverloadedLongConstructor() = checkClassWithLongParameter(
"net.corda.gradle.HasOverloadedLongConstructorToStub",
COUNT_OVERLOADED
)
@Test
fun stubMultipleLongConstructor() = checkClassWithLongParameter(
"net.corda.gradle.HasMultipleLongConstructorsToStub",
COUNT_MULTIPLE
)
private fun checkClassWithLongParameter(longClass: String, constructorCount: Int) {
val longConstructor = isConstructor(longClass, Long::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasLong>(longClass).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
assertEquals(BIG_NUMBER, it.longData())
}
kotlin.constructors.apply {
assertThat("<init>(J) not found", this, hasItem(longConstructor))
assertEquals(constructorCount, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(noArg.callBy(emptyMap()).longData()).isEqualTo(0)
assertThat(newInstance().longData()).isEqualTo(0)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLong>(longClass).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
assertEquals(BIG_NUMBER, it.longData())
}
kotlin.constructors.apply {
assertThat("<init>(J) not found", this, hasItem(longConstructor))
assertEquals(constructorCount, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(assertFailsWith<InvocationTargetException> { noArg.callBy(emptyMap()) }.targetException)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
assertThat(assertFailsWith<UnsupportedOperationException> { newInstance() })
.hasMessage("Method has been deleted")
}
}
}
@Test
fun stubOverloadedIntConstructor() = checkClassWithIntParameter(
"net.corda.gradle.HasOverloadedIntConstructorToStub",
COUNT_OVERLOADED
)
@Test
fun stubMultipleIntConstructor() = checkClassWithIntParameter(
"net.corda.gradle.HasMultipleIntConstructorsToStub",
COUNT_MULTIPLE
)
private fun checkClassWithIntParameter(intClass: String, constructorCount: Int) {
val intConstructor = isConstructor(intClass, Int::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasInt>(intClass).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
assertEquals(NUMBER, it.intData())
}
kotlin.constructors.apply {
assertThat("<init>(I) not found", this, hasItem(intConstructor))
assertEquals(constructorCount, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(noArg.callBy(emptyMap()).intData()).isEqualTo(0)
assertThat(newInstance().intData()).isEqualTo(0)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasInt>(intClass).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
assertEquals(NUMBER, it.intData())
}
kotlin.constructors.apply {
assertThat("<init>(I) not found", this, hasItem(intConstructor))
assertEquals(constructorCount, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(assertFailsWith<InvocationTargetException> { noArg.callBy(emptyMap()) }.targetException)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
assertThat(assertFailsWith<UnsupportedOperationException> { newInstance() })
.hasMessage("Method has been deleted")
}
}
}
@Test
fun stubOverloadedStringConstructor() = checkClassWithStringParameter(
"net.corda.gradle.HasOverloadedStringConstructorToStub",
COUNT_OVERLOADED
)
@Test
fun stubMultipleStringConstructor() = checkClassWithStringParameter(
"net.corda.gradle.HasMultipleStringConstructorsToStub",
COUNT_MULTIPLE
)
private fun checkClassWithStringParameter(stringClass: String, constructorCount: Int) {
val stringConstructor = isConstructor(stringClass, String::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(stringClass).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertEquals(MESSAGE, it.stringData())
}
kotlin.constructors.apply {
assertThat("<init>(String) not found", this, hasItem(stringConstructor))
assertEquals(constructorCount, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(noArg.callBy(emptyMap()).stringData()).isEqualTo(DEFAULT_MESSAGE)
assertThat(newInstance().stringData()).isEqualTo(DEFAULT_MESSAGE)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(stringClass).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertEquals(MESSAGE, it.stringData())
}
kotlin.constructors.apply {
assertThat("<init>(String) not found", this, hasItem(stringConstructor))
assertEquals(constructorCount, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(assertFailsWith<InvocationTargetException> { noArg.callBy(emptyMap()) }.targetException)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
assertThat(assertFailsWith<UnsupportedOperationException> { newInstance() })
.hasMessage("Method has been deleted")
}
}
}
@Test
fun stubOverloadedComplexConstructor() {
val complexConstructor = isConstructor(COMPLEX_CONSTRUCTOR_CLASS, Int::class, String::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(COMPLEX_CONSTRUCTOR_CLASS).apply {
kotlin.constructors.apply {
assertThat("<init>(Int,String) not found", this, hasItem(complexConstructor))
assertEquals(1, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
primary.call(NUMBER, MESSAGE).also { complex ->
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
assertThat((complex as HasInt).intData()).isEqualTo(NUMBER)
}
primary.callBy(mapOf(primary.parameters[1] to MESSAGE)).also { complex ->
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
assertThat((complex as HasInt).intData()).isEqualTo(0)
}
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { complex ->
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
assertThat((complex as HasInt).intData()).isEqualTo(0)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(COMPLEX_CONSTRUCTOR_CLASS).apply {
kotlin.constructors.apply {
assertThat("<init>(Int,String) not found", this, hasItem(complexConstructor))
assertEquals(1, this.size)
}
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
primary.call(NUMBER, MESSAGE).also { complex ->
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
assertThat((complex as HasInt).intData()).isEqualTo(NUMBER)
}
assertThat(assertFailsWith<InvocationTargetException> { primary.callBy(mapOf(primary.parameters[1] to MESSAGE)) }.targetException)
.isInstanceOf(kotlin.UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
assertThat(assertFailsWith<InvocationTargetException> { getDeclaredConstructor(String::class.java).newInstance(MESSAGE) }.targetException)
.hasMessage("Method has been deleted")
}
}
}
}

View File

@ -1,102 +0,0 @@
@file:JvmName("StaticFields")
@file:Suppress("UNUSED")
package net.corda.gradle.jarfilter
import net.corda.gradle.jarfilter.annotations.Deletable
import net.corda.gradle.jarfilter.asm.bytecode
import net.corda.gradle.jarfilter.asm.toClass
import org.gradle.api.logging.Logger
import org.junit.Assert.*
import org.junit.BeforeClass
import org.junit.Test
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import kotlin.reflect.jvm.jvmName
import kotlin.test.assertFailsWith
/**
* Static properties are all initialised in the same <clinit> block.
* Show that deleting some field references doesn't break the other
* properties' initialisation code.
*/
class StaticFieldRemovalTest {
companion object {
private val logger: Logger = StdOutLogging(StaticFieldRemovalTest::class)
private const val FIELD_CLASS = "net.corda.gradle.jarfilter.StaticFields"
private lateinit var sourceClass: Class<out Any>
private lateinit var targetClass: Class<out Any>
private fun <T : R, R : Any> transform(type: Class<in T>, asType: Class<out R>): Class<out R> {
val bytecode = type.bytecode.execute({ writer ->
FilterTransformer(
visitor = writer,
logger = logger,
removeAnnotations = emptySet(),
deleteAnnotations = setOf(Deletable::class.jvmName.descriptor),
stubAnnotations = emptySet(),
unwantedElements = UnwantedCache()
)
}, COMPUTE_MAXS)
return bytecode.toClass(type, asType)
}
@JvmStatic
@BeforeClass
fun setup() {
sourceClass = Class.forName(FIELD_CLASS)
targetClass = transform(sourceClass, Any::class.java)
}
}
@Test
fun deleteStaticString() {
assertEquals("1", sourceClass.getDeclaredMethod("getStaticString").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticString") }
}
@Test
fun deleteStaticLong() {
assertEquals(2L, sourceClass.getDeclaredMethod("getStaticLong").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticLong") }
}
@Test
fun deleteStaticInt() {
assertEquals(3, sourceClass.getDeclaredMethod("getStaticInt").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticInt") }
}
@Test
fun deleteStaticShort() {
assertEquals(4.toShort(), sourceClass.getDeclaredMethod("getStaticShort").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticShort") }
}
@Test
fun deleteStaticByte() {
assertEquals(5.toByte(), sourceClass.getDeclaredMethod("getStaticByte").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticByte") }
}
@Test
fun deleteStaticChar() {
assertEquals(6.toChar(), sourceClass.getDeclaredMethod("getStaticChar").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticChar") }
}
@Test
fun checkSeedHasBeenIncremented() {
assertEquals(6, sourceClass.getDeclaredMethod("getStaticSeed").invoke(null))
assertEquals(6, targetClass.getDeclaredMethod("getStaticSeed").invoke(null))
}
}
private var seed: Int = 0
val staticSeed get() = seed
@Deletable val staticString: String = (++seed).toString()
@Deletable val staticLong: Long = (++seed).toLong()
@Deletable val staticInt: Int = ++seed
@Deletable val staticShort: Short = (++seed).toShort()
@Deletable val staticByte: Byte = (++seed).toByte()
@Deletable val staticChar: Char = (++seed).toChar()

View File

@ -1,262 +0,0 @@
package net.corda.gradle.jarfilter
import org.gradle.api.logging.LogLevel.*
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.Logger
import org.slf4j.Marker
import org.slf4j.helpers.MessageFormatter
import kotlin.reflect.KClass
class StdOutLogging(private val name: String, private val threshold: LogLevel = INFO) : Logger {
constructor(clazz: KClass<*>) : this(clazz.java.simpleName)
override fun getName(): String = name
override fun isErrorEnabled(): Boolean = isEnabled(ERROR)
override fun isErrorEnabled(marker: Marker): Boolean = isEnabled(ERROR)
override fun isWarnEnabled(): Boolean = isEnabled(WARN)
override fun isWarnEnabled(marker: Marker): Boolean = isEnabled(WARN)
override fun isInfoEnabled(): Boolean = isEnabled(INFO)
override fun isInfoEnabled(marker: Marker): Boolean = isEnabled(INFO)
override fun isDebugEnabled(): Boolean = isEnabled(DEBUG)
override fun isDebugEnabled(marker: Marker): Boolean = isEnabled(DEBUG)
override fun isTraceEnabled(): Boolean = isEnabled(DEBUG)
override fun isTraceEnabled(marker: Marker): Boolean = isEnabled(DEBUG)
override fun isQuietEnabled(): Boolean = isEnabled(QUIET)
override fun isLifecycleEnabled(): Boolean = isEnabled(LIFECYCLE)
override fun isEnabled(level: LogLevel): Boolean = threshold <= level
override fun warn(msg: String) = log(WARN, msg)
override fun warn(msg: String, obj: Any?) = log(WARN, msg, obj)
override fun warn(msg: String, vararg objects: Any?) = log(WARN, msg, *objects)
override fun warn(msg: String, obj1: Any?, obj2: Any?) = log(WARN, msg, obj1, obj2)
override fun warn(msg: String, ex: Throwable) = log(WARN, msg, ex)
override fun warn(marker: Marker, msg: String) {
if (isWarnEnabled(marker)) {
print(WARN, msg)
}
}
override fun warn(marker: Marker, msg: String, obj: Any?) {
if (isWarnEnabled(marker)) {
print(WARN, msg, obj)
}
}
override fun warn(marker: Marker, msg: String, obj1: Any?, obj2: Any?) {
if (isWarnEnabled(marker)) {
print(WARN, msg, obj1, obj2)
}
}
override fun warn(marker: Marker, msg: String, vararg objects: Any?) {
if (isWarnEnabled(marker)) {
printAny(WARN, msg, *objects)
}
}
override fun warn(marker: Marker, msg: String, ex: Throwable) {
if (isWarnEnabled(marker)) {
print(WARN, msg, ex)
}
}
override fun info(message: String, vararg objects: Any?) = log(INFO, message, *objects)
override fun info(message: String) = log(INFO, message)
override fun info(message: String, obj: Any?) = log(INFO, message, obj)
override fun info(message: String, obj1: Any?, obj2: Any?) = log(INFO, message, obj1, obj2)
override fun info(message: String, ex: Throwable) = log(INFO, message, ex)
override fun info(marker: Marker, msg: String) {
if (isInfoEnabled(marker)) {
print(INFO, msg)
}
}
override fun info(marker: Marker, msg: String, obj: Any?) {
if (isInfoEnabled(marker)) {
print(INFO, msg, obj)
}
}
override fun info(marker: Marker, msg: String, obj1: Any?, obj2: Any?) {
if (isInfoEnabled(marker)) {
print(INFO, msg, obj1, obj2)
}
}
override fun info(marker: Marker, msg: String, vararg objects: Any?) {
if (isInfoEnabled(marker)) {
printAny(INFO, msg, *objects)
}
}
override fun info(marker: Marker, msg: String, ex: Throwable) {
if (isInfoEnabled(marker)) {
print(INFO, msg, ex)
}
}
override fun error(message: String) = log(ERROR, message)
override fun error(message: String, obj: Any?) = log(ERROR, message, obj)
override fun error(message: String, obj1: Any?, obj2: Any?) = log(ERROR, message, obj1, obj2)
override fun error(message: String, vararg objects: Any?) = log(ERROR, message, *objects)
override fun error(message: String, ex: Throwable) = log(ERROR, message, ex)
override fun error(marker: Marker, msg: String) {
if (isErrorEnabled(marker)) {
print(ERROR, msg)
}
}
override fun error(marker: Marker, msg: String, obj: Any?) {
if (isErrorEnabled(marker)) {
print(ERROR, msg, obj)
}
}
override fun error(marker: Marker, msg: String, obj1: Any?, obj2: Any?) {
if (isErrorEnabled(marker)) {
print(ERROR, msg, obj1, obj2)
}
}
override fun error(marker: Marker, msg: String, vararg objects: Any?) {
if (isErrorEnabled(marker)) {
printAny(ERROR, msg, *objects)
}
}
override fun error(marker: Marker, msg: String, ex: Throwable) {
if (isErrorEnabled(marker)) {
print(ERROR, msg, ex)
}
}
override fun log(level: LogLevel, message: String) {
if (isEnabled(level)) {
print(level, message)
}
}
override fun log(level: LogLevel, message: String, vararg objects: Any?) {
if (isEnabled(level)) {
printAny(level, message, *objects)
}
}
override fun log(level: LogLevel, message: String, ex: Throwable) {
if (isEnabled(level)) {
print(level, message, ex)
}
}
override fun debug(message: String, vararg objects: Any?) = log(DEBUG, message, *objects)
override fun debug(message: String) = log(DEBUG, message)
override fun debug(message: String, obj: Any?) = log(DEBUG, message, obj)
override fun debug(message: String, obj1: Any?, obj2: Any?) = log(DEBUG, message, obj1, obj2)
override fun debug(message: String, ex: Throwable) = log(DEBUG, message, ex)
override fun debug(marker: Marker, msg: String) {
if (isDebugEnabled(marker)) {
print(DEBUG, msg)
}
}
override fun debug(marker: Marker, msg: String, obj: Any?) {
if (isDebugEnabled(marker)) {
print(DEBUG, msg, obj)
}
}
override fun debug(marker: Marker, msg: String, obj1: Any?, obj2: Any?) {
if (isDebugEnabled(marker)) {
print(DEBUG, msg, obj1, obj2)
}
}
override fun debug(marker: Marker, msg: String, vararg objects: Any?) {
if (isDebugEnabled(marker)) {
printAny(DEBUG, msg, *objects)
}
}
override fun debug(marker: Marker, msg: String, ex: Throwable) {
if (isDebugEnabled(marker)) {
print(DEBUG, msg, ex)
}
}
override fun lifecycle(message: String) = log(LIFECYCLE, message)
override fun lifecycle(message: String, vararg objects: Any?) = log(LIFECYCLE, message, *objects)
override fun lifecycle(message: String, ex: Throwable) = log(LIFECYCLE, message, ex)
override fun quiet(message: String) = log(QUIET, message)
override fun quiet(message: String, vararg objects: Any?) = log(QUIET, message, *objects)
override fun quiet(message: String, ex: Throwable) = log(QUIET, message, ex)
override fun trace(message: String) = debug(message)
override fun trace(message: String, obj: Any?) = debug(message, obj)
override fun trace(message: String, obj1: Any?, obj2: Any?) = debug(message, obj1, obj2)
override fun trace(message: String, vararg objects: Any?) = debug(message, *objects)
override fun trace(message: String, ex: Throwable) = debug(message, ex)
override fun trace(marker: Marker, msg: String) {
if (isTraceEnabled(marker)) {
print(DEBUG, msg)
}
}
override fun trace(marker: Marker, msg: String, obj: Any?) {
if (isTraceEnabled(marker)) {
print(DEBUG, msg, obj)
}
}
override fun trace(marker: Marker, msg: String, obj1: Any?, obj2: Any?) {
if (isTraceEnabled(marker)) {
print(DEBUG, msg, obj1, obj2)
}
}
override fun trace(marker: Marker, msg: String, vararg objects: Any?) {
if (isTraceEnabled(marker)) {
printAny(DEBUG, msg, *objects)
}
}
override fun trace(marker: Marker, msg: String, ex: Throwable) {
if (isTraceEnabled) {
print(DEBUG, msg, ex)
}
}
private fun print(level: LogLevel, message: String) {
println("$name - $level: $message")
}
private fun print(level: LogLevel, message: String, ex: Throwable) {
print(level, message)
ex.printStackTrace(System.out)
}
private fun print(level: LogLevel, message: String, obj: Any?) {
print(level, MessageFormatter.format(message, obj).message)
}
private fun print(level: LogLevel, message: String, obj1: Any?, obj2: Any?) {
print(level, MessageFormatter.format(message, obj1, obj2).message)
}
private fun printAny(level: LogLevel, message: String, vararg objects: Any?) {
print(level, MessageFormatter.arrayFormat(message, objects).message)
}
}

View File

@ -1,160 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.unwanted.HasAll
import net.corda.gradle.unwanted.HasInt
import net.corda.gradle.unwanted.HasLong
import net.corda.gradle.unwanted.HasString
import org.assertj.core.api.Assertions.*
import org.junit.Assert.*
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import java.lang.reflect.InvocationTargetException
import kotlin.test.assertFailsWith
class StubConstructorTest {
companion object {
private const val STRING_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryStringConstructorToStub"
private const val LONG_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryLongConstructorToStub"
private const val INT_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryIntConstructorToStub"
private const val SECONDARY_CONSTRUCTOR_CLASS = "net.corda.gradle.HasConstructorToStub"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "stub-constructor")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun stubConstructorWithLongParameter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { obj ->
assertEquals(BIG_NUMBER, obj.longData())
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).also {
assertFailsWith<InvocationTargetException> { it.newInstance(BIG_NUMBER) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubConstructorWithStringParameter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.stringData())
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).also {
assertFailsWith<InvocationTargetException> { it.newInstance(MESSAGE) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun showUnannotatedConstructorIsUnaffected() {
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasAll>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { obj ->
assertEquals(NUMBER, obj.intData())
assertEquals(NUMBER.toLong(), obj.longData())
assertEquals("<nothing>", obj.stringData())
}
}
}
}
@Test
fun stubPrimaryConstructorWithStringParameter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.stringData())
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).also {
assertFailsWith<InvocationTargetException> { it.newInstance(MESSAGE) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubPrimaryConstructorWithLongParameter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { obj ->
assertEquals(BIG_NUMBER, obj.longData())
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).also {
assertFailsWith<InvocationTargetException> { it.newInstance(BIG_NUMBER) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubPrimaryConstructorWithIntParameter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { obj ->
assertEquals(NUMBER, obj.intData())
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Int::class.java).apply {
val error = assertFailsWith<InvocationTargetException> { newInstance(NUMBER) }.targetException
assertThat(error)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}

View File

@ -1,74 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.unwanted.HasUnwantedFun
import org.assertj.core.api.Assertions.*
import org.junit.Assert.*
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import javax.annotation.Resource
import kotlin.test.assertFailsWith
class StubFunctionOutTest {
companion object {
private const val FUNCTION_CLASS = "net.corda.gradle.HasFunctionToStub"
private const val STUB_ME_OUT_ANNOTATION = "net.corda.gradle.jarfilter.StubMeOut"
private const val PARAMETER_ANNOTATION = "net.corda.gradle.Parameter"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "stub-function")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun stubFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
val stubMeOut = cl.load<Annotation>(STUB_ME_OUT_ANNOTATION)
val parameter = cl.load<Annotation>(PARAMETER_ANNOTATION)
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
newInstance().also { obj ->
assertEquals(MESSAGE, obj.unwantedFun(MESSAGE))
}
getMethod("unwantedFun", String::class.java).also { method ->
assertTrue("StubMeOut annotation missing", method.isAnnotationPresent (stubMeOut))
assertTrue("Resource annotation missing", method.isAnnotationPresent(Resource::class.java))
method.parameterAnnotations.also { paramAnns ->
assertEquals(1, paramAnns.size)
assertThat(paramAnns[0])
.hasOnlyOneElementSatisfying { a -> a.javaClass.isInstance(parameter) }
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val stubMeOut = cl.load<Annotation>(STUB_ME_OUT_ANNOTATION)
val parameter = cl.load<Annotation>(PARAMETER_ANNOTATION)
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
newInstance().also { obj ->
assertFailsWith<UnsupportedOperationException> { obj.unwantedFun(MESSAGE) }.also { ex ->
assertEquals("Method has been deleted", ex.message)
}
}
getMethod("unwantedFun", String::class.java).also { method ->
assertFalse("StubMeOut annotation present", method.isAnnotationPresent(stubMeOut))
assertTrue("Resource annotation missing", method.isAnnotationPresent(Resource::class.java))
method.parameterAnnotations.also { paramAnns ->
assertEquals(1, paramAnns.size)
assertThat(paramAnns[0])
.hasOnlyOneElementSatisfying { a -> a.javaClass.isInstance(parameter) }
}
}
}
}
}
}

View File

@ -1,127 +0,0 @@
package net.corda.gradle.jarfilter
import org.assertj.core.api.Assertions.*
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestRule
import java.lang.reflect.InvocationTargetException
import kotlin.test.*
class StubStaticFunctionTest {
companion object {
private const val FUNCTION_CLASS = "net.corda.gradle.StaticFunctionsToStub"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "stub-static-function")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun stubStringFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedStringToStub", String::class.java).also { method ->
method.invoke(null, MESSAGE).also { result ->
assertThat(result)
.isInstanceOf(String::class.java)
.isEqualTo(MESSAGE)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedStringToStub", String::class.java).also { method ->
assertFailsWith<InvocationTargetException> { method.invoke(null, MESSAGE) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubLongFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedLongToStub", Long::class.java).also { method ->
method.invoke(null, BIG_NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Long::class.javaObjectType)
.isEqualTo(BIG_NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedLongToStub", Long::class.java).also { method ->
assertFailsWith<InvocationTargetException> { method.invoke(null, BIG_NUMBER) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubIntFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedIntToStub", Int::class.java).also { method ->
method.invoke(null, NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Int::class.javaObjectType)
.isEqualTo(NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedIntToStub", Int::class.java).also { method ->
assertFailsWith<InvocationTargetException> { method.invoke(null, NUMBER) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubVoidFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
val staticSeed = getDeclaredMethod("getStaticSeed")
assertEquals(0, staticSeed.invoke(null))
getDeclaredMethod("unwantedVoidToStub").invoke(null)
assertEquals(1, staticSeed.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
val staticSeed = getDeclaredMethod("getStaticSeed")
assertEquals(0, staticSeed.invoke(null))
getDeclaredMethod("unwantedVoidToStub").invoke(null)
assertEquals(0, staticSeed.invoke(null))
}
}
}
}

View File

@ -1,46 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.unwanted.HasUnwantedVal
import org.junit.Assert.*
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 StubValPropertyTest {
companion object {
private const val PROPERTY_CLASS = "net.corda.gradle.HasValPropertyForStub"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "stub-val-property")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteGetter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVal)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertFailsWith<UnsupportedOperationException> { obj.unwantedVal }.also { ex ->
assertEquals("Method has been deleted", ex.message)
}
}
}
}
}
}

View File

@ -1,70 +0,0 @@
package net.corda.gradle.jarfilter
import net.corda.gradle.unwanted.HasUnwantedVar
import org.junit.Assert.*
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 StubVarPropertyTest {
companion object {
private const val GETTER_CLASS = "net.corda.gradle.HasUnwantedGetForStub"
private const val SETTER_CLASS = "net.corda.gradle.HasUnwantedSetForStub"
private val testProjectDir = TemporaryFolder()
private val testProject = JarFilterProject(testProjectDir, "stub-var-property")
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
}
@Test
fun deleteGetter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVar)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertFailsWith<UnsupportedOperationException> { obj.unwantedVar }.also { ex ->
assertEquals("Method has been deleted", ex.message)
}
}
}
}
}
@Test
fun deleteSetter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
obj.unwantedVar = MESSAGE
assertEquals(MESSAGE, obj.unwantedVar)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
obj.unwantedVar = MESSAGE
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
}
}
}
}
}

View File

@ -1,54 +0,0 @@
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)
}
}

View File

@ -1,97 +0,0 @@
@file:JvmName("Utilities")
package net.corda.gradle.jarfilter
import org.junit.AssumptionViolatedException
import org.junit.rules.TemporaryFolder
import java.io.File
import java.io.IOException
import java.net.MalformedURLException
import java.net.URLClassLoader
import java.nio.file.StandardCopyOption.*
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.stream.Collectors.*
import java.util.zip.ZipFile
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.full.valueParameters
const val DEFAULT_MESSAGE = "<default-value>"
const val MESSAGE = "Goodbye, Cruel World!"
const val NUMBER = 111
const val BIG_NUMBER = 9999L
private val classLoader: ClassLoader = object {}.javaClass.classLoader
// The AssumptionViolatedException must be caught by the JUnit test runner,
// which means that it must not be thrown when this class loads.
private val testGradleUserHomeValue: String? = System.getProperty("test.gradle.user.home")
private val testGradleUserHome: String get() = testGradleUserHomeValue
?: throw AssumptionViolatedException("System property 'test.gradle.user.home' not set.")
fun getGradleArgsForTasks(vararg taskNames: String): MutableList<String> = getBasicArgsForTasks(*taskNames).apply { add("--info") }
fun getBasicArgsForTasks(vararg taskNames: String): MutableList<String> = mutableListOf(*taskNames, "--stacktrace", "-g", testGradleUserHome)
@Throws(IOException::class)
fun copyResourceTo(resourceName: String, target: Path) {
classLoader.getResourceAsStream(resourceName).use { source ->
Files.copy(source, target, REPLACE_EXISTING)
}
}
@Throws(IOException::class)
fun copyResourceTo(resourceName: String, target: File) = copyResourceTo(resourceName, target.toPath())
@Throws(IOException::class)
fun TemporaryFolder.installResources(vararg resourceNames: String) {
resourceNames.forEach { installResource(it) }
}
@Throws(IOException::class)
fun TemporaryFolder.installResource(resourceName: String): File = newFile(resourceName.fileName).let { file ->
copyResourceTo(resourceName, file)
file
}
private val String.fileName: String get() = substring(1 + lastIndexOf('/'))
val String.toPackageFormat: String get() = replace('/', '.')
fun pathsOf(vararg types: KClass<*>): Set<String> = types.map { it.java.name.toPathFormat }.toSet()
fun TemporaryFolder.pathOf(vararg elements: String): Path = Paths.get(root.absolutePath, *elements)
fun arrayOfJunk(size: Int) = ByteArray(size).apply {
for (i in 0 until size) {
this[i] = (i and 0xFF).toByte()
}
}
val KFunction<*>.hasAnyOptionalParameters: Boolean
get() = valueParameters.any(KParameter::isOptional)
val KFunction<*>.hasAllOptionalParameters: Boolean
get() = valueParameters.all(KParameter::isOptional)
val KFunction<*>.hasAllMandatoryParameters: Boolean
get() = valueParameters.none(KParameter::isOptional)
val <T : Any> KClass<T>.noArgConstructor: KFunction<T>?
get() = constructors.firstOrNull(KFunction<*>::hasAllOptionalParameters)
@Throws(MalformedURLException::class)
fun classLoaderFor(jar: Path) = URLClassLoader(arrayOf(jar.toUri().toURL()), classLoader)
@Suppress("UNCHECKED_CAST")
@Throws(ClassNotFoundException::class)
fun <T> ClassLoader.load(className: String)
= Class.forName(className, true, this) as Class<T>
fun Path.getClassNames(prefix: String): List<String> {
val resourcePrefix = prefix.toPathFormat
return ZipFile(toFile()).stream()
.filter { it.name.startsWith(resourcePrefix) && it.name.endsWith(".class") }
.map { it.name.removeSuffix(".class").toPackageFormat }
.collect(toList<String>())
}

View File

@ -1,42 +0,0 @@
package net.corda.gradle.jarfilter
import org.assertj.core.api.Assertions.assertThat
import org.gradle.api.GradleException
import org.gradle.api.InvalidUserDataException
import org.junit.Test
import java.io.IOException
import kotlin.test.assertFailsWith
class UtilsTest {
@Test
fun testRethrowingCheckedException() {
val ex = assertFailsWith<GradleException> { rethrowAsUncheckedException(IOException(MESSAGE)) }
assertThat(ex)
.hasMessage(MESSAGE)
.hasCauseExactlyInstanceOf(IOException::class.java)
}
@Test
fun testRethrowingCheckExceptionWithoutMessage() {
val ex = assertFailsWith<GradleException> { rethrowAsUncheckedException(IOException()) }
assertThat(ex)
.hasMessage("")
.hasCauseExactlyInstanceOf(IOException::class.java)
}
@Test
fun testRethrowingUncheckedException() {
val ex = assertFailsWith<IllegalArgumentException> { rethrowAsUncheckedException(IllegalArgumentException(MESSAGE)) }
assertThat(ex)
.hasMessage(MESSAGE)
.hasNoCause()
}
@Test
fun testRethrowingGradleException() {
val ex = assertFailsWith<InvalidUserDataException> { rethrowAsUncheckedException(InvalidUserDataException(MESSAGE)) }
assertThat(ex)
.hasMessage(MESSAGE)
.hasNoCause()
}
}

View File

@ -1,8 +0,0 @@
package net.corda.gradle.jarfilter.annotations
import kotlin.annotation.AnnotationRetention.BINARY
import kotlin.annotation.AnnotationTarget.PROPERTY
@Retention(BINARY)
@Target(PROPERTY)
annotation class Deletable

View File

@ -1,47 +0,0 @@
@file:JvmName("AsmTools")
package net.corda.gradle.jarfilter.asm
import net.corda.gradle.jarfilter.descriptor
import net.corda.gradle.jarfilter.toPathFormat
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import java.io.ByteArrayInputStream
import java.io.InputStream
fun ByteArray.accept(visitor: (ClassVisitor) -> ClassVisitor): ByteArray {
return ClassWriter(COMPUTE_MAXS).let { writer ->
ClassReader(this).accept(visitor(writer), 0)
writer.toByteArray()
}
}
private val String.resourceName: String get() = "$toPathFormat.class"
val Class<*>.resourceName get() = name.resourceName
val Class<*>.bytecode: ByteArray get() = classLoader.getResourceAsStream(resourceName).use { it.readBytes() }
val Class<*>.descriptor: String get() = name.descriptor
/**
* Functions for converting bytecode into a "live" Java class.
*/
inline fun <reified T: R, reified R: Any> ByteArray.toClass(): Class<out R> = toClass(T::class.java, R::class.java)
fun <T: R, R: Any> ByteArray.toClass(type: Class<in T>, asType: Class<out R>): Class<out R>
= BytecodeClassLoader(this, type.name, type.classLoader).createClass().asSubclass(asType)
private class BytecodeClassLoader(
private val bytecode: ByteArray,
private val className: String,
parent: ClassLoader
) : ClassLoader(parent) {
internal fun createClass(): Class<*> {
return defineClass(className, bytecode, 0, bytecode.size).apply { resolveClass(this) }
}
// Ensure that the class we create also honours Class<*>.bytecode (above).
override fun getResourceAsStream(name: String): InputStream? {
return if (name == className.resourceName) ByteArrayInputStream(bytecode) else super.getResourceAsStream(name)
}
}

View File

@ -1,48 +0,0 @@
package net.corda.gradle.jarfilter.asm
import net.corda.gradle.jarfilter.MetadataTransformer
import net.corda.gradle.jarfilter.getClassInternalName
import net.corda.gradle.jarfilter.toPackageFormat
import net.corda.gradle.jarfilter.mutableList
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
internal class ClassMetadata(
logger: Logger,
d1: List<String>,
d2: List<String>
) : MetadataTransformer<ProtoBuf.Class>(
logger,
emptyList(),
emptyList(),
emptyList(),
emptyList(),
emptyList(),
{},
d1,
d2,
ProtoBuf.Class::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val className = nameResolver.getClassInternalName(message.fqName)
override val nestedClassNames = mutableList(message.nestedClassNameList)
override val properties = mutableList(message.propertyList)
override val functions = mutableList(message.functionList)
override val constructors = mutableList(message.constructorList)
override val typeAliases = mutableList(message.typeAliasList)
override val sealedSubclassNames = mutableList(message.sealedSubclassFqNameList)
override fun rebuild(): ProtoBuf.Class = message
val sealedSubclasses: List<String> = sealedSubclassNames.map {
// Transform "a/b/c/BaseName$SubclassName" -> "a.b.c.BaseName$SubclassName"
nameResolver.getClassInternalName(it).toPackageFormat }.toList()
val nestedClasses: List<String>
init {
val internalClassName = className.toPackageFormat
nestedClasses = nestedClassNames.map { "$internalClassName\$${nameResolver.getClassInternalName(it)}" }.toList()
}
}

View File

@ -1,33 +0,0 @@
package net.corda.gradle.jarfilter.asm
import net.corda.gradle.jarfilter.MetadataTransformer
import net.corda.gradle.jarfilter.mutableList
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
internal class FileMetadata(
logger: Logger,
d1: List<String>,
d2: List<String>
) : MetadataTransformer<ProtoBuf.Package>(
logger,
emptyList(),
emptyList(),
emptyList(),
emptyList(),
emptyList(),
{},
d1,
d2,
ProtoBuf.Package::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val properties = mutableList(message.propertyList)
override val functions = mutableList(message.functionList)
override val typeAliases = mutableList(message.typeAliasList)
override fun rebuild(): ProtoBuf.Package = message
val typeAliasNames: List<String> = typeAliases.map { nameResolver.getString(it.name) }.toList()
}

View File

@ -1,86 +0,0 @@
@file:JvmName("MetadataTools")
package net.corda.gradle.jarfilter.asm
import net.corda.gradle.jarfilter.StdOutLogging
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.*
import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.ASM6
@Suppress("UNCHECKED_CAST")
private val metadataClass: Class<out Annotation>
= object {}.javaClass.classLoader.loadClass("kotlin.Metadata") as Class<out Annotation>
/**
* Rewrite the bytecode for this class with the Kotlin @Metadata of another class.
*/
inline fun <reified T: Any, reified X: Any> recodeMetadataFor(): ByteArray = T::class.java.metadataAs(X::class.java)
fun <T: Any, X: Any> Class<in T>.metadataAs(template: Class<in X>): ByteArray {
val metadata = template.readMetadata().let { m ->
val templateDescriptor = template.descriptor
val templatePrefix = templateDescriptor.dropLast(1) + '$'
val targetDescriptor = descriptor
val targetPrefix = targetDescriptor.dropLast(1) + '$'
Pair(m.first, m.second.map { s ->
when {
// Replace any references to the template class with the target class.
s == templateDescriptor -> targetDescriptor
s.startsWith(templatePrefix) -> targetPrefix + s.substring(templatePrefix.length)
else -> s
}
}.toList())
}
return bytecode.accept { w -> MetadataWriter(metadata, w) }
}
/**
* Kotlin reflection only supports classes atm, so use this to examine file metadata.
*/
internal val Class<*>.fileMetadata: FileMetadata get() {
val (d1, d2) = readMetadata()
return FileMetadata(StdOutLogging(kotlin), d1, d2)
}
/**
* For accessing the parts of class metadata that Kotlin reflection cannot reach.
*/
internal val Class<*>.classMetadata: ClassMetadata get() {
val (d1, d2) = readMetadata()
return ClassMetadata(StdOutLogging(kotlin), d1, d2)
}
private fun Class<*>.readMetadata(): Pair<List<String>, List<String>> {
val metadata = getAnnotation(metadataClass)
val d1 = metadataClass.getMethod(METADATA_DATA_FIELD_NAME)
val d2 = metadataClass.getMethod(METADATA_STRINGS_FIELD_NAME)
return Pair(d1.invoke(metadata).asList(), d2.invoke(metadata).asList())
}
@Suppress("UNCHECKED_CAST")
fun <T> Any.asList(): List<T> {
return (this as? Array<T>)?.toList() ?: emptyList()
}
private class MetadataWriter(metadata: Pair<List<String>, List<String>>, visitor: ClassVisitor) : ClassVisitor(ASM6, visitor) {
private val kotlinMetadata: MutableMap<String, List<String>> = mutableMapOf(
METADATA_DATA_FIELD_NAME to metadata.first,
METADATA_STRINGS_FIELD_NAME to metadata.second
)
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
val av = super.visitAnnotation(descriptor, visible) ?: return null
return if (descriptor == METADATA_DESC) KotlinMetadataWriter(av) else av
}
private inner class KotlinMetadataWriter(av: AnnotationVisitor) : AnnotationVisitor(api, av) {
override fun visitArray(name: String): AnnotationVisitor? {
val av = super.visitArray(name)
if (av != null) {
val data = kotlinMetadata.remove(name) ?: return av
data.forEach { av.visit(null, it) }
av.visitEnd()
}
return null
}
}
}

View File

@ -1,81 +0,0 @@
@file:JvmName("JavaMatchers")
package net.corda.gradle.jarfilter.matcher
import org.hamcrest.Description
import org.hamcrest.DiagnosingMatcher
import org.hamcrest.Matcher
import org.hamcrest.core.IsEqual.*
import java.lang.reflect.Method
import kotlin.reflect.KClass
fun isMethod(name: Matcher<in String>, returnType: Matcher<in Class<*>>, vararg parameters: Matcher<in Class<*>>): Matcher<Method> {
return MethodMatcher(name, returnType, *parameters)
}
fun isMethod(name: String, returnType: Class<*>, vararg parameters: Class<*>): Matcher<Method> {
return isMethod(equalTo(name), equalTo(returnType), *parameters.toMatchers())
}
private fun Array<out Class<*>>.toMatchers() = map(::equalTo).toTypedArray()
val KClass<*>.javaDeclaredMethods: List<Method> get() = java.declaredMethods.toList()
/**
* Matcher logic for a Java [Method] object. Also applicable to constructors.
*/
private class MethodMatcher(
private val name: Matcher<in String>,
private val returnType: Matcher<in Class<*>>,
vararg parameters: Matcher<in Class<*>>
) : DiagnosingMatcher<Method>() {
private val parameters = listOf(*parameters)
override fun describeTo(description: Description) {
description.appendText("Method[name as ").appendDescriptionOf(name)
.appendText(", returnType as ").appendDescriptionOf(returnType)
.appendText(", parameters as '")
if (parameters.isNotEmpty()) {
val param = parameters.iterator()
description.appendValue(param.next())
while (param.hasNext()) {
description.appendText(",").appendValue(param.next())
}
}
description.appendText("']")
}
override fun matches(obj: Any?, mismatch: Description): Boolean {
if (obj == null) {
mismatch.appendText("is null")
return false
}
val method: Method = obj as? Method ?: return false
if (!name.matches(method.name)) {
mismatch.appendText("name is ").appendValue(method.name)
return false
}
method.returnType.apply {
if (!returnType.matches(this)) {
mismatch.appendText("returnType is ").appendValue(this.name)
return false
}
}
if (method.parameterTypes.size != parameters.size) {
mismatch.appendText("number of parameters is ").appendValue(method.parameterTypes.size)
.appendText(", parameters=").appendValueList("[", ",", "]", method.parameterTypes)
return false
}
var i = 0
method.parameterTypes.forEach { param ->
if (!parameters[i].matches(param)) {
mismatch.appendText("parameter[").appendValue(i).appendText("] is ").appendValue(param)
return false
}
++i
}
return true
}
}

View File

@ -1,195 +0,0 @@
@file:JvmName("KotlinMatchers")
package net.corda.gradle.jarfilter.matcher
import org.hamcrest.Description
import org.hamcrest.DiagnosingMatcher
import org.hamcrest.Matcher
import org.hamcrest.core.IsEqual.equalTo
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty
import kotlin.reflect.full.valueParameters
import kotlin.reflect.jvm.jvmName
fun isFunction(name: Matcher<in String>, returnType: Matcher<in String>, vararg parameters: Matcher<in KParameter>): Matcher<KFunction<*>> {
return KFunctionMatcher(name, returnType, *parameters)
}
fun isFunction(name: String, returnType: KClass<*>, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
return isFunction(equalTo(name), matches(returnType), *parameters.toMatchers())
}
fun isConstructor(returnType: Matcher<in String>, vararg parameters: Matcher<in KParameter>): Matcher<KFunction<*>> {
return KFunctionMatcher(equalTo("<init>"), returnType, *parameters)
}
fun isConstructor(returnType: KClass<*>, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
return isConstructor(matches(returnType), *parameters.toMatchers())
}
fun isConstructor(returnType: String, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
return isConstructor(equalTo(returnType), *parameters.toMatchers())
}
fun hasParam(type: Matcher<in String>): Matcher<KParameter> = KParameterMatcher(type)
fun hasParam(type: KClass<*>): Matcher<KParameter> = hasParam(matches(type))
fun isProperty(name: String, type: KClass<*>): Matcher<KProperty<*>> = isProperty(equalTo(name), matches(type))
fun isProperty(name: Matcher<in String>, type: Matcher<in String>): Matcher<KProperty<*>> = KPropertyMatcher(name, type)
fun isClass(name: String): Matcher<KClass<*>> = KClassMatcher(equalTo(name))
fun matches(type: KClass<*>): Matcher<in String> = equalTo(type.qualifiedName)
private fun Array<out KClass<*>>.toMatchers() = map(::hasParam).toTypedArray()
/**
* Matcher logic for a Kotlin [KFunction] object. Also applicable to constructors.
*/
private class KFunctionMatcher(
private val name: Matcher<in String>,
private val returnType: Matcher<in String>,
vararg parameters: Matcher<in KParameter>
) : DiagnosingMatcher<KFunction<*>>() {
private val parameters = listOf(*parameters)
override fun describeTo(description: Description) {
description.appendText("KFunction[name as ").appendDescriptionOf(name)
.appendText(", returnType as ").appendDescriptionOf(returnType)
.appendText(", parameters as '")
if (parameters.isNotEmpty()) {
val param = parameters.iterator()
description.appendValue(param.next())
while (param.hasNext()) {
description.appendText(",").appendValue(param.next())
}
}
description.appendText("']")
}
override fun matches(obj: Any?, mismatch: Description): Boolean {
if (obj == null) {
mismatch.appendText("is null")
return false
}
val function: KFunction<*> = obj as? KFunction<*> ?: return false
if (!name.matches(function.name)) {
mismatch.appendText("name is ").appendValue(function.name)
return false
}
function.returnType.toString().apply {
if (!returnType.matches(this)) {
mismatch.appendText("returnType is ").appendValue(this)
return false
}
}
if (function.valueParameters.size != parameters.size) {
mismatch.appendText("number of parameters is ").appendValue(function.valueParameters.size)
.appendText(", parameters=").appendValueList("[", ",", "]", function.valueParameters)
return false
}
var i = 0
function.valueParameters.forEach { param ->
if (!parameters[i].matches(param)) {
mismatch.appendText("parameter[").appendValue(i).appendText("] is ").appendValue(param)
return false
}
++i
}
return true
}
}
/**
* Matcher logic for a Kotlin [KParameter] object.
*/
private class KParameterMatcher(
private val type: Matcher<in String>
) : DiagnosingMatcher<KParameter>() {
override fun describeTo(description: Description) {
description.appendText("KParameter[type as ").appendDescriptionOf(type)
.appendText("]")
}
override fun matches(obj: Any?, mismatch: Description): Boolean {
if (obj == null) {
mismatch.appendText("is null")
return false
}
val parameter: KParameter = obj as? KParameter ?: return false
parameter.type.toString().apply {
if (!type.matches(this)) {
mismatch.appendText("type is ").appendValue(this)
return false
}
}
return true
}
}
/**
* Matcher logic for a Kotlin [KProperty] object.
*/
private class KPropertyMatcher(
private val name: Matcher<in String>,
private val type: Matcher<in String>
) : DiagnosingMatcher<KProperty<*>>() {
override fun describeTo(description: Description) {
description.appendText("KProperty[name as ").appendDescriptionOf(name)
.appendText(", type as ").appendDescriptionOf(type)
.appendText("]")
}
override fun matches(obj: Any?, mismatch: Description): Boolean {
if (obj == null) {
mismatch.appendText("is null")
return false
}
val property: KProperty<*> = obj as? KProperty<*> ?: return false
if (!name.matches(property.name)) {
mismatch.appendText("name is ").appendValue(property.name)
return false
}
property.returnType.toString().apply {
if (!type.matches(this)) {
mismatch.appendText("type is ").appendValue(this)
return false
}
}
return true
}
}
/**
* Matcher logic for a Kotlin [KClass] object.
*/
private class KClassMatcher(private val className: Matcher<in String>) : DiagnosingMatcher<KClass<*>>() {
override fun describeTo(description: Description) {
description.appendText("KClass[name as ").appendDescriptionOf(className)
.appendText("]")
}
override fun matches(obj: Any?, mismatch: Description): Boolean {
if (obj == null) {
mismatch.appendText("is null")
return false
}
val type: KClass<*> = obj as? KClass<*> ?: return false
type.jvmName.apply {
if (!className.matches(this)) {
mismatch.appendText("name is ").appendValue(this)
return false
}
}
return true
}
}

View File

@ -1,9 +0,0 @@
@file:JvmName("PackageWithDefaultParameters")
@file:Suppress("UNUSED")
package net.corda.gradle.jarfilter.template
import net.corda.gradle.jarfilter.DEFAULT_MESSAGE
fun hasDefaultParameters(intData: Int=0, message: String=DEFAULT_MESSAGE): String = "$message: intData=$intData"
fun hasMandatoryParameters(longData: Long=0, message: String=DEFAULT_MESSAGE): String = "$message: longData=$longData"

View File

@ -1,34 +0,0 @@
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/abstract-function/kotlin',
'../resources/test/annotations/kotlin'
)
}
}
}
dependencies {
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
}
jar {
baseName = 'abstract-function'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars jar
annotations {
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
forStub = ["net.corda.gradle.jarfilter.StubMeOut"]
}
}

View File

@ -1,13 +0,0 @@
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
import net.corda.gradle.jarfilter.StubMeOut
abstract class AbstractFunctions {
@DeleteMe
abstract fun toDelete(value: Long): Long
@StubMeOut
abstract fun toStubOut(value: Long): Long
}

View File

@ -1,20 +0,0 @@
package net.corda.gradle.jarfilter
import kotlin.annotation.AnnotationRetention.*
import kotlin.annotation.AnnotationTarget.*
import kotlin.annotation.Retention
import kotlin.annotation.Target
@Target(
FILE,
CLASS,
CONSTRUCTOR,
FUNCTION,
PROPERTY,
PROPERTY_GETTER,
PROPERTY_SETTER,
FIELD,
TYPEALIAS
)
@Retention(BINARY)
annotation class DeleteMe

View File

@ -1,19 +0,0 @@
package net.corda.gradle.jarfilter
import kotlin.annotation.AnnotationRetention.*
import kotlin.annotation.AnnotationTarget.*
import kotlin.annotation.Retention
import kotlin.annotation.Target
@Target(
FILE,
CLASS,
CONSTRUCTOR,
FUNCTION,
PROPERTY,
PROPERTY_GETTER,
PROPERTY_SETTER,
FIELD
)
@Retention(RUNTIME)
annotation class RemoveMe

View File

@ -1,15 +0,0 @@
package net.corda.gradle.jarfilter
import kotlin.annotation.AnnotationRetention.*
import kotlin.annotation.AnnotationTarget.*
import kotlin.annotation.Retention
import kotlin.annotation.Target
@Target(
CONSTRUCTOR,
FUNCTION,
PROPERTY_GETTER,
PROPERTY_SETTER
)
@Retention(RUNTIME)
annotation class StubMeOut

View File

@ -1,34 +0,0 @@
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-and-stub/kotlin',
'../resources/test/annotations/kotlin'
)
}
}
}
dependencies {
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
}
jar {
baseName = 'delete-and-stub'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars jar
annotations {
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
forStub = ["net.corda.gradle.jarfilter.StubMeOut"]
}
}

View File

@ -1,12 +0,0 @@
@file:JvmName("DeletePackageWithStubbed")
@file:Suppress("UNUSED")
@file:DeleteMe
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
import net.corda.gradle.jarfilter.StubMeOut
fun bracket(str: String): String = "[$str]"
@StubMeOut
fun stubbed(str: String): String = bracket(str)

View File

@ -1,28 +0,0 @@
@file:JvmName("HasDeletedInsideStubbed")
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
import net.corda.gradle.jarfilter.StubMeOut
import net.corda.gradle.unwanted.HasString
import net.corda.gradle.unwanted.HasUnwantedFun
import net.corda.gradle.unwanted.HasUnwantedVal
import net.corda.gradle.unwanted.HasUnwantedVar
class DeletedFunctionInsideStubbed(private val data: String): HasString, HasUnwantedFun {
@DeleteMe
override fun unwantedFun(str: String): String = str
@StubMeOut
override fun stringData(): String = unwantedFun(data)
}
class DeletedValInsideStubbed(@DeleteMe override val unwantedVal: String): HasString, HasUnwantedVal {
@StubMeOut
override fun stringData(): String = unwantedVal
}
class DeletedVarInsideStubbed(@DeleteMe override var unwantedVar: String) : HasString, HasUnwantedVar {
@StubMeOut
override fun stringData(): String = unwantedVar
}

View File

@ -1,21 +0,0 @@
@file:JvmName("HasPropertyForDeleteAndStub")
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
import net.corda.gradle.jarfilter.StubMeOut
import net.corda.gradle.unwanted.*
class HasVarPropertyForDeleteAndStub(value: Long) : HasLongVar {
@DeleteMe
@get:StubMeOut
@set:StubMeOut
override var longVar: Long = value
}
class HasValPropertyForDeleteAndStub(str: String) : HasStringVal {
@DeleteMe
@get:StubMeOut
override val stringVal: String = str
}

View File

@ -1,33 +0,0 @@
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-constructor/kotlin',
'../resources/test/annotations/kotlin'
)
}
}
}
dependencies {
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
}
jar {
baseName = 'delete-constructor'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars jar
annotations {
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
}
}

View File

@ -1,15 +0,0 @@
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
import net.corda.gradle.unwanted.HasAll
class HasConstructorToDelete(private val message: String, private val data: Long) : HasAll {
@DeleteMe constructor(message: String) : this(message, 0)
@DeleteMe constructor(data: Long) : this("<nothing>", data)
constructor(data: Int) : this("<nothing>", data.toLong())
override fun stringData(): String = message
override fun longData(): Long = data
override fun intData(): Int = data.toInt()
}

View File

@ -1,21 +0,0 @@
@file:JvmName("PrimaryConstructorsToDelete")
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
import net.corda.gradle.unwanted.HasInt
import net.corda.gradle.unwanted.HasLong
import net.corda.gradle.unwanted.HasString
class PrimaryIntConstructorToDelete @DeleteMe constructor(private val value: Int) : HasInt {
override fun intData() = value
}
class PrimaryLongConstructorToDelete @DeleteMe constructor(private val value: Long) : HasLong {
override fun longData() = value
}
class PrimaryStringConstructorToDelete @DeleteMe constructor(private val value: String) : HasString {
override fun stringData() = value
}

View File

@ -1,33 +0,0 @@
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-extension-val/kotlin',
'../resources/test/annotations/kotlin'
)
}
}
}
dependencies {
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
}
jar {
baseName = 'delete-extension-val'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars jar
annotations {
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
}
}

View File

@ -1,10 +0,0 @@
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
import net.corda.gradle.unwanted.HasUnwantedVal
class HasValExtension(override val unwantedVal: String) : HasUnwantedVal {
@DeleteMe
val List<String>.unwantedVal: String get() = this[0]
}

View File

@ -1,32 +0,0 @@
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-field/kotlin',
'../resources/test/annotations/kotlin'
)
}
}
}
dependencies {
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
}
jar {
baseName = 'delete-field'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars jar
annotations {
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
}
}

View File

@ -1,23 +0,0 @@
@file:JvmName("HasFieldToDelete")
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
class HasStringFieldToDelete(value: String) {
@JvmField
@field:DeleteMe
val stringField: String = value
}
class HasLongFieldToDelete(value: Long) {
@JvmField
@field:DeleteMe
val longField: Long = value
}
class HasIntFieldToDelete(value: Int) {
@JvmField
@field:DeleteMe
val intField: Int = value
}

View File

@ -1,32 +0,0 @@
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-file-typealias/kotlin',
'../resources/test/annotations/kotlin'
)
}
}
}
dependencies {
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
}
jar {
baseName = 'delete-file-typealias'
}
import net.corda.gradle.jarfilter.JarFilterTask
task jarFilter(type: JarFilterTask) {
jars jar
annotations {
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
}
}

View File

@ -1,12 +0,0 @@
@file:JvmName("FileWithTypeAlias")
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
typealias FileWantedType = Long
@DeleteMe
typealias FileUnwantedType = (String) -> Boolean
val Any.FileUnwantedType: String get() = "<value>"

Some files were not shown because too many files have changed in this diff Show More