CORDA-1699: Remove parameter defaults from Kotlin constructor metadata (#3542)

* Remove unwanted JavaScript metadata files from kotlin-metadata.
* Rename SanitiseConstructor case to SanitiseDeleteConstructor.
* Also remove default parameter flags from primary constructor when deleting the synthetic constructor that handles them.
Add a test case for stubbing constructors with default parameters.
* Restore deterministic modules' dependency on assemble task.
This commit is contained in:
Chris Rankin 2018-07-10 18:11:28 +01:00 committed by GitHub
parent f4426ef172
commit da32a7e56f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 520 additions and 67 deletions

View File

@ -26,7 +26,7 @@ def originalJar = configurations.proguard.files.find { it.name.startsWith("kotli
import proguard.gradle.ProGuardTask
task metadata(type: ProGuardTask) {
injars originalJar, filter: '!META-INF/native/**'
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"
@ -49,7 +49,10 @@ task metadata(type: ProGuardTask) {
dontnote
keep 'class org.jetbrains.kotlin.load.java.JvmAnnotationNames { *; }'
keep 'class org.jetbrains.kotlin.metadata.** { *; }', includedescriptorclasses: true
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

View File

@ -2,16 +2,21 @@
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 DUMMY_PASSES = 1
private val DECLARES_DEFAULT_VALUE_MASK: Int = DECLARES_DEFAULT_VALUE.toFlags(true).inv()
internal abstract class Element(val name: String, val descriptor: String) {
private var lifetime: Int = DUMMY_PASSES
@ -36,6 +41,7 @@ internal class MethodElement(name: String, descriptor: String, val access: Int =
private val suffix: String
val visibleName: String
val signature: String = name + descriptor
init {
val idx = name.indexOf('$')
@ -44,6 +50,14 @@ internal class MethodElement(name: String, descriptor: String, val access: Int =
}
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
}
}
}
@ -107,4 +121,24 @@ internal fun JvmProtoBuf.JvmPropertySignature.toFieldElement(property: ProtoBuf.
}
return FieldElement(nameResolver.getString(nameId), descriptor)
}
}
/**
* Rewrites metadata for 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.ValueParameter.clearDeclaresDefaultValue(): ProtoBuf.ValueParameter {
return if (DECLARES_DEFAULT_VALUE.get(flags)) {
toBuilder().setFlags(flags and DECLARES_DEFAULT_VALUE_MASK).build()
} else {
this
}
}

View File

@ -248,7 +248,7 @@ class FilterTransformer private constructor (
logger.info("-- also identified property or typealias {},{} for deletion", method.visibleName, extensionType)
}
}
} else if (stubAnnotations.contains(descriptor) && (method.access and (ACC_ABSTRACT or ACC_SYNTHETIC)) == 0) {
} 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)
}

View File

@ -95,17 +95,30 @@ internal abstract class MetadataTransformer<out T : MessageLite>(
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.name + deleted.descriptor) {
if (signature == deleted.signature) {
if (IS_SECONDARY.get(constructor.flags)) {
logger.info("-- removing constructor: {}{}", deleted.name, deleted.descriptor)
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
@ -118,8 +131,8 @@ internal abstract class MetadataTransformer<out T : MessageLite>(
val function = functions[idx]
if (nameResolver.getString(function.name) == deleted.name) {
val signature = JvmProtoBufUtil.getJvmMethodSignature(function, nameResolver, typeTable)
if (signature == deleted.name + deleted.descriptor) {
logger.info("-- removing function: {}{}", deleted.name, deleted.descriptor)
if (signature == deleted.signature) {
logger.info("-- removing function: {}", deleted.signature)
functions.removeAt(idx)
return true
}
@ -157,7 +170,7 @@ internal abstract class MetadataTransformer<out T : MessageLite>(
private fun deleteExtra(func: MethodElement) {
if (!deletedFunctions.contains(func)) {
logger.info("-- identified extra method {}{} for deletion", func.name, func.descriptor)
logger.info("-- identified extra method {} for deletion", func.signature)
handleExtraMethod(func)
filterFunction(func)
}
@ -244,12 +257,11 @@ internal class ClassMetadataTransformer(
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 (constructors.size != constructorCount) {
clearConstructor().addAllConstructor(constructors)
}
if (functions.size != functionCount) {
clearFunction().addAllFunction(functions)
}

View File

@ -16,12 +16,13 @@ import kotlin.jvm.kotlin
import kotlin.reflect.full.primaryConstructor
import kotlin.test.assertFailsWith
class SanitiseConstructorTest {
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-constructor")
private val testProject = JarFilterProject(testProjectDir, "sanitise-delete-constructor")
@ClassRule
@JvmField
@ -32,13 +33,13 @@ class SanitiseConstructorTest {
@Test
fun deleteOverloadedLongConstructor() = checkClassWithLongParameter(
"net.corda.gradle.HasOverloadedLongConstructor",
"net.corda.gradle.HasOverloadedLongConstructorToDelete",
COUNT_INITIAL_OVERLOADED
)
@Test
fun deleteMultipleLongConstructor() = checkClassWithLongParameter(
"net.corda.gradle.HasMultipleLongConstructors",
"net.corda.gradle.HasMultipleLongConstructorsToDelete",
COUNT_INITIAL_MULTIPLE
)
@ -57,9 +58,9 @@ class SanitiseConstructorTest {
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
newInstance().also {
assertEquals(0, it.longData())
}
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(noArg.callBy(emptyMap()).longData()).isEqualTo(0)
assertThat(newInstance().longData()).isEqualTo(0)
}
}
@ -75,6 +76,7 @@ class SanitiseConstructorTest {
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() }
}
}
@ -82,13 +84,13 @@ class SanitiseConstructorTest {
@Test
fun deleteOverloadedIntConstructor() = checkClassWithIntParameter(
"net.corda.gradle.HasOverloadedIntConstructor",
"net.corda.gradle.HasOverloadedIntConstructorToDelete",
COUNT_INITIAL_OVERLOADED
)
@Test
fun deleteMultipleIntConstructor() = checkClassWithIntParameter(
"net.corda.gradle.HasMultipleIntConstructors",
"net.corda.gradle.HasMultipleIntConstructorsToDelete",
COUNT_INITIAL_MULTIPLE
)
@ -107,10 +109,9 @@ class SanitiseConstructorTest {
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
//assertThat("", constructors, hasItem(isConstructor(""))
newInstance().also {
assertEquals(0, it.intData())
}
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(noArg.callBy(emptyMap()).intData()).isEqualTo(0)
assertThat(newInstance().intData()).isEqualTo(0)
}
}
@ -126,6 +127,7 @@ class SanitiseConstructorTest {
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() }
}
}
@ -133,13 +135,13 @@ class SanitiseConstructorTest {
@Test
fun deleteOverloadedStringConstructor() = checkClassWithStringParameter(
"net.corda.gradle.HasOverloadedStringConstructor",
"net.corda.gradle.HasOverloadedStringConstructorToDelete",
COUNT_INITIAL_OVERLOADED
)
@Test
fun deleteMultipleStringConstructor() = checkClassWithStringParameter(
"net.corda.gradle.HasMultipleStringConstructors",
"net.corda.gradle.HasMultipleStringConstructorsToDelete",
COUNT_INITIAL_MULTIPLE
)
@ -158,9 +160,9 @@ class SanitiseConstructorTest {
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
newInstance().also {
assertEquals(DEFAULT_MESSAGE, it.stringData())
}
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
assertThat(noArg.callBy(emptyMap()).stringData()).isEqualTo(DEFAULT_MESSAGE)
assertThat(newInstance().stringData()).isEqualTo(DEFAULT_MESSAGE)
}
}
@ -176,8 +178,57 @@ class SanitiseConstructorTest {
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

@ -0,0 +1,250 @@
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

@ -14,6 +14,8 @@ 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
const val DEFAULT_MESSAGE = "<default-value>"
const val MESSAGE = "Goodbye, Cruel World!"
@ -65,6 +67,9 @@ fun arrayOfJunk(size: Int) = ByteArray(size).apply {
}
}
val <T : Any> KClass<T>.noArgConstructor: KFunction<T>?
get() = constructors.firstOrNull { it.parameters.all(KParameter::isOptional) }
@Throws(MalformedURLException::class)
fun classLoaderFor(jar: Path) = URLClassLoader(arrayOf(jar.toUri().toURL()), classLoader)

View File

@ -1,34 +0,0 @@
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
import net.corda.gradle.unwanted.*
private const val DEFAULT_MESSAGE = "<default-value>"
class HasOverloadedStringConstructor @JvmOverloads @DeleteMe constructor(private val message: String = DEFAULT_MESSAGE) : HasString {
override fun stringData(): String = message
}
class HasOverloadedLongConstructor @JvmOverloads @DeleteMe constructor(private val data: Long = 0) : HasLong {
override fun longData(): Long = data
}
class HasOverloadedIntConstructor @JvmOverloads @DeleteMe constructor(private val data: Int = 0) : HasInt {
override fun intData(): Int = data
}
class HasMultipleStringConstructors(private val message: String) : HasString {
@DeleteMe constructor() : this(DEFAULT_MESSAGE)
override fun stringData(): String = message
}
class HasMultipleLongConstructors(private val data: Long) : HasLong {
@DeleteMe constructor() : this(0)
override fun longData(): Long = data
}
class HasMultipleIntConstructors(private val data: Int) : HasInt {
@DeleteMe constructor() : this(0)
override fun intData(): Int = data
}

View File

@ -8,7 +8,7 @@ sourceSets {
main {
kotlin {
srcDir files(
'../resources/test/sanitise-constructor/kotlin',
'../resources/test/sanitise-delete-constructor/kotlin',
'../resources/test/annotations/kotlin'
)
}
@ -21,7 +21,7 @@ dependencies {
}
jar {
baseName = 'sanitise-constructor'
baseName = 'sanitise-delete-constructor'
}
import net.corda.gradle.jarfilter.JarFilterTask

View File

@ -0,0 +1,48 @@
@file:JvmName("HasOverloadedConstructorsToDelete")
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
import net.corda.gradle.unwanted.*
private const val DEFAULT_MESSAGE = "<default-value>"
class HasOverloadedStringConstructorToDelete @JvmOverloads @DeleteMe constructor(private val message: String = DEFAULT_MESSAGE) : HasString {
override fun stringData(): String = message
}
class HasOverloadedLongConstructorToDelete @JvmOverloads @DeleteMe constructor(private val data: Long = 0) : HasLong {
override fun longData(): Long = data
}
class HasOverloadedIntConstructorToDelete @JvmOverloads @DeleteMe constructor(private val data: Int = 0) : HasInt {
override fun intData(): Int = data
}
/**
* This case is complex because:
* - The primary constructor has two parameters.
* - The first constructor parameter has a default value.
* - The second constructor parameter is mandatory.
*/
class HasOverloadedComplexConstructorToDelete @JvmOverloads @DeleteMe constructor(private val data: Int = 0, private val message: String)
: HasInt, HasString
{
override fun stringData(): String = message
override fun intData(): Int = data
}
class HasMultipleStringConstructorsToDelete(private val message: String) : HasString {
@DeleteMe constructor() : this(DEFAULT_MESSAGE)
override fun stringData(): String = message
}
class HasMultipleLongConstructorsToDelete(private val data: Long) : HasLong {
@DeleteMe constructor() : this(0)
override fun longData(): Long = data
}
class HasMultipleIntConstructorsToDelete(private val data: Int) : HasInt {
@DeleteMe constructor() : this(0)
override fun intData(): Int = data
}

View File

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

View File

@ -0,0 +1,48 @@
@file:JvmName("HasOverloadedConstructorsToStub")
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.StubMeOut
import net.corda.gradle.unwanted.*
private const val DEFAULT_MESSAGE = "<default-value>"
class HasOverloadedStringConstructorToStub @JvmOverloads @StubMeOut constructor(private val message: String = DEFAULT_MESSAGE) : HasString {
override fun stringData(): String = message
}
class HasOverloadedLongConstructorToStub @JvmOverloads @StubMeOut constructor(private val data: Long = 0) : HasLong {
override fun longData(): Long = data
}
class HasOverloadedIntConstructorToStub @JvmOverloads @StubMeOut constructor(private val data: Int = 0) : HasInt {
override fun intData(): Int = data
}
/**
* This case is complex because:
* - The primary constructor has two parameters.
* - The first constructor parameter has a default value.
* - The second constructor parameter is mandatory.
*/
class HasOverloadedComplexConstructorToStub @JvmOverloads @StubMeOut constructor(private val data: Int = 0, private val message: String)
: HasInt, HasString
{
override fun stringData(): String = message
override fun intData(): Int = data
}
class HasMultipleStringConstructorsToStub(private val message: String) : HasString {
@StubMeOut constructor() : this(DEFAULT_MESSAGE)
override fun stringData(): String = message
}
class HasMultipleLongConstructorsToStub(private val data: Long) : HasLong {
@StubMeOut constructor() : this(0)
override fun longData(): Long = data
}
class HasMultipleIntConstructorsToStub(private val data: Int) : HasInt {
@StubMeOut constructor() : this(0)
override fun intData(): Int = data
}

View File

@ -172,6 +172,7 @@ task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) {
defaultTasks "determinise"
determinise.finalizedBy metafix
metafix.finalizedBy checkDeterminism
assemble.dependsOn checkDeterminism
def deterministicJar = metafix.outputs.files.singleFile
artifacts {

View File

@ -157,6 +157,7 @@ task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) {
defaultTasks "determinise"
determinise.finalizedBy metafix
metafix.finalizedBy checkDeterminism
assemble.dependsOn checkDeterminism
def deterministicJar = metafix.outputs.files.singleFile
artifacts {