mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
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:
parent
9f905da036
commit
7e220d317d
@ -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) }
|
||||
|
@ -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,32 +94,52 @@ 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 {
|
||||
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
|
||||
while (idx < constructors.size) {
|
||||
val signature = JvmProtoBufUtil.getJvmConstructorSignature(constructors[idx], nameResolver, typeTable)
|
||||
if ((signature == null) || actualMethods.contains(signature)) {
|
||||
++idx
|
||||
} else {
|
||||
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,7 +148,8 @@ 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 signature = property.getExtensionOrNull(propertySignature)
|
||||
if (signature != null) {
|
||||
val field = signature.toFieldElement(property, nameResolver, typeTable)
|
||||
val getterMethod = signature.toGetter(nameResolver)
|
||||
|
||||
@ -143,7 +165,7 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
|
||||
val isValidProperty = if (getterMethod == null) {
|
||||
actualFields.contains(field) || classKind == COMPANION_OBJECT
|
||||
} else {
|
||||
actualMethods.contains(getterMethod.name + getterMethod.descriptor)
|
||||
actualMethods.contains(getterMethod.signature)
|
||||
}
|
||||
|
||||
if (!isValidProperty) {
|
||||
@ -152,6 +174,7 @@ internal abstract class MetaFixerTransformer<out T : MessageLite>(
|
||||
++count
|
||||
continue@removed
|
||||
}
|
||||
}
|
||||
++idx
|
||||
}
|
||||
return count
|
||||
@ -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)
|
||||
}
|
||||
|
||||
if (properties.size != propertyCount) {
|
||||
clearProperty().addAllProperty(properties)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
@ -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)))
|
||||
}
|
||||
|
@ -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"
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
Loading…
Reference in New Issue
Block a user