CORDA-1748: Delete unwanted default parameter values from Kotlin metadata. (#3556)

* Delete default parameter values from Kotlin metadata when their supporting synthetic functions disappear.
* Fix conversion of class name to class descriptor.
This commit is contained in:
Chris Rankin 2018-07-16 12:02:19 +01:00 committed by GitHub
parent 9f905da036
commit 7e220d317d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 413 additions and 81 deletions

View File

@ -13,6 +13,7 @@ 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()
@ -79,6 +80,30 @@ internal class FieldElement(name: String, descriptor: String = "?", val extensio
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.
*/
@ -124,7 +149,7 @@ internal fun JvmProtoBuf.JvmPropertySignature.toFieldElement(property: ProtoBuf.
}
/**
* Rewrites metadata for constructor parameters.
* Rewrites metadata for function and constructor parameters.
*/
internal fun ProtoBuf.Constructor.Builder.updateValueParameters(
updater: (ProtoBuf.ValueParameter) -> ProtoBuf.ValueParameter
@ -135,6 +160,15 @@ internal fun ProtoBuf.Constructor.Builder.updateValueParameters(
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()
@ -142,3 +176,6 @@ internal fun ProtoBuf.ValueParameter.clearDeclaresDefaultValue(): ProtoBuf.Value
this
}
}
internal val List<ProtoBuf.ValueParameter>.hasAnyDefaultValues
get() = any { DECLARES_DEFAULT_VALUE.get(it.flags) }

View File

@ -34,10 +34,11 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
parser: (InputStream, ExtensionRegistryLite) -> T
) {
private val stringTableTypes: StringTableTypes
private val nameResolver: NameResolver
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>
@ -78,7 +79,7 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
var count = 0
var idx = 0
while (idx < sealedSubclassNames.size) {
val sealedSubclassName = nameResolver.getString(sealedSubclassNames[idx]).replace('.', '$')
val sealedSubclassName = nameResolver.getClassInternalName(sealedSubclassNames[idx])
if (actualClasses.contains(sealedSubclassName)) {
++idx
} else {
@ -93,15 +94,25 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
private fun filterFunctions(): Int {
var count = 0
var idx = 0
while (idx < functions.size) {
val signature = JvmProtoBufUtil.getJvmMethodSignature(functions[idx], nameResolver, typeTable)
if ((signature == null) || actualMethods.contains(signature)) {
++idx
} else {
logger.info("-- removing method: {}", signature)
functions.removeAt(idx)
++count
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
}
@ -109,15 +120,25 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
private fun filterConstructors(): Int {
var count = 0
var idx = 0
while (idx < constructors.size) {
val signature = JvmProtoBufUtil.getJvmConstructorSignature(constructors[idx], nameResolver, typeTable)
if ((signature == null) || actualMethods.contains(signature)) {
++idx
} else {
logger.info("-- removing constructor: {}", signature)
constructors.removeAt(idx)
++count
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
}
@ -127,30 +148,32 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
var idx = 0
removed@ while (idx < properties.size) {
val property = properties[idx]
val signature = property.getExtensionOrNull(propertySignature) ?: continue
val field = signature.toFieldElement(property, nameResolver, typeTable)
val getterMethod = signature.toGetter(nameResolver)
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.name + getterMethod.descriptor)
}
/**
* 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
if (!isValidProperty) {
logger.info("-- removing property: {},{}", field.name, field.descriptor)
properties.removeAt(idx)
++count
continue@removed
}
}
++idx
}
@ -196,6 +219,7 @@ internal class ClassMetaFixerTransformer(
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)
@ -204,18 +228,15 @@ internal class ClassMetaFixerTransformer(
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 (constructors.size != constructorCount) {
clearConstructor().addAllConstructor(constructors)
}
if (functions.size != functionCount) {
clearFunction().addAllFunction(functions)
}
if (properties.size != propertyCount) {
clearProperty().addAllProperty(properties)
}
@ -248,9 +269,8 @@ internal class PackageMetaFixerTransformer(
override val constructors = mutableListOf<ProtoBuf.Constructor>()
override fun rebuild(): ProtoBuf.Package = message.toBuilder().apply {
if (functions.size != functionCount) {
clearFunction().addAllFunction(functions)
}
clearFunction().addAllFunction(functions)
if (properties.size != propertyCount) {
clearProperty().addAllProperty(properties)
}

View File

@ -200,7 +200,7 @@ internal abstract class MetadataTransformer<out T : MessageLite>(
var count = 0
var idx = 0
while (idx < sealedSubclassNames.size) {
val subclassName = nameResolver.getString(sealedSubclassNames[idx]).replace('.', '$')
val subclassName = nameResolver.getClassInternalName(sealedSubclassNames[idx])
if (deletedClasses.contains(subclassName)) {
logger.info("-- removing sealed subclass: {}", subclassName)
sealedSubclassNames.removeAt(idx)
@ -248,7 +248,7 @@ internal class ClassMetadataTransformer(
ProtoBuf.Class::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val className = nameResolver.getString(message.fqName)
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)

View File

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

@ -13,14 +13,8 @@ import kotlin.jvm.kotlin
class MetaFixConstructorTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixConstructorTest::class)
private val unwantedCon = isConstructor(
returnType = WithConstructor::class,
parameters = *arrayOf(Int::class, Long::class)
)
private val wantedCon = isConstructor(
returnType = WithConstructor::class,
parameters = *arrayOf(Long::class)
)
private val unwantedCon = isConstructor(WithConstructor::class, Int::class, Long::class)
private val wantedCon = isConstructor(WithConstructor::class, Long::class)
}
@Test
@ -32,15 +26,19 @@ class MetaFixConstructorTest {
// 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())
assertThat("<init>(Int,Long) not found", sourceClass.kotlin.constructors, hasItem(unwantedCon))
assertThat("<init>(Long) not found", sourceClass.kotlin.constructors, hasItem(wantedCon))
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())
assertThat("<init>(Int,Long) still exists", fixedClass.kotlin.constructors, not(hasItem(unwantedCon)))
assertThat("<init>(Long) not found", fixedClass.kotlin.constructors, hasItem(wantedCon))
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 {

View File

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

@ -15,11 +15,7 @@ class MetaFixFunctionTest {
companion object {
private val logger: Logger = StdOutLogging(MetaFixFunctionTest::class)
private val longData = isFunction("longData", Long::class)
private val unwantedFun = isFunction(
name = "unwantedFun",
returnType = String::class,
parameters = *arrayOf(String::class)
)
private val unwantedFun = isFunction("unwantedFun", String::class, String::class)
}
@Test
@ -31,15 +27,19 @@ class MetaFixFunctionTest {
// added to the metadata, and that the class is valid.
val sourceObj = sourceClass.newInstance()
assertEquals(BIG_NUMBER, sourceObj.longData())
assertThat("unwantedFun(String) not found", sourceClass.kotlin.declaredFunctions, hasItem(unwantedFun))
assertThat("longData not found", sourceClass.kotlin.declaredFunctions, hasItem(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())
assertThat("unwantedFun(String) still exists", fixedClass.kotlin.declaredFunctions, not(hasItem(unwantedFun)))
assertThat("longData not found", fixedClass.kotlin.declaredFunctions, hasItem(longData))
with(fixedClass.kotlin.declaredFunctions) {
assertThat("unwantedFun(String) still exists", this, not(hasItem(unwantedFun)))
assertThat("longData not found", this, hasItem(longData))
}
}
class MetadataTemplate : HasLong {

View File

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

@ -41,21 +41,21 @@ class MetaFixPackageTest {
@Test
fun testPackageFunction() {
assertFailsWith<UnsupportedOperationException> { sourceClass.kotlin.declaredFunctions }
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> { sourceClass.kotlin.declaredMembers }
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> { sourceClass.kotlin.declaredMembers }
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredMembers }
//assertThat("templateVar not found", sourceClass.kotlin.declaredMembers, hasItem(staticVar))
//assertThat("templateVar still exists", fixedClass.kotlin.declaredMembers, not(hasItem(staticVar)))
}

View File

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

@ -16,6 +16,7 @@ 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!"
@ -67,8 +68,17 @@ fun arrayOfJunk(size: Int) = ByteArray(size).apply {
}
}
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 { it.parameters.all(KParameter::isOptional) }
get() = constructors.firstOrNull(KFunction<*>::hasAllOptionalParameters)
@Throws(MalformedURLException::class)
fun classLoaderFor(jar: Path) = URLClassLoader(arrayOf(jar.toUri().toURL()), classLoader)

View File

@ -1,6 +1,7 @@
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
@ -24,7 +25,7 @@ internal class ClassMetadata(
ProtoBuf.Class::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val className = nameResolver.getString(message.fqName)
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)
@ -35,13 +36,13 @@ internal class ClassMetadata(
override fun rebuild(): ProtoBuf.Class = message
val sealedSubclasses: List<String> = sealedSubclassNames.map {
// Transform "a/b/c/BaseName.SubclassName" -> "a.b.c.BaseName$SubclassName"
nameResolver.getString(it).replace('.', '$').toPackageFormat }.toList()
// 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.getString(it)}" }.toList()
nestedClasses = nestedClassNames.map { "$internalClassName\$${nameResolver.getClassInternalName(it)}" }.toList()
}
}

View File

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