diff --git a/build.gradle b/build.gradle index 9f0b4c784c..47eb5ee092 100644 --- a/build.gradle +++ b/build.gradle @@ -74,6 +74,7 @@ buildscript { ext.selenium_version = '3.8.1' ext.ghostdriver_version = '2.1.0' ext.eaagentloader_version = '1.0.3' + ext.proguard_version = constants.getProperty('proguardVersion') ext.jsch_version = '0.1.54' ext.commons_cli_version = '1.4' ext.protonj_version = '0.27.1' @@ -81,6 +82,8 @@ buildscript { ext.fast_classpath_scanner_version = '2.12.3' ext.jcabi_manifests_version = '1.1' + ext.deterministic_rt_version = '1.0-SNAPSHOT' + // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: ext.java8_minUpdateVersion = '131' @@ -105,6 +108,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.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" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" @@ -143,7 +147,6 @@ targetCompatibility = 1.8 allprojects { apply plugin: 'kotlin' - apply plugin: 'java' apply plugin: 'jacoco' apply plugin: 'org.owasp.dependencycheck' apply plugin: 'kotlin-allopen' @@ -191,7 +194,7 @@ allprojects { tasks.withType(Test) { // Prevent the project from creating temporary files outside of the build directory. - systemProperties['java.io.tmpdir'] = buildDir + systemProperty 'java.io.tmpdir', buildDir.absolutePath if (System.getProperty("test.maxParallelForks") != null) { maxParallelForks = Integer.valueOf(System.getProperty("test.maxParallelForks")) @@ -254,6 +257,7 @@ if (!JavaVersion.current().java8Compatible) throw new GradleException("Corda requires Java 8, please upgrade to at least 1.8.0_$java8_minUpdateVersion") repositories { + mavenLocal() mavenCentral() jcenter() } @@ -324,7 +328,29 @@ bintrayConfig { projectUrl = 'https://github.com/corda/corda' gpgSign = true gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE') - publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver', 'corda-confidential-identities', 'corda-shell', 'corda-serialization', 'tools-blob-inspector', 'tools-network-bootstrapper'] + publications = [ + 'corda-jfx', + 'corda-mock', + 'corda-rpc', + 'corda-core', + 'corda-core-deterministic', + 'corda', + 'corda-finance', + 'corda-node', + 'corda-node-api', + 'corda-test-common', + 'corda-test-utils', + 'corda-jackson', + 'corda-webserver-impl', + 'corda-webserver', + 'corda-node-driver', + 'corda-confidential-identities', + 'corda-shell', + 'corda-serialization', + 'corda-serialization-deterministic', + 'tools-blob-inspector', + 'tools-network-bootstrapper' + ] license { name = 'Apache-2.0' url = 'https://www.apache.org/licenses/LICENSE-2.0' diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index c65b8aae95..1e48e27a37 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -2,7 +2,24 @@ buildscript { Properties constants = new Properties() file("../constants.properties").withInputStream { constants.load(it) } - ext.guava_version = constants.getProperty("guavaVersion") + 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" + } } apply plugin: 'maven' @@ -13,6 +30,27 @@ repositories { mavenCentral() } +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 + } +} + dependencies { // Add the top-level projects ONLY to the host project. runtime project.childProjects.values().collect { diff --git a/buildSrc/jarfilter/README.md b/buildSrc/jarfilter/README.md new file mode 100644 index 0000000000..a7a0546816 --- /dev/null +++ b/buildSrc/jarfilter/README.md @@ -0,0 +1,152 @@ +# 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. + +### 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 = [] + val d2: Array = [] + // ... +} +``` + +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(TM). + +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. diff --git a/buildSrc/jarfilter/build.gradle b/buildSrc/jarfilter/build.gradle new file mode 100644 index 0000000000..f4067d6e7a --- /dev/null +++ b/buildSrc/jarfilter/build.gradle @@ -0,0 +1,46 @@ +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" +} diff --git a/buildSrc/jarfilter/kotlin-metadata/build.gradle b/buildSrc/jarfilter/kotlin-metadata/build.gradle new file mode 100644 index 0000000000..b509187551 --- /dev/null +++ b/buildSrc/jarfilter/kotlin-metadata/build.gradle @@ -0,0 +1,81 @@ +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/native/**' + 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 + printseeds + 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.**' + + keep 'class org.jetbrains.kotlin.load.java.JvmAnnotationNames { *; }' + keep 'class org.jetbrains.kotlin.metadata.** { *; }', 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.**' + + keep 'class *' +} + +artifacts { + 'default' file: metadataJar, name: project.name, type: 'jar', extension: 'jar', builtBy: metadata +} + +defaultTasks "metadata" +assemble.dependsOn metadata +metadata.finalizedBy validate diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/ClassTransformer.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/ClassTransformer.kt new file mode 100644 index 0000000000..8b181fed6c --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/ClassTransformer.kt @@ -0,0 +1,353 @@ +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 ClassTransformer private constructor ( + visitor: ClassVisitor, + logger: Logger, + kotlinMetadata: MutableMap>, + private val removeAnnotations: Set, + private val deleteAnnotations: Set, + private val stubAnnotations: Set, + private val unwantedClasses: MutableSet, + private val unwantedFields: MutableSet, + private val deletedMethods: MutableSet, + private val stubbedMethods: MutableSet +) : KotlinAwareVisitor(ASM6, visitor, logger, kotlinMetadata), Repeatable { + constructor( + visitor: ClassVisitor, + logger: Logger, + removeAnnotations: Set, + deleteAnnotations: Set, + stubAnnotations: Set, + unwantedClasses: MutableSet + ) : this( + visitor = visitor, + logger = logger, + kotlinMetadata = mutableMapOf(), + removeAnnotations = removeAnnotations, + deleteAnnotations = deleteAnnotations, + stubAnnotations = stubAnnotations, + unwantedClasses = unwantedClasses, + unwantedFields = mutableSetOf(), + deletedMethods = mutableSetOf(), + stubbedMethods = mutableSetOf() + ) + + private var _className: String = "(unknown)" + val className: String get() = _className + + 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 = unwantedClasses.contains(name) + private fun hasDeletedSyntheticMethod(name: String): Boolean = deletedMethods.any { method -> + name.startsWith("$className\$${method.visibleName}\$") + } + + override fun recreate(visitor: ClassVisitor) = ClassTransformer( + visitor = visitor, + logger = logger, + kotlinMetadata = kotlinMetadata, + removeAnnotations = removeAnnotations, + deleteAnnotations = deleteAnnotations, + stubAnnotations = stubAnnotations, + unwantedClasses = unwantedClasses, + unwantedFields = unwantedFields, + deletedMethods = deletedMethods, + stubbedMethods = stubbedMethods + ) + + override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array?) { + _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 (unwantedClasses.add(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?): MethodVisitor? { + val method = MethodElement(methodName, descriptor, access) + logger.debug("--- method ---> {}", method) + if (deletedMethods.contains(method)) { + logger.info("- Deleted method {}{}", method.name, method.descriptor) + 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 (unwantedClasses.add(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 (isUnwantedClass(outerName)) { + logger.info("- Deleted reference to outer class {}", outerName) + } 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 transformClassMetadata(d1: List, d2: List): List { + val partitioned = deletedMethods.groupBy(MethodElement::isConstructor) + val prefix = "$className$" + return ClassMetadataTransformer( + logger = logger, + deletedFields = unwantedFields, + deletedFunctions = partitioned[false] ?: emptyList(), + deletedConstructors = partitioned[true] ?: emptyList(), + deletedNestedClasses = unwantedClasses.filter { it.startsWith(prefix) }.map { it.drop(prefix.length) }, + deletedClasses = unwantedClasses, + handleExtraMethod = ::delete, + d1 = d1, + d2 = d2) + .transform() + } + + /** + * Removes the deleted methods and fields from the Kotlin Package metadata. + */ + override fun transformPackageMetadata(d1: List, d2: List): List { + 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 or ACC_SYNTHETIC)) == 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", "", "(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) + } + } +} diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Elements.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Elements.kt new file mode 100644 index 0000000000..54ca3ad542 --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Elements.kt @@ -0,0 +1,110 @@ +@file:JvmName("Elements") +package net.corda.gradle.jarfilter + +import org.jetbrains.kotlin.metadata.ProtoBuf +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.objectweb.asm.Opcodes.ACC_SYNTHETIC +import java.util.* + +private const val DUMMY_PASSES = 1 + +internal abstract class Element(val name: String, val descriptor: String) { + private var lifetime: Int = DUMMY_PASSES + + open val isExpired: Boolean get() = --lifetime < 0 +} + + +internal class MethodElement(name: String, descriptor: String, val access: Int = 0) : Element(name, descriptor) { + 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 == "" + val isObjectConstructor: Boolean get() = name == "" + val isVoidFunction: Boolean get() = !isConstructor && descriptor.endsWith(")V") + + private val suffix: String + val visibleName: String + + 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) +} + + +/** + * A class cannot have two fields with the same name but different types. However, + * it can define extension functions and properties. + */ +internal class FieldElement(name: String, descriptor: String = "?", val extension: String = "()") : Element(name, descriptor) { + 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(')')) + +/** + * 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) +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/JarFilterPlugin.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/JarFilterPlugin.kt new file mode 100644 index 0000000000..ae33419b2b --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/JarFilterPlugin.kt @@ -0,0 +1,14 @@ +@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 { + override fun apply(project: Project) { + project.logger.info("Applying JarFilter plugin") + } +} diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/JarFilterTask.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/JarFilterTask.kt new file mode 100644 index 0000000000..7d7b87e264 --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/JarFilterTask.kt @@ -0,0 +1,254 @@ +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 = emptySet() + + @get:Input + protected var forStub: Set = emptySet() + + @get:Input + protected var forRemove: Set = emptySet() + + fun annotations(assign: Closure>) { + 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()) + } + 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 unwantedClasses: MutableSet = mutableSetOf() + private val source: Path = inFile.toPath() + private val target: Path = toFiltered(inFile).toPath() + + init { + Files.deleteIfExists(target) + } + + fun run() { + logger.info("Filtering to: {}", target) + var input = source + + try { + var passes = 1 + while (true) { + verbose("Pass {}", passes) + val isModified = Pass(input).use { it.run() } + + if (!isModified) { + logger.info("No changes after latest pass - exiting.") + break + } else if (++passes > maxPasses) { + break + } + + input = Files.move( + target, Files.createTempFile(target.parent, "filter-", ".tmp"), REPLACE_EXISTING) + verbose("New input JAR: {}", input) + } + } catch (e: Exception) { + logger.error("Error filtering '{}' elements from {}", ArrayList(forRemove).apply { addAll(forDelete); addAll(forStub) }, input) + throw e + } + } + + private 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. + */ + private val inJar = ZipFile(input.toFile()) + private val outJar = ZipOutputStream(Files.newOutputStream(target)) + private var isModified = false + + @Throws(IOException::class) + override fun close() { + inJar.use { + outJar.close() + } + } + + 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 fun transform(inBytes: ByteArray): ByteArray { + var reader = ClassReader(inBytes) + var writer = ClassWriter(COMPUTE_MAXS) + var transformer = ClassTransformer( + visitor = writer, + logger = logger, + removeAnnotations = toDescriptors(forRemove), + deleteAnnotations = toDescriptors(forDelete), + stubAnnotations = toDescriptors(forStub), + unwantedClasses = unwantedClasses + ) + + /* + * 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() + } + } + } + } +} diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/KotlinAwareVisitor.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/KotlinAwareVisitor.kt new file mode 100644 index 0000000000..ae078c69b4 --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/KotlinAwareVisitor.kt @@ -0,0 +1,109 @@ +package net.corda.gradle.jarfilter + +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, + * or writes new ProtoBuf data that was created during a previous pass. + */ +abstract class KotlinAwareVisitor( + api: Int, + visitor: ClassVisitor, + protected val logger: Logger, + protected val kotlinMetadata: MutableMap> +) : 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 abstract fun transformClassMetadata(d1: List, d2: List): List + protected abstract fun transformPackageMetadata(d1: List, d2: List): List + + 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 + } + + override fun visitEnd() { + super.visitEnd() + if (kotlinMetadata.isNotEmpty()) { + logger.info("- 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) { + transformMetadata(d1, d2).apply { + if (isNotEmpty()) { + kotlinMetadata[METADATA_DATA_FIELD_NAME] = this + kotlinMetadata[METADATA_STRINGS_FIELD_NAME] = d2 + } + } + } + } + } + + private fun transformMetadata(d1: List, d2: List): List { + return when (classKind) { + KOTLIN_CLASS -> transformClassMetadata(d1, d2) + KOTLIN_FILE, KOTLIN_MULTIFILE_PART -> transformPackageMetadata(d1, d2) + KOTLIN_SYNTHETIC -> { + logger.info("-- 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.info("-- 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 + } + + private inner class ArrayAccumulator(av: AnnotationVisitor, private val name: String) : AnnotationVisitor(api, av) { + private val data: MutableList = 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) + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerTask.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerTask.kt new file mode 100644 index 0000000000..5c120cc926 --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerTask.kt @@ -0,0 +1,128 @@ +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.namesEndingWith(suffix: String): Set { + return filter { it.name.endsWith(suffix) }.map { it.name.dropLast(suffix.length) }.toSet() + } +} + +fun ByteArray.fixMetadata(logger: Logger, classNames: Set): ByteArray + = execute({ writer -> MetaFixerVisitor(writer, logger, classNames) }) diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerTransformer.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerTransformer.kt new file mode 100644 index 0000000000..65988db056 --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerTransformer.kt @@ -0,0 +1,258 @@ +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( + private val logger: Logger, + private val actualFields: Collection, + private val actualMethods: Collection, + private val actualNestedClasses: Collection, + private val actualClasses: Collection, + d1: List, + d2: List, + parser: (InputStream, ExtensionRegistryLite) -> T +) { + private val stringTableTypes: StringTableTypes + private val nameResolver: NameResolver + protected val message: T + + protected abstract val typeTable: TypeTable + protected open val classKind: ProtoBuf.Class.Kind? = null + protected abstract val properties: MutableList + protected abstract val functions: MutableList + protected abstract val constructors: MutableList + protected open val nestedClassNames: MutableList get() = throw UnsupportedOperationException("No nestedClassNames") + protected open val sealedSubclassNames: MutableList 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.getString(sealedSubclassNames[idx]).replace('.', '$') + 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 + 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 + } + } + 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 { + logger.info("-- removing constructor: {}", signature) + constructors.removeAt(idx) + ++count + } + } + 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) ?: continue + 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) + } + + if (!isValidProperty) { + logger.info("-- removing property: {},{}", field.name, field.descriptor) + properties.removeAt(idx) + ++count + continue@removed + } + ++idx + } + return count + } + + fun transform(): List { + 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, + actualMethods: Collection, + actualNestedClasses: Collection, + actualClasses: Collection, + d1: List, + d2: List +) : MetaFixerTransformer( + logger, + actualFields, + actualMethods, + actualNestedClasses, + actualClasses, + d1, + d2, + ProtoBuf.Class::parseFrom +) { + override val typeTable = TypeTable(message.typeTable) + 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 { + 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) + } + }.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, + actualMethods: Collection, + d1: List, + d2: List +) : MetaFixerTransformer( + 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() + + override fun rebuild(): ProtoBuf.Package = message.toBuilder().apply { + if (functions.size != functionCount) { + clearFunction().addAllFunction(functions) + } + if (properties.size != propertyCount) { + clearProperty().addAllProperty(properties) + } + }.build() +} diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerVisitor.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerVisitor.kt new file mode 100644 index 0000000000..a122cf4308 --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerVisitor.kt @@ -0,0 +1,76 @@ +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>, + private val classNames: Set, + private val fields: MutableSet, + private val methods: MutableSet, + private val nestedClasses: MutableSet +) : KotlinAwareVisitor(ASM6, visitor, logger, kotlinMetadata), Repeatable { + constructor(visitor: ClassVisitor, logger: Logger, classNames: Set) + : 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?) { + 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?): 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 transformClassMetadata(d1: List, d2: List): List { + return ClassMetaFixerTransformer( + logger = logger, + actualFields = fields, + actualMethods = methods, + actualNestedClasses = nestedClasses, + actualClasses = classNames, + d1 = d1, + d2 = d2) + .transform() + } + + override fun transformPackageMetadata(d1: List, d2: List): List { + return PackageMetaFixerTransformer( + logger = logger, + actualFields = fields, + actualMethods = methods, + d1 = d1, + d2 = d2) + .transform() + } +} diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetadataTransformer.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetadataTransformer.kt new file mode 100644 index 0000000000..6c75fe0121 --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetadataTransformer.kt @@ -0,0 +1,307 @@ +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 [ClassTransformer] for [JarFilterTask]. + */ +internal abstract class MetadataTransformer( + private val logger: Logger, + private val deletedFields: Collection, + private val deletedFunctions: Collection, + private val deletedConstructors: Collection, + private val deletedNestedClasses: Collection, + private val deletedClasses: Collection, + private val handleExtraMethod: (MethodElement) -> Unit, + d1: List, + d2: List, + 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 get() = throw UnsupportedOperationException("No nestedClassNames") + protected open val sealedSubclassNames: MutableList get() = throw UnsupportedOperationException("No sealedSubclassNames") + protected abstract val properties: MutableList + protected abstract val functions: MutableList + protected open val constructors: MutableList get() = throw UnsupportedOperationException("No constructors") + protected abstract val typeAliases: MutableList + + 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 { + 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 { + 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 (IS_SECONDARY.get(constructor.flags)) { + logger.info("-- removing constructor: {}{}", deleted.name, deleted.descriptor) + } else { + logger.warn("Removing primary constructor: {}{}", className, deleted.descriptor) + } + constructors.removeAt(idx) + 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.name + deleted.descriptor) { + logger.info("-- removing function: {}{}", deleted.name, deleted.descriptor) + 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.name, func.descriptor) + 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.getString(sealedSubclassNames[idx]).replace('.', '$') + 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, + deletedFunctions: Collection, + deletedConstructors: Collection, + deletedNestedClasses: Collection, + deletedClasses: Collection, + handleExtraMethod: (MethodElement) -> Unit, + d1: List, + d2: List +) : MetadataTransformer( + logger, + deletedFields, + deletedFunctions, + deletedConstructors, + deletedNestedClasses, + deletedClasses, + handleExtraMethod, + d1, + d2, + ProtoBuf.Class::parseFrom +) { + override val typeTable = TypeTable(message.typeTable) + override val className = nameResolver.getString(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 { + if (nestedClassNames.size != nestedClassNameCount) { + clearNestedClassName().addAllNestedClassName(nestedClassNames) + } + if (constructors.size != constructorCount) { + clearConstructor().addAllConstructor(constructors) + } + 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, + deletedFunctions: Collection, + handleExtraMethod: (MethodElement) -> Unit, + d1: List, + d2: List +) : MetadataTransformer( + 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() +} diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Repeatable.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Repeatable.kt new file mode 100644 index 0000000000..4123fb3de4 --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Repeatable.kt @@ -0,0 +1,8 @@ +package net.corda.gradle.jarfilter + +import org.objectweb.asm.ClassVisitor + +interface Repeatable { + fun recreate(visitor: ClassVisitor): T + val hasUnwantedElements: Boolean +} diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Utils.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Utils.kt new file mode 100644 index 0000000000..cbbc026c99 --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Utils.kt @@ -0,0 +1,89 @@ +@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 mutableList(c: Collection): MutableList = ArrayList(c) + +/** + * Converts Java class names to Java descriptors. + */ +internal fun toDescriptors(classNames: Iterable): Set { + 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 ByteArray.execute(visitor: (ClassVisitor) -> T, flags: Int = 0, passes: Int = 2): ByteArray + where T : ClassVisitor, + T : Repeatable { + var reader = ClassReader(this) + var writer = ClassWriter(flags) + val transformer = visitor(writer) + var count = max(passes, 1) + + reader.accept(transformer, 0) + while (transformer.hasUnwantedElements && --count > 0) { + reader = ClassReader(writer.toByteArray()) + writer = ClassWriter(flags) + reader.accept(transformer.recreate(writer), 0) + } + + return writer.toByteArray() +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/AbstractFunctionTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/AbstractFunctionTest.kt new file mode 100644 index 0000000000..fb4bbc63a0 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/AbstractFunctionTest.kt @@ -0,0 +1,69 @@ +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(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(FUNCTION_CLASS).apply { + assertFailsWith { 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(FUNCTION_CLASS).apply { + getMethod("toStubOut", Long::class.java).also { method -> + assertEquals(ABSTRACT, method.modifiers and ABSTRACT) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(FUNCTION_CLASS).apply { + getMethod("toStubOut", Long::class.java).also { method -> + assertEquals(ABSTRACT, method.modifiers and ABSTRACT) + } + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteAndStubTests.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteAndStubTests.kt new file mode 100644 index 0000000000..40e61b2706 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteAndStubTests.kt @@ -0,0 +1,188 @@ +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(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(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(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(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(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(DELETED_FUN_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj -> + assertFailsWith { obj.stringData() }.also { ex -> + assertThat(ex).hasMessage("Method has been deleted") + } + assertFailsWith { (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(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(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(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(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(DELETED_PKG_CLASS).apply { + getDeclaredMethod("stubbed", String::class.java).also { method -> + assertEquals("[$MESSAGE]", method.invoke(null, MESSAGE)) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + assertFailsWith { cl.load(DELETED_PKG_CLASS) } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteConstructorTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteConstructorTest.kt new file mode 100644 index 0000000000..08599d0865 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteConstructorTest.kt @@ -0,0 +1,165 @@ +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, hasParam(Long::class)) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(SECONDARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { + assertEquals(BIG_NUMBER, it.longData()) + } + assertThat("(J) not found", kotlin.constructors, hasItem(longConstructor)) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(SECONDARY_CONSTRUCTOR_CLASS).apply { + assertFailsWith { getDeclaredConstructor(Long::class.java) } + assertThat("(J) still exists", kotlin.constructors, not(hasItem(longConstructor))) + assertNotNull("primary constructor missing", kotlin.primaryConstructor) + } + } + } + + @Test + fun deleteConstructorWithStringParameter() { + val stringConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, hasParam(String::class)) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(SECONDARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { + assertEquals(MESSAGE, it.stringData()) + } + assertThat("(String) not found", kotlin.constructors, hasItem(stringConstructor)) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(SECONDARY_CONSTRUCTOR_CLASS).apply { + assertFailsWith { getDeclaredConstructor(String::class.java) } + assertThat("(String) still exists", kotlin.constructors, not(hasItem(stringConstructor))) + assertNotNull("primary constructor missing", kotlin.primaryConstructor) + } + } + } + + @Test + fun showUnannotatedConstructorIsUnaffected() { + val intConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, hasParam(Int::class)) + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(SECONDARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { + assertEquals(NUMBER, it.intData()) + assertEquals(NUMBER.toLong(), it.longData()) + assertEquals("", it.stringData()) + } + assertThat("(Int) not found", kotlin.constructors, hasItem(intConstructor)) + assertNotNull("primary constructor missing", kotlin.primaryConstructor) + } + } + } + + @Test + fun deletePrimaryConstructorWithStringParameter() { + val stringConstructor = isConstructor(STRING_PRIMARY_CONSTRUCTOR_CLASS, hasParam(String::class)) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { + assertEquals(MESSAGE, it.stringData()) + } + assertThat("(String) not found", kotlin.constructors, hasItem(stringConstructor)) + assertThat("primary constructor missing", kotlin.primaryConstructor!!, stringConstructor) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply { + assertFailsWith { getDeclaredConstructor(String::class.java) } + assertThat("(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, hasParam(Long::class)) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { + assertEquals(BIG_NUMBER, it.longData()) + } + assertThat("(J) not found", kotlin.constructors, hasItem(longConstructor)) + assertThat("primary constructor missing", kotlin.primaryConstructor!!, longConstructor) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply { + assertFailsWith { getDeclaredConstructor(Long::class.java) } + assertThat("(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, hasParam(Int::class)) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(INT_PRIMARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { + assertEquals(NUMBER, it.intData()) + } + assertThat("(I) not found", kotlin.constructors, hasItem(intConstructor)) + assertThat("primary constructor missing", kotlin.primaryConstructor!!, intConstructor) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(INT_PRIMARY_CONSTRUCTOR_CLASS).apply { + assertFailsWith { getDeclaredConstructor(Int::class.java) } + assertThat("(I) still exists", kotlin.constructors, not(hasItem(intConstructor))) + assertNull("primary constructor still exists", kotlin.primaryConstructor) + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteExtensionValPropertyTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteExtensionValPropertyTest.kt new file mode 100644 index 0000000000..4723caef19 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteExtensionValPropertyTest.kt @@ -0,0 +1,52 @@ +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(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(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))) + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteFieldTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteFieldTest.kt new file mode 100644 index 0000000000..4be9530f20 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteFieldTest.kt @@ -0,0 +1,99 @@ +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(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(STRING_FIELD_CLASS).apply { + assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE)) + assertFailsWith { 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(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(LONG_FIELD_CLASS).apply { + assertNotNull(getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)) + assertFailsWith { 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(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(INTEGER_FIELD_CLASS).apply { + assertNotNull(getDeclaredConstructor(Int::class.java).newInstance(NUMBER)) + assertFailsWith { getDeclaredField("intField") } + assertThat("intField still exists", kotlin.declaredMemberProperties, not(hasItem(intField))) + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteFunctionTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteFunctionTest.kt new file mode 100644 index 0000000000..8e035fead8 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteFunctionTest.kt @@ -0,0 +1,81 @@ +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(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(FUNCTION_CLASS).apply { + newInstance().also { + assertFailsWith { 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(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(INDIRECT_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { + assertFailsWith { it.unwantedFun(MESSAGE) } + assertFailsWith { (it as HasString).stringData() } + } + assertThat("unwantedFun(String) still exists", kotlin.declaredFunctions, not(hasItem(unwantedFun))) + assertThat("stringData still exists", kotlin.declaredFunctions, not(hasItem(stringData))) + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteLazyTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteLazyTest.kt new file mode 100644 index 0000000000..ac217fea87 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteLazyTest.kt @@ -0,0 +1,71 @@ +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 + private lateinit var filteredClasses: List + + @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(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(LAZY_VAL_CLASS).apply { + assertFailsWith { getConstructor(String::class.java) } + assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal))) + assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal))) + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteMultiFileTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteMultiFileTest.kt new file mode 100644 index 0000000000..4c7333c41a --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteMultiFileTest.kt @@ -0,0 +1,90 @@ +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(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(MULTIFILE_CLASS).apply { + assertFailsWith { getMethod(STRING_METHOD, String::class.java) } + } + } + } + + @Test + fun deleteLongFunction() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(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(MULTIFILE_CLASS).apply { + assertFailsWith { getMethod(LONG_METHOD, Long::class.java) } + } + } + } + + @Test + fun deleteIntFunction() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(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(MULTIFILE_CLASS).apply { + assertFailsWith { getMethod(INT_METHOD, Int::class.java) } + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteNestedClassTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteNestedClassTest.kt new file mode 100644 index 0000000000..934a2797bc --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteNestedClassTest.kt @@ -0,0 +1,90 @@ +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(DELETED_CLASS) + val kept = cl.load(KEPT_CLASS) + cl.load(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 { cl.load(DELETED_CLASS) } + val kept = cl.load(KEPT_CLASS) + cl.load(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(UNWANTED_SUBCLASS) + val wanted = cl.load(WANTED_SUBCLASS) + cl.load(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 { cl.load(UNWANTED_SUBCLASS) } + val wanted = cl.load(WANTED_SUBCLASS) + cl.load(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) + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteObjectTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteObjectTest.kt new file mode 100644 index 0000000000..5e6db4a4b1 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteObjectTest.kt @@ -0,0 +1,89 @@ +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 + private lateinit var filteredClasses: List + + @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(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(OBJECT_CLASS).apply { + assertFailsWith { getDeclaredMethod(UNWANTED_OBJ_METHOD) } + assertFailsWith { getDeclaredField(UNWANTED_OBJ_FIELD) } + } + } + } + + @Test + fun deleteFunctionWithObject() { + assertThat(sourceClasses).anyMatch { it.contains("\$unwantedFun\$") } + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(OBJECT_CLASS).apply { + getDeclaredMethod(UNWANTED_FUN_METHOD).also { method -> + assertEquals("", method.invoke(null)) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(OBJECT_CLASS).apply { + assertFailsWith { getDeclaredMethod(UNWANTED_FUN_METHOD) } + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteSealedSubclassTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteSealedSubclassTest.kt new file mode 100644 index 0000000000..eeb90e4c7f --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteSealedSubclassTest.kt @@ -0,0 +1,56 @@ +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(WANTED_SUBCLASS) + cl.load(UNWANTED_SUBCLASS) + cl.load(SEALED_CLASS).apply { + assertTrue(kotlin.isSealed) + assertThat(classMetadata.sealedSubclasses) + .containsExactlyInAnyOrder(WANTED_SUBCLASS, UNWANTED_SUBCLASS) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(WANTED_SUBCLASS) + assertFailsWith { cl.load(UNWANTED_SUBCLASS) } + cl.load(SEALED_CLASS).apply { + assertTrue(kotlin.isSealed) + assertThat(classMetadata.sealedSubclasses) + .containsExactly(WANTED_SUBCLASS) + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticFieldTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticFieldTest.kt new file mode 100644 index 0000000000..1b3d668035 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticFieldTest.kt @@ -0,0 +1,74 @@ +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(PROPERTY_CLASS).apply { + val getter = getDeclaredField("stringField") + assertEquals(DEFAULT_MESSAGE, getter.get(null)) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredField("stringField") } + } + } + } + + @Test + fun deleteLongField() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + val getter = getDeclaredField("longField") + assertEquals(DEFAULT_BIG_NUMBER, getter.get(null)) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredField("longField") } + } + } + } + + @Test + fun deleteIntField() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + val getter = getDeclaredField("intField") + assertEquals(DEFAULT_NUMBER, getter.get(null)) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredField("intField") } + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticFunctionTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticFunctionTest.kt new file mode 100644 index 0000000000..5ad8102dfb --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticFunctionTest.kt @@ -0,0 +1,87 @@ +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(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(FUNCTION_CLASS).apply { + assertFailsWith { getMethod("unwantedStringToDelete", String::class.java) } + } + } + } + + @Test + fun deleteLongFunction() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(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(FUNCTION_CLASS).apply { + assertFailsWith { getMethod("unwantedLongToDelete", Long::class.java) } + } + } + } + + @Test + fun deleteIntFunction() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(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(FUNCTION_CLASS).apply { + assertFailsWith { getMethod("unwantedIntToDelete", Int::class.java) } + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticValPropertyTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticValPropertyTest.kt new file mode 100644 index 0000000000..e24afa4b99 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticValPropertyTest.kt @@ -0,0 +1,91 @@ +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(PROPERTY_CLASS).apply { + val getter = getDeclaredMethod("getStringVal") + assertEquals(DEFAULT_MESSAGE, getter.invoke(null)) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredMethod("getStringVal") } + } + } + } + + @Test + fun deleteLongVal() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + val getter = getDeclaredMethod("getLongVal") + assertEquals(DEFAULT_BIG_NUMBER, getter.invoke(null)) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredMethod("getLongVal") } + } + } + } + + @Test + fun deleteIntVal() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + val getter = getDeclaredMethod("getIntVal") + assertEquals(DEFAULT_NUMBER, getter.invoke(null)) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredMethod("getIntVal") } + } + } + } + + @Test + fun deleteMemberVal() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + val getter = getDeclaredMethod("getMemberVal", Any::class.java) + assertEquals(LocalBlob, getter.invoke(null, LocalBlob)) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredMethod("getMemberVal", Any::class.java) } + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticVarPropertyTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticVarPropertyTest.kt new file mode 100644 index 0000000000..9bac98c37b --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteStaticVarPropertyTest.kt @@ -0,0 +1,106 @@ +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(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(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredMethod("getStringVar") } + assertFailsWith { getDeclaredMethod("setStringVar", String::class.java) } + } + } + } + + @Test + fun deleteLongVar() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(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(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredMethod("getLongVar") } + assertFailsWith { getDeclaredMethod("setLongVar", Long::class.java) } + } + } + } + + @Test + fun deleteIntVar() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(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(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredMethod("getIntVar") } + assertFailsWith { getDeclaredMethod("setIntVar", Int::class.java) } + } + } + } + + @Test + fun deleteMemberVar() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(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(PROPERTY_CLASS).apply { + assertFailsWith { getDeclaredMethod("getMemberVar", Any::class.java) } + assertFailsWith { getDeclaredMethod("setMemberVar", Any::class.java, Any::class.java) } + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteTypeAliasFromFileTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteTypeAliasFromFileTest.kt new file mode 100644 index 0000000000..bb2dc6f2a7 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteTypeAliasFromFileTest.kt @@ -0,0 +1,48 @@ +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 + private lateinit var filteredClasses: List + + @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(TYPEALIAS_CLASS).fileMetadata + assertThat(metadata.typeAliasNames) + .containsExactlyInAnyOrder("FileWantedType", "FileUnwantedType") + } + classLoaderFor(testProject.filteredJar).use { cl -> + val metadata = cl.load(TYPEALIAS_CLASS).fileMetadata + assertThat(metadata.typeAliasNames) + .containsExactly("FileWantedType") + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteValPropertyTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteValPropertyTest.kt new file mode 100644 index 0000000000..75d59b4e54 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteValPropertyTest.kt @@ -0,0 +1,102 @@ +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(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(PROPERTY_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj -> + assertFailsWith { obj.unwantedVal } + } + assertFailsWith { 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(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(GETTER_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj -> + assertFailsWith { 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(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(JVM_FIELD_CLASS).apply { + assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE)) + assertFailsWith { getDeclaredField("unwantedVal") } + assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal))) + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteVarPropertyTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteVarPropertyTest.kt new file mode 100644 index 0000000000..392f9466ea --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteVarPropertyTest.kt @@ -0,0 +1,141 @@ +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(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(PROPERTY_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj -> + assertFailsWith { obj.unwantedVar } + assertFailsWith { obj.unwantedVar = MESSAGE } + } + assertFailsWith { 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(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(GETTER_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj -> + assertFailsWith { 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(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(SETTER_CLASS).apply { + getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj -> + assertEquals(DEFAULT_MESSAGE, obj.unwantedVar) + assertFailsWith { 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(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(JVM_FIELD_CLASS).apply { + assertNotNull(getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE)) + assertFailsWith { getDeclaredField("unwantedVar") } + assertThat("unwantedVar still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVar))) + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DummyJar.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DummyJar.kt new file mode 100644 index 0000000000..c332860669 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DummyJar.kt @@ -0,0 +1,104 @@ +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() + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/EmptyPackage.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/EmptyPackage.kt new file mode 100644 index 0000000000..bbb76a8036 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/EmptyPackage.kt @@ -0,0 +1,8 @@ +@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 diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/FieldElementTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/FieldElementTest.kt new file mode 100644 index 0000000000..0d54ecba27 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/FieldElementTest.kt @@ -0,0 +1,32 @@ +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) + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/FieldRemovalTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/FieldRemovalTest.kt new file mode 100644 index 0000000000..da80e93660 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/FieldRemovalTest.kt @@ -0,0 +1,212 @@ +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 transform(): Class = transform(T::class.java, R::class.java) + + private fun transform(type: Class, asType: Class): Class { + val bytecode = type.bytecode.execute({ writer -> + ClassTransformer( + visitor = writer, + logger = logger, + removeAnnotations = emptySet(), + deleteAnnotations = setOf(Deletable::class.jvmName.descriptor), + stubAnnotations = emptySet(), + unwantedClasses = mutableSetOf() + ) + }, 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, HasGenericField>() + .getDeclaredConstructor(Any::class.java).newInstance(MESSAGE) + assertFailsWith { targetField.objectField } + assertFailsWith { 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() + .getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER) + assertFailsWith { targetField.longField } + assertFailsWith { 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() + .getDeclaredConstructor(Int::class.java).newInstance(NUMBER) + assertFailsWith { targetField.intField } + assertFailsWith { 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() + .getDeclaredConstructor(Short::class.java).newInstance(SHORT_NUMBER) + assertFailsWith { targetField.shortField } + assertFailsWith { 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() + .getDeclaredConstructor(Byte::class.java).newInstance(BYTE_NUMBER) + assertFailsWith { targetField.byteField } + assertFailsWith { 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() + .getDeclaredConstructor(Boolean::class.java).newInstance(true) + assertFailsWith { targetField.booleanField } + assertFailsWith { 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() + .getDeclaredConstructor(Char::class.java).newInstance('?') + assertFailsWith { targetField.charField } + assertFailsWith { 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() + .getDeclaredConstructor(Double::class.java).newInstance(BIG_FLOATING_POINT) + assertFailsWith { targetField.doubleField } + assertFailsWith { 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() + .getDeclaredConstructor(Float::class.java).newInstance(FLOATING_POINT) + assertFailsWith { targetField.floatField } + assertFailsWith { 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() + .getDeclaredConstructor(ByteArray::class.java).newInstance(byteArrayOf()) + assertFailsWith { targetField.arrayField } + assertFailsWith { targetField.arrayField = byteArrayOf(0x35, 0x73) } + assertThat("arrayField still exists", targetField::class.declaredMemberProperties, not(hasItem(arrayField))) + } +} + +interface HasGenericField { 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(@Deletable override var objectField: T) : HasGenericField +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 diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/InterfaceFunctionTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/InterfaceFunctionTest.kt new file mode 100644 index 0000000000..452f26d140 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/InterfaceFunctionTest.kt @@ -0,0 +1,61 @@ +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(FUNCTION_CLASS).apply { + getMethod("toDelete", Long::class.java).also { method -> + assertEquals(ABSTRACT, method.modifiers and ABSTRACT) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(FUNCTION_CLASS).apply { + assertFailsWith { getMethod("toDelete", Long::class.java) } + } + } + } + + @Test + fun cannotStubInterfaceFunction() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(FUNCTION_CLASS).apply { + getMethod("toStubOut", Long::class.java).also { method -> + assertEquals(ABSTRACT, method.modifiers and ABSTRACT) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(FUNCTION_CLASS).apply { + getMethod("toStubOut", Long::class.java).also { method -> + assertEquals(ABSTRACT, method.modifiers and ABSTRACT) + } + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/JarFilterConfigurationTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/JarFilterConfigurationTest.kt new file mode 100644 index 0000000000..a124545ae4 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/JarFilterConfigurationTest.kt @@ -0,0 +1,272 @@ +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", "--stacktrace")) + .withPluginClasspath() + } + + private fun BuildResult.forTask(name: String): BuildTask { + return task(":$name") ?: throw AssertionError("No outcome for $name task") + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/JarFilterProject.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/JarFilterProject.kt new file mode 100644 index 0000000000..1a2e3cb128 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/JarFilterProject.kt @@ -0,0 +1,56 @@ +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() + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/JarFilterTimestampTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/JarFilterTimestampTest.kt new file mode 100644 index 0000000000..d57aca4b65 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/JarFilterTimestampTest.kt @@ -0,0 +1,107 @@ +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) + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixAnnotationTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixAnnotationTest.kt new file mode 100644 index 0000000000..1fdcc44e7e --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixAnnotationTest.kt @@ -0,0 +1,47 @@ +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("() 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() + assertThat("() not found", fixedClass.kotlin.constructors, hasItem(defaultCon)) + } + + @Test + fun testAnnotationWithValue() { + val sourceClass = AnnotationWithValue::class.java + assertThat("(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() + assertThat("(String) not found", fixedClass.kotlin.constructors, hasItem(valueCon)) + } +} + +annotation class AnnotationWithValue(val str: String) +annotation class SimpleAnnotation diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixConfigurationTests.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixConfigurationTests.kt new file mode 100644 index 0000000000..bb67dba0c2 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixConfigurationTests.kt @@ -0,0 +1,79 @@ +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", "--stacktrace")) + .withPluginClasspath() + } + + private fun BuildResult.forTask(name: String): BuildTask { + return task(":$name") ?: throw AssertionError("No outcome for $name task") + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixConstructorTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixConstructorTest.kt new file mode 100644 index 0000000000..cbab25571a --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixConstructorTest.kt @@ -0,0 +1,55 @@ +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( + returnType = WithConstructor::class, + parameters = *arrayOf(Int::class, Long::class) + ) + private val wantedCon = isConstructor( + returnType = WithConstructor::class, + parameters = *arrayOf(Long::class) + ) + } + + @Test + fun testConstructorRemovedFromMetadata() { + val bytecode = recodeMetadataFor() + val sourceClass = bytecode.toClass() + + // 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()) + assertThat("(Int,Long) not found", sourceClass.kotlin.constructors, hasItem(unwantedCon)) + assertThat("(Long) not found", sourceClass.kotlin.constructors, hasItem(wantedCon)) + + // Rewrite the metadata according to the contents of the bytecode. + val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithConstructor::class)).toClass() + val fixedObj = fixedClass.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER) + assertEquals(BIG_NUMBER, fixedObj.longData()) + assertThat("(Int,Long) still exists", fixedClass.kotlin.constructors, not(hasItem(unwantedCon))) + assertThat("(Long) not found", fixedClass.kotlin.constructors, 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 +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixFunctionTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixFunctionTest.kt new file mode 100644 index 0000000000..1f4011d84e --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixFunctionTest.kt @@ -0,0 +1,53 @@ +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( + name = "unwantedFun", + returnType = String::class, + parameters = *arrayOf(String::class) + ) + } + + @Test + fun testFunctionRemovedFromMetadata() { + val bytecode = recodeMetadataFor() + val sourceClass = bytecode.toClass() + + // 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()) + assertThat("unwantedFun(String) not found", sourceClass.kotlin.declaredFunctions, hasItem(unwantedFun)) + assertThat("longData not found", sourceClass.kotlin.declaredFunctions, hasItem(longData)) + + // Rewrite the metadata according to the contents of the bytecode. + val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithFunction::class)).toClass() + 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)) + } + + 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 +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixNestedClassTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixNestedClassTest.kt new file mode 100644 index 0000000000..d460e9c453 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixNestedClassTest.kt @@ -0,0 +1,57 @@ +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() + val sourceClass = bytecode.toClass() + 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() + assertThat(fixedClass.classMetadata.nestedClasses).containsExactly(WANTED_CLASS) + } + + @Test + fun testAllNestedClassesRemovedFromMetadata() { + val bytecode = recodeMetadataFor() + val sourceClass = bytecode.toClass() + 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() + assertThat(fixedClass.classMetadata.nestedClasses).isEmpty() + } + + @Suppress("UNUSED") + class MetadataTemplate { + class Wanted + class Unwanted + } +} + +class WithNestedClass { + class Wanted +} + +class WithoutNestedClass \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixPackageTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixPackageTest.kt new file mode 100644 index 0000000000..0ef49ac46b --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixPackageTest.kt @@ -0,0 +1,66 @@ +@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 + private lateinit var fixedClass: Class + + @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 { sourceClass.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 { sourceClass.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 { sourceClass.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 \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixProject.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixProject.kt new file mode 100644 index 0000000000..3891f14aed --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixProject.kt @@ -0,0 +1,57 @@ +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() + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixSealedClassTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixSealedClassTest.kt new file mode 100644 index 0000000000..53ac03edb6 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixSealedClassTest.kt @@ -0,0 +1,37 @@ +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() + val sourceClass = bytecode.toClass() + 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() + assertThat(fixedClass.classMetadata.sealedSubclasses).containsExactly(WANTED_CLASS) + } + + @Suppress("UNUSED") + sealed class MetadataTemplate { + class Wanted : MetadataTemplate() + class Unwanted : MetadataTemplate() + } +} + +sealed class MetaSealedClass { + class Wanted : MetaSealedClass() +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixTimestampTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixTimestampTest.kt new file mode 100644 index 0000000000..f96b94558c --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixTimestampTest.kt @@ -0,0 +1,108 @@ +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) + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixValPropertyTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixValPropertyTest.kt new file mode 100644 index 0000000000..cef1cb2a77 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixValPropertyTest.kt @@ -0,0 +1,49 @@ +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() + val sourceClass = bytecode.toClass() + + // 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() + 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 +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixVarPropertyTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixVarPropertyTest.kt new file mode 100644 index 0000000000..9d904f610a --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MetaFixVarPropertyTest.kt @@ -0,0 +1,49 @@ +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() + val sourceClass = bytecode.toClass() + + // 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() + 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 +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MethodElementTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MethodElementTest.kt new file mode 100644 index 0000000000..bee8d58568 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/MethodElementTest.kt @@ -0,0 +1,88 @@ +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) + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/RemoveAnnotationsTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/RemoveAnnotationsTest.kt new file mode 100644 index 0000000000..ba074903c8 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/RemoveAnnotationsTest.kt @@ -0,0 +1,176 @@ +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(REMOVE_ME_CLASS) + cl.load(ANNOTATED_CLASS).apply { + assertNotNull(getAnnotation(removeMe)) + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + val removeMe = cl.load(REMOVE_ME_CLASS) + cl.load(ANNOTATED_CLASS).apply { + assertNull(getAnnotation(removeMe)) + } + } + } + + @Test + fun deleteFromDefaultConstructor() { + classLoaderFor(testProject.sourceJar).use { cl -> + val removeMe = cl.load(REMOVE_ME_CLASS) + cl.load(ANNOTATED_CLASS).apply { + getDeclaredConstructor().also { con -> + assertNotNull(con.getAnnotation(removeMe)) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + val removeMe = cl.load(REMOVE_ME_CLASS) + cl.load(ANNOTATED_CLASS).apply { + getDeclaredConstructor().also { con -> + assertNull(con.getAnnotation(removeMe)) + } + } + } + } + + @Test + fun deleteFromPrimaryConstructor() { + classLoaderFor(testProject.sourceJar).use { cl -> + val removeMe = cl.load(REMOVE_ME_CLASS) + cl.load(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(REMOVE_ME_CLASS) + cl.load(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(REMOVE_ME_CLASS) + cl.load(ANNOTATED_CLASS).apply { + getField("longField").also { field -> + assertNotNull(field.getAnnotation(removeMe)) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + val removeMe = cl.load(REMOVE_ME_CLASS) + cl.load(ANNOTATED_CLASS).apply { + getField("longField").also { field -> + assertNull(field.getAnnotation(removeMe)) + } + } + } + } + + @Test + fun deleteFromMethod() { + classLoaderFor(testProject.sourceJar).use { cl -> + val removeMe = cl.load(REMOVE_ME_CLASS) + cl.load(ANNOTATED_CLASS).apply { + getMethod("unwantedFun", String::class.java).also { method -> + assertNotNull(method.getAnnotation(removeMe)) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + val removeMe = cl.load(REMOVE_ME_CLASS) + cl.load(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(REMOVE_ME_CLASS) + cl.load(ANNOTATED_CLASS).apply { + getMethod("getUnwantedVal").also { method -> + assertNotNull(method.getAnnotation(removeMe)) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + val removeMe = cl.load(REMOVE_ME_CLASS) + cl.load(ANNOTATED_CLASS).apply { + getMethod("getUnwantedVal").also { method -> + assertNull(method.getAnnotation(removeMe)) + } + } + } + } + + @Test + fun deleteFromVarProperty() { + classLoaderFor(testProject.sourceJar).use { cl -> + val removeMe = cl.load(REMOVE_ME_CLASS) + cl.load(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(REMOVE_ME_CLASS) + cl.load(ANNOTATED_CLASS).apply { + getMethod("getUnwantedVar").also { method -> + assertNull(method.getAnnotation(removeMe)) + } + getMethod("setUnwantedVar", String::class.java).also { method -> + assertNull(method.getAnnotation(removeMe)) + } + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StaticFieldRemovalTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StaticFieldRemovalTest.kt new file mode 100644 index 0000000000..49df4a2eb7 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StaticFieldRemovalTest.kt @@ -0,0 +1,102 @@ +@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 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 + private lateinit var targetClass: Class + + private fun transform(type: Class, asType: Class): Class { + val bytecode = type.bytecode.execute({ writer -> + ClassTransformer( + visitor = writer, + logger = logger, + removeAnnotations = emptySet(), + deleteAnnotations = setOf(Deletable::class.jvmName.descriptor), + stubAnnotations = emptySet(), + unwantedClasses = mutableSetOf() + ) + }, 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 { targetClass.getDeclaredMethod("getStaticString") } + } + + @Test + fun deleteStaticLong() { + assertEquals(2L, sourceClass.getDeclaredMethod("getStaticLong").invoke(null)) + assertFailsWith { targetClass.getDeclaredMethod("getStaticLong") } + } + + @Test + fun deleteStaticInt() { + assertEquals(3, sourceClass.getDeclaredMethod("getStaticInt").invoke(null)) + assertFailsWith { targetClass.getDeclaredMethod("getStaticInt") } + } + + @Test + fun deleteStaticShort() { + assertEquals(4.toShort(), sourceClass.getDeclaredMethod("getStaticShort").invoke(null)) + assertFailsWith { targetClass.getDeclaredMethod("getStaticShort") } + } + + @Test + fun deleteStaticByte() { + assertEquals(5.toByte(), sourceClass.getDeclaredMethod("getStaticByte").invoke(null)) + assertFailsWith { targetClass.getDeclaredMethod("getStaticByte") } + } + + @Test + fun deleteStaticChar() { + assertEquals(6.toChar(), sourceClass.getDeclaredMethod("getStaticChar").invoke(null)) + assertFailsWith { 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() diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StdOutLogging.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StdOutLogging.kt new file mode 100644 index 0000000000..7cd785cf8f --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StdOutLogging.kt @@ -0,0 +1,262 @@ +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) + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubConstructorTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubConstructorTest.kt new file mode 100644 index 0000000000..56352be355 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubConstructorTest.kt @@ -0,0 +1,160 @@ +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(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(SECONDARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(Long::class.java).also { + assertFailsWith { 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(SECONDARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj -> + assertEquals(MESSAGE, obj.stringData()) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(SECONDARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(String::class.java).also { + assertFailsWith { 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(SECONDARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { obj -> + assertEquals(NUMBER, obj.intData()) + assertEquals(NUMBER.toLong(), obj.longData()) + assertEquals("", obj.stringData()) + } + } + } + } + + @Test + fun stubPrimaryConstructorWithStringParameter() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj -> + assertEquals(MESSAGE, obj.stringData()) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(String::class.java).also { + assertFailsWith { 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(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(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(Long::class.java).also { + assertFailsWith { 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(INT_PRIMARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { obj -> + assertEquals(NUMBER, obj.intData()) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(INT_PRIMARY_CONSTRUCTOR_CLASS).apply { + getDeclaredConstructor(Int::class.java).apply { + val error = assertFailsWith { newInstance(NUMBER) }.targetException + assertThat(error) + .isInstanceOf(UnsupportedOperationException::class.java) + .hasMessage("Method has been deleted") + } + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubFunctionOutTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubFunctionOutTest.kt new file mode 100644 index 0000000000..333fa2ce56 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubFunctionOutTest.kt @@ -0,0 +1,74 @@ +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(STUB_ME_OUT_ANNOTATION) + val parameter = cl.load(PARAMETER_ANNOTATION) + + cl.load(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(STUB_ME_OUT_ANNOTATION) + val parameter = cl.load(PARAMETER_ANNOTATION) + + cl.load(FUNCTION_CLASS).apply { + newInstance().also { obj -> + assertFailsWith { 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) } + } + } + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubStaticFunctionTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubStaticFunctionTest.kt new file mode 100644 index 0000000000..79a7ed3b7c --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubStaticFunctionTest.kt @@ -0,0 +1,127 @@ +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(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(FUNCTION_CLASS).apply { + getDeclaredMethod("unwantedStringToStub", String::class.java).also { method -> + assertFailsWith { 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(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(FUNCTION_CLASS).apply { + getDeclaredMethod("unwantedLongToStub", Long::class.java).also { method -> + assertFailsWith { 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(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(FUNCTION_CLASS).apply { + getDeclaredMethod("unwantedIntToStub", Int::class.java).also { method -> + assertFailsWith { 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(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(FUNCTION_CLASS).apply { + val staticSeed = getDeclaredMethod("getStaticSeed") + assertEquals(0, staticSeed.invoke(null)) + getDeclaredMethod("unwantedVoidToStub").invoke(null) + assertEquals(0, staticSeed.invoke(null)) + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubValPropertyTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubValPropertyTest.kt new file mode 100644 index 0000000000..fbc26f2211 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubValPropertyTest.kt @@ -0,0 +1,46 @@ +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(PROPERTY_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj -> + assertEquals(MESSAGE, obj.unwantedVal) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(PROPERTY_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj -> + assertFailsWith { obj.unwantedVal }.also { ex -> + assertEquals("Method has been deleted", ex.message) + } + } + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubVarPropertyTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubVarPropertyTest.kt new file mode 100644 index 0000000000..4910e83d62 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/StubVarPropertyTest.kt @@ -0,0 +1,70 @@ +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(GETTER_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj -> + assertEquals(MESSAGE, obj.unwantedVar) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(GETTER_CLASS).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj -> + assertFailsWith { obj.unwantedVar }.also { ex -> + assertEquals("Method has been deleted", ex.message) + } + } + } + } + } + + @Test + fun deleteSetter() { + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(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(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) + } + } + } + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/Utilities.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/Utilities.kt new file mode 100644 index 0000000000..62a0a36cff --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/Utilities.kt @@ -0,0 +1,82 @@ +@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 + +const val DEFAULT_MESSAGE = "" +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 = getBasicArgsForTasks(*taskNames).apply { add("--info") } +fun getBasicArgsForTasks(vararg taskNames: String): MutableList = mutableListOf(*taskNames, "-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 = 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() + } +} + +@Throws(MalformedURLException::class) +fun classLoaderFor(jar: Path) = URLClassLoader(arrayOf(jar.toUri().toURL()), classLoader) + +@Suppress("UNCHECKED_CAST") +@Throws(ClassNotFoundException::class) +fun ClassLoader.load(className: String) + = Class.forName(className, true, this) as Class + +fun Path.getClassNames(prefix: String): List { + 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()) +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/UtilsTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/UtilsTest.kt new file mode 100644 index 0000000000..b65a31130a --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/UtilsTest.kt @@ -0,0 +1,42 @@ +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 { rethrowAsUncheckedException(IOException(MESSAGE)) } + assertThat(ex) + .hasMessage(MESSAGE) + .hasCauseExactlyInstanceOf(IOException::class.java) + } + + @Test + fun testRethrowingCheckExceptionWithoutMessage() { + val ex = assertFailsWith { rethrowAsUncheckedException(IOException()) } + assertThat(ex) + .hasMessage("") + .hasCauseExactlyInstanceOf(IOException::class.java) + } + + @Test + fun testRethrowingUncheckedException() { + val ex = assertFailsWith { rethrowAsUncheckedException(IllegalArgumentException(MESSAGE)) } + assertThat(ex) + .hasMessage(MESSAGE) + .hasNoCause() + } + + @Test + fun testRethrowingGradleException() { + val ex = assertFailsWith { rethrowAsUncheckedException(InvalidUserDataException(MESSAGE)) } + assertThat(ex) + .hasMessage(MESSAGE) + .hasNoCause() + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/annotations/Deletable.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/annotations/Deletable.kt new file mode 100644 index 0000000000..3c9597c75c --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/annotations/Deletable.kt @@ -0,0 +1,8 @@ +package net.corda.gradle.jarfilter.annotations + +import kotlin.annotation.AnnotationRetention.BINARY +import kotlin.annotation.AnnotationTarget.PROPERTY + +@Retention(BINARY) +@Target(PROPERTY) +annotation class Deletable diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/AsmTools.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/AsmTools.kt new file mode 100644 index 0000000000..4c488d9dec --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/AsmTools.kt @@ -0,0 +1,47 @@ +@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 ByteArray.toClass(): Class = toClass(T::class.java, R::class.java) + +fun ByteArray.toClass(type: Class, asType: Class): Class + = 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) + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/ClassMetadata.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/ClassMetadata.kt new file mode 100644 index 0000000000..b4852deaa7 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/ClassMetadata.kt @@ -0,0 +1,47 @@ +package net.corda.gradle.jarfilter.asm + +import net.corda.gradle.jarfilter.MetadataTransformer +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, + d2: List +) : MetadataTransformer( + logger, + emptyList(), + emptyList(), + emptyList(), + emptyList(), + emptyList(), + {}, + d1, + d2, + ProtoBuf.Class::parseFrom +) { + override val typeTable = TypeTable(message.typeTable) + override val className = nameResolver.getString(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 = sealedSubclassNames.map { + // Transform "a/b/c/BaseName.SubclassName" -> "a.b.c.BaseName$SubclassName" + nameResolver.getString(it).replace('.', '$').toPackageFormat }.toList() + + val nestedClasses: List + + init { + val internalClassName = className.toPackageFormat + nestedClasses = nestedClassNames.map { "$internalClassName\$${nameResolver.getString(it)}" }.toList() + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/FileMetadata.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/FileMetadata.kt new file mode 100644 index 0000000000..f7636c810c --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/FileMetadata.kt @@ -0,0 +1,33 @@ +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, + d2: List +) : MetadataTransformer( + 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 = typeAliases.map { nameResolver.getString(it.name) }.toList() +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/MetadataTools.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/MetadataTools.kt new file mode 100644 index 0000000000..5526c5056d --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/asm/MetadataTools.kt @@ -0,0 +1,86 @@ +@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 + = object {}.javaClass.classLoader.loadClass("kotlin.Metadata") as Class + +/** + * Rewrite the bytecode for this class with the Kotlin @Metadata of another class. + */ +inline fun recodeMetadataFor(): ByteArray = T::class.java.metadataAs(X::class.java) + +fun Class.metadataAs(template: Class): 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> { + 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 Any.asList(): List { + return (this as? Array)?.toList() ?: emptyList() +} + +private class MetadataWriter(metadata: Pair, List>, visitor: ClassVisitor) : ClassVisitor(ASM6, visitor) { + private val kotlinMetadata: MutableMap> = 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 + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/JavaMatchers.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/JavaMatchers.kt new file mode 100644 index 0000000000..25a82e6c8d --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/JavaMatchers.kt @@ -0,0 +1,79 @@ +@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, returnType: Matcher>, vararg parameters: Matcher>): Matcher { + return MethodMatcher(name, returnType, *parameters) +} + +fun isMethod(name: String, returnType: Class<*>, vararg parameters: Class<*>): Matcher { + return isMethod(equalTo(name), equalTo(returnType), *parameters.map(::equalTo).toTypedArray()) +} + +val KClass.javaDeclaredMethods: List get() = java.declaredMethods.toList() + +/** + * Matcher logic for a Java [Method] object. Also applicable to constructors. + */ +private class MethodMatcher( + private val name: Matcher, + private val returnType: Matcher>, + vararg parameters: Matcher> +) : DiagnosingMatcher() { + 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 + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/KotlinMatchers.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/KotlinMatchers.kt new file mode 100644 index 0000000000..2082ddc4dc --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/KotlinMatchers.kt @@ -0,0 +1,193 @@ +@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, returnType: Matcher, vararg parameters: Matcher): Matcher> { + return KFunctionMatcher(name, returnType, *parameters) +} + +fun isFunction(name: String, returnType: KClass<*>, vararg parameters: KClass<*>): Matcher> { + return isFunction(equalTo(name), matches(returnType), *parameters.map(::hasParam).toTypedArray()) +} + +fun isConstructor(returnType: Matcher, vararg parameters: Matcher): Matcher> { + return KFunctionMatcher(equalTo(""), returnType, *parameters) +} + +fun isConstructor(returnType: KClass<*>, vararg parameters: KClass<*>): Matcher> { + return isConstructor(matches(returnType), *parameters.map(::hasParam).toTypedArray()) +} + +fun isConstructor(returnType: String, vararg parameters: Matcher): Matcher> { + return isConstructor(equalTo(returnType), *parameters) +} + +fun hasParam(type: Matcher): Matcher = KParameterMatcher(type) + +fun hasParam(type: KClass<*>): Matcher = hasParam(matches(type)) + +fun isProperty(name: String, type: KClass<*>): Matcher> = isProperty(equalTo(name), matches(type)) + +fun isProperty(name: Matcher, type: Matcher): Matcher> = KPropertyMatcher(name, type) + +fun isClass(name: String): Matcher> = KClassMatcher(equalTo(name)) + +fun matches(type: KClass<*>): Matcher = equalTo(type.qualifiedName) + +/** + * Matcher logic for a Kotlin [KFunction] object. Also applicable to constructors. + */ +private class KFunctionMatcher( + private val name: Matcher, + private val returnType: Matcher, + vararg parameters: Matcher +) : DiagnosingMatcher>() { + 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 +) : DiagnosingMatcher() { + 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, + private val type: Matcher +) : DiagnosingMatcher>() { + 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) : DiagnosingMatcher>() { + 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 + } +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/resources/abstract-function/build.gradle b/buildSrc/jarfilter/src/test/resources/abstract-function/build.gradle new file mode 100644 index 0000000000..c67c80b303 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/abstract-function/build.gradle @@ -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/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"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/abstract-function/kotlin/net/corda/gradle/AbstractFunctions.kt b/buildSrc/jarfilter/src/test/resources/abstract-function/kotlin/net/corda/gradle/AbstractFunctions.kt new file mode 100644 index 0000000000..8d22e90854 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/abstract-function/kotlin/net/corda/gradle/AbstractFunctions.kt @@ -0,0 +1,13 @@ +@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 +} diff --git a/buildSrc/jarfilter/src/test/resources/annotations/kotlin/net/corda/gradle/jarfilter/DeleteMe.kt b/buildSrc/jarfilter/src/test/resources/annotations/kotlin/net/corda/gradle/jarfilter/DeleteMe.kt new file mode 100644 index 0000000000..60ea95420b --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/annotations/kotlin/net/corda/gradle/jarfilter/DeleteMe.kt @@ -0,0 +1,20 @@ +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 \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/resources/annotations/kotlin/net/corda/gradle/jarfilter/RemoveMe.kt b/buildSrc/jarfilter/src/test/resources/annotations/kotlin/net/corda/gradle/jarfilter/RemoveMe.kt new file mode 100644 index 0000000000..5023cff43a --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/annotations/kotlin/net/corda/gradle/jarfilter/RemoveMe.kt @@ -0,0 +1,19 @@ +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 diff --git a/buildSrc/jarfilter/src/test/resources/annotations/kotlin/net/corda/gradle/jarfilter/StubMeOut.kt b/buildSrc/jarfilter/src/test/resources/annotations/kotlin/net/corda/gradle/jarfilter/StubMeOut.kt new file mode 100644 index 0000000000..5d148dcb58 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/annotations/kotlin/net/corda/gradle/jarfilter/StubMeOut.kt @@ -0,0 +1,15 @@ +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 \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/resources/delete-and-stub/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-and-stub/build.gradle new file mode 100644 index 0000000000..8e356b4936 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-and-stub/build.gradle @@ -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/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"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-and-stub/kotlin/net/corda/gradle/DeletePackageWithStubbed.kt b/buildSrc/jarfilter/src/test/resources/delete-and-stub/kotlin/net/corda/gradle/DeletePackageWithStubbed.kt new file mode 100644 index 0000000000..b7d4baece2 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-and-stub/kotlin/net/corda/gradle/DeletePackageWithStubbed.kt @@ -0,0 +1,12 @@ +@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) diff --git a/buildSrc/jarfilter/src/test/resources/delete-and-stub/kotlin/net/corda/gradle/HasDeletedInsideStubbed.kt b/buildSrc/jarfilter/src/test/resources/delete-and-stub/kotlin/net/corda/gradle/HasDeletedInsideStubbed.kt new file mode 100644 index 0000000000..3083c2b4d8 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-and-stub/kotlin/net/corda/gradle/HasDeletedInsideStubbed.kt @@ -0,0 +1,28 @@ +@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 +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/resources/delete-and-stub/kotlin/net/corda/gradle/HasPropertyForDeleteAndStub.kt b/buildSrc/jarfilter/src/test/resources/delete-and-stub/kotlin/net/corda/gradle/HasPropertyForDeleteAndStub.kt new file mode 100644 index 0000000000..c40d183929 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-and-stub/kotlin/net/corda/gradle/HasPropertyForDeleteAndStub.kt @@ -0,0 +1,21 @@ +@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 +} + diff --git a/buildSrc/jarfilter/src/test/resources/delete-constructor/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-constructor/build.gradle new file mode 100644 index 0000000000..8503573c6c --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-constructor/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/delete-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"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-constructor/kotlin/net/corda/gradle/HasConstructorToDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-constructor/kotlin/net/corda/gradle/HasConstructorToDelete.kt new file mode 100644 index 0000000000..6e142646d1 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-constructor/kotlin/net/corda/gradle/HasConstructorToDelete.kt @@ -0,0 +1,15 @@ +@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("", data) + constructor(data: Int) : this("", data.toLong()) + + override fun stringData(): String = message + override fun longData(): Long = data + override fun intData(): Int = data.toInt() +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-constructor/kotlin/net/corda/gradle/PrimaryConstructorsToDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-constructor/kotlin/net/corda/gradle/PrimaryConstructorsToDelete.kt new file mode 100644 index 0000000000..4be16cd208 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-constructor/kotlin/net/corda/gradle/PrimaryConstructorsToDelete.kt @@ -0,0 +1,21 @@ +@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 +} + diff --git a/buildSrc/jarfilter/src/test/resources/delete-extension-val/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-extension-val/build.gradle new file mode 100644 index 0000000000..2bc54db421 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-extension-val/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/delete-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"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-extension-val/kotlin/net/corda/gradle/HasValExtension.kt b/buildSrc/jarfilter/src/test/resources/delete-extension-val/kotlin/net/corda/gradle/HasValExtension.kt new file mode 100644 index 0000000000..f1219ab1ff --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-extension-val/kotlin/net/corda/gradle/HasValExtension.kt @@ -0,0 +1,10 @@ +@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.unwantedVal: String get() = this[0] +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-field/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-field/build.gradle new file mode 100644 index 0000000000..92dd13a5f2 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-field/build.gradle @@ -0,0 +1,32 @@ +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"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-field/kotlin/net/corda/gradle/HasFieldToDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-field/kotlin/net/corda/gradle/HasFieldToDelete.kt new file mode 100644 index 0000000000..d3127ce7d7 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-field/kotlin/net/corda/gradle/HasFieldToDelete.kt @@ -0,0 +1,23 @@ +@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 +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-file-typealias/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-file-typealias/build.gradle new file mode 100644 index 0000000000..f4de2588c8 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-file-typealias/build.gradle @@ -0,0 +1,32 @@ +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"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-file-typealias/kotlin/net/corda/gradle/FileWithTypeAlias.kt b/buildSrc/jarfilter/src/test/resources/delete-file-typealias/kotlin/net/corda/gradle/FileWithTypeAlias.kt new file mode 100644 index 0000000000..500e6b9be8 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-file-typealias/kotlin/net/corda/gradle/FileWithTypeAlias.kt @@ -0,0 +1,12 @@ +@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() = "" diff --git a/buildSrc/jarfilter/src/test/resources/delete-function/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-function/build.gradle new file mode 100644 index 0000000000..5a5de263ad --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-function/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/delete-function/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'delete-function' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-function/kotlin/net/corda/gradle/HasFunctionToDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-function/kotlin/net/corda/gradle/HasFunctionToDelete.kt new file mode 100644 index 0000000000..81997e0857 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-function/kotlin/net/corda/gradle/HasFunctionToDelete.kt @@ -0,0 +1,12 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe +import net.corda.gradle.unwanted.HasUnwantedFun + +class HasFunctionToDelete : HasUnwantedFun { + @DeleteMe + override fun unwantedFun(str: String): String { + return str + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-function/kotlin/net/corda/gradle/HasIndirectFunctionToDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-function/kotlin/net/corda/gradle/HasIndirectFunctionToDelete.kt new file mode 100644 index 0000000000..5a85f74d8e --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-function/kotlin/net/corda/gradle/HasIndirectFunctionToDelete.kt @@ -0,0 +1,13 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe +import net.corda.gradle.unwanted.HasString +import net.corda.gradle.unwanted.HasUnwantedFun + +class HasIndirectFunctionToDelete(private val data: String) : HasUnwantedFun, HasString { + @DeleteMe + override fun unwantedFun(str: String): String = str + + override fun stringData() = unwantedFun(data) +} \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/resources/delete-lazy/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-lazy/build.gradle new file mode 100644 index 0000000000..74de278f40 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-lazy/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/delete-lazy/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'delete-lazy' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-lazy/kotlin/net/corda/gradle/HasLazy.kt b/buildSrc/jarfilter/src/test/resources/delete-lazy/kotlin/net/corda/gradle/HasLazy.kt new file mode 100644 index 0000000000..ad824cab4d --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-lazy/kotlin/net/corda/gradle/HasLazy.kt @@ -0,0 +1,13 @@ +@file:JvmName("HasLazy") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe +import net.corda.gradle.unwanted.HasUnwantedVal + +class HasLazyVal(private val message: String) : HasUnwantedVal { + @DeleteMe + override val unwantedVal: String by lazy { + message + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-multifile/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-multifile/build.gradle new file mode 100644 index 0000000000..417c8d97fe --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-multifile/build.gradle @@ -0,0 +1,32 @@ +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-multifile/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +} + +jar { + baseName = 'delete-multifile' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-multifile/kotlin/net/corda/gradle/HasInt.kt b/buildSrc/jarfilter/src/test/resources/delete-multifile/kotlin/net/corda/gradle/HasInt.kt new file mode 100644 index 0000000000..0c4507d568 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-multifile/kotlin/net/corda/gradle/HasInt.kt @@ -0,0 +1,9 @@ +@file:JvmName("HasMultiData") +@file:JvmMultifileClass +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe + +@DeleteMe +fun intToDelete(data: Int): Int = data diff --git a/buildSrc/jarfilter/src/test/resources/delete-multifile/kotlin/net/corda/gradle/HasLong.kt b/buildSrc/jarfilter/src/test/resources/delete-multifile/kotlin/net/corda/gradle/HasLong.kt new file mode 100644 index 0000000000..c4c6617e33 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-multifile/kotlin/net/corda/gradle/HasLong.kt @@ -0,0 +1,9 @@ +@file:JvmName("HasMultiData") +@file:JvmMultifileClass +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe + +@DeleteMe +fun longToDelete(data: Long): Long = data diff --git a/buildSrc/jarfilter/src/test/resources/delete-multifile/kotlin/net/corda/gradle/HasString.kt b/buildSrc/jarfilter/src/test/resources/delete-multifile/kotlin/net/corda/gradle/HasString.kt new file mode 100644 index 0000000000..3fe9965c2f --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-multifile/kotlin/net/corda/gradle/HasString.kt @@ -0,0 +1,9 @@ +@file:JvmName("HasMultiData") +@file:JvmMultifileClass +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe + +@DeleteMe +fun stringToDelete(str: String): String = str diff --git a/buildSrc/jarfilter/src/test/resources/delete-nested-class/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-nested-class/build.gradle new file mode 100644 index 0000000000..9a74a8e3c3 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-nested-class/build.gradle @@ -0,0 +1,32 @@ +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-nested-class/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +} + +jar { + baseName = 'delete-nested-class' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-nested-class/kotlin/net/corda/gradle/HasNestedClasses.kt b/buildSrc/jarfilter/src/test/resources/delete-nested-class/kotlin/net/corda/gradle/HasNestedClasses.kt new file mode 100644 index 0000000000..a8f8a2f2f4 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-nested-class/kotlin/net/corda/gradle/HasNestedClasses.kt @@ -0,0 +1,10 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe + +class HasNestedClasses { + class OneToKeep + + @DeleteMe class OneToThrowAway +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-nested-class/kotlin/net/corda/gradle/SealedClass.kt b/buildSrc/jarfilter/src/test/resources/delete-nested-class/kotlin/net/corda/gradle/SealedClass.kt new file mode 100644 index 0000000000..1e85d6564c --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-nested-class/kotlin/net/corda/gradle/SealedClass.kt @@ -0,0 +1,10 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe + +sealed class SealedClass { + class Wanted : SealedClass() + + @DeleteMe class Unwanted : SealedClass() +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-object/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-object/build.gradle new file mode 100644 index 0000000000..9958740aad --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-object/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/delete-object/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'delete-object' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-object/kotlin/net/corda/gradle/HasObjects.kt b/buildSrc/jarfilter/src/test/resources/delete-object/kotlin/net/corda/gradle/HasObjects.kt new file mode 100644 index 0000000000..03ec14bcb4 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-object/kotlin/net/corda/gradle/HasObjects.kt @@ -0,0 +1,20 @@ +@file:JvmName("HasObjects") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe +import net.corda.gradle.unwanted.HasUnwantedFun +import net.corda.gradle.unwanted.HasUnwantedVal + +@DeleteMe +val unwantedObj = object : HasUnwantedFun { + override fun unwantedFun(str: String): String = str +} + +@DeleteMe +fun unwantedFun(): String { + val obj = object : HasUnwantedVal { + override val unwantedVal: String = "" + } + return obj.unwantedVal +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-sealed-subclass/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-sealed-subclass/build.gradle new file mode 100644 index 0000000000..38c76d6f7c --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-sealed-subclass/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/delete-sealed-subclass/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'delete-sealed-subclass' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-sealed-subclass/kotlin/net/corda/gradle/SealedWithSubclasses.kt b/buildSrc/jarfilter/src/test/resources/delete-sealed-subclass/kotlin/net/corda/gradle/SealedWithSubclasses.kt new file mode 100644 index 0000000000..52e28a4475 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-sealed-subclass/kotlin/net/corda/gradle/SealedWithSubclasses.kt @@ -0,0 +1,12 @@ +@file:JvmName("SealedWithSubclasses") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe + +sealed class SealedBaseClass + +@DeleteMe +class UnwantedSubclass : SealedBaseClass() + +class WantedSubclass : SealedBaseClass() diff --git a/buildSrc/jarfilter/src/test/resources/delete-static-field/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-static-field/build.gradle new file mode 100644 index 0000000000..18040b9954 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-static-field/build.gradle @@ -0,0 +1,32 @@ +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-static-field/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +} + +jar { + baseName = 'delete-static-field' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-static-field/kotlin/net/corda/gradle/StaticFieldsToDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-static-field/kotlin/net/corda/gradle/StaticFieldsToDelete.kt new file mode 100644 index 0000000000..a16350d5d4 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-static-field/kotlin/net/corda/gradle/StaticFieldsToDelete.kt @@ -0,0 +1,17 @@ +@file:JvmName("StaticFieldsToDelete") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe + +@DeleteMe +@JvmField +val stringField: String = "" + +@DeleteMe +@JvmField +val longField: Long = 123456789L + +@DeleteMe +@JvmField +val intField: Int = 123456 diff --git a/buildSrc/jarfilter/src/test/resources/delete-static-function/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-static-function/build.gradle new file mode 100644 index 0000000000..b2308fad37 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-static-function/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/delete-static-function/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'delete-static-function' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-static-function/kotlin/net/corda/gradle/StaticFunctionsToDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-static-function/kotlin/net/corda/gradle/StaticFunctionsToDelete.kt new file mode 100644 index 0000000000..a9144140c0 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-static-function/kotlin/net/corda/gradle/StaticFunctionsToDelete.kt @@ -0,0 +1,14 @@ +@file:JvmName("StaticFunctionsToDelete") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe + +@DeleteMe +fun unwantedStringToDelete(value: String): String = value + +@DeleteMe +fun unwantedIntToDelete(value: Int): Int = value + +@DeleteMe +fun unwantedLongToDelete(value: Long): Long = value diff --git a/buildSrc/jarfilter/src/test/resources/delete-static-val/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-static-val/build.gradle new file mode 100644 index 0000000000..a67c4f7760 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-static-val/build.gradle @@ -0,0 +1,32 @@ +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-static-val/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +} + +jar { + baseName = 'delete-static-val' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-static-val/kotlin/net/corda/gradle/StaticValToDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-static-val/kotlin/net/corda/gradle/StaticValToDelete.kt new file mode 100644 index 0000000000..51de79e5c5 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-static-val/kotlin/net/corda/gradle/StaticValToDelete.kt @@ -0,0 +1,17 @@ +@file:JvmName("StaticValToDelete") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe + +@DeleteMe +val stringVal: String = "" + +@DeleteMe +val longVal: Long = 123456789L + +@DeleteMe +val intVal: Int = 123456 + +@DeleteMe +val T.memberVal: T get() = this diff --git a/buildSrc/jarfilter/src/test/resources/delete-static-var/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-static-var/build.gradle new file mode 100644 index 0000000000..2d601a37c4 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-static-var/build.gradle @@ -0,0 +1,32 @@ +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-static-var/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +} + +jar { + baseName = 'delete-static-var' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-static-var/kotlin/net/corda/gradle/StaticVarToDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-static-var/kotlin/net/corda/gradle/StaticVarToDelete.kt new file mode 100644 index 0000000000..1b941b8fff --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-static-var/kotlin/net/corda/gradle/StaticVarToDelete.kt @@ -0,0 +1,19 @@ +@file:JvmName("StaticVarToDelete") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe + +@DeleteMe +var stringVar: String = "" + +@DeleteMe +var longVar: Long = 123456789L + +@DeleteMe +var intVar: Int = 123456 + +@DeleteMe +var T.memberVar: T + get() = this + set(value) { } diff --git a/buildSrc/jarfilter/src/test/resources/delete-val-property/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-val-property/build.gradle new file mode 100644 index 0000000000..3d6f3d4224 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-val-property/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/delete-val-property/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'delete-val-property' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-val-property/kotlin/net/corda/gradle/HasValPropertyForDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-val-property/kotlin/net/corda/gradle/HasValPropertyForDelete.kt new file mode 100644 index 0000000000..26f9ebfad3 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-val-property/kotlin/net/corda/gradle/HasValPropertyForDelete.kt @@ -0,0 +1,11 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe +import net.corda.gradle.unwanted.HasUnwantedVal + +class HasValPropertyForDelete(@DeleteMe override val unwantedVal: String) : HasUnwantedVal + +class HasValGetterForDelete(@get:DeleteMe override val unwantedVal: String): HasUnwantedVal + +class HasValJvmFieldForDelete(@DeleteMe @JvmField val unwantedVal: String) \ No newline at end of file diff --git a/buildSrc/jarfilter/src/test/resources/delete-var-property/build.gradle b/buildSrc/jarfilter/src/test/resources/delete-var-property/build.gradle new file mode 100644 index 0000000000..dfc29b78b6 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-var-property/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/delete-var-property/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'delete-var-property' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/delete-var-property/kotlin/net/corda/gradle/HasVarPropertyForDelete.kt b/buildSrc/jarfilter/src/test/resources/delete-var-property/kotlin/net/corda/gradle/HasVarPropertyForDelete.kt new file mode 100644 index 0000000000..4726ef9e3a --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/delete-var-property/kotlin/net/corda/gradle/HasVarPropertyForDelete.kt @@ -0,0 +1,14 @@ +@file:JvmName("HasVarPropertyForDelete") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe +import net.corda.gradle.unwanted.HasUnwantedVar + +class HasUnwantedVarPropertyForDelete(@DeleteMe override var unwantedVar: String) : HasUnwantedVar + +class HasUnwantedGetForDelete(@get:DeleteMe override var unwantedVar: String) : HasUnwantedVar + +class HasUnwantedSetForDelete(@set:DeleteMe override var unwantedVar: String) : HasUnwantedVar + +class HasVarJvmFieldForDelete(@DeleteMe @JvmField var unwantedVar: String) diff --git a/buildSrc/jarfilter/src/test/resources/gradle.properties b/buildSrc/jarfilter/src/test/resources/gradle.properties new file mode 100644 index 0000000000..0c744a8a05 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-javaagent:"$jacocoAgent"=destfile="$buildDir/jacoco/test.exec",includes=net/corda/gradle/jarfilter/** diff --git a/buildSrc/jarfilter/src/test/resources/interface-function/build.gradle b/buildSrc/jarfilter/src/test/resources/interface-function/build.gradle new file mode 100644 index 0000000000..dbea447e6b --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/interface-function/build.gradle @@ -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/interface-function/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'interface-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"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/interface-function/kotlin/net/corda/gradle/InterfaceFunctions.kt b/buildSrc/jarfilter/src/test/resources/interface-function/kotlin/net/corda/gradle/InterfaceFunctions.kt new file mode 100644 index 0000000000..cbc782794a --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/interface-function/kotlin/net/corda/gradle/InterfaceFunctions.kt @@ -0,0 +1,13 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe +import net.corda.gradle.jarfilter.StubMeOut + +interface InterfaceFunctions { + @DeleteMe + fun toDelete(value: Long): Long + + @StubMeOut + fun toStubOut(value: Long): Long +} diff --git a/buildSrc/jarfilter/src/test/resources/remove-annotations/build.gradle b/buildSrc/jarfilter/src/test/resources/remove-annotations/build.gradle new file mode 100644 index 0000000000..e477038ce9 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/remove-annotations/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/remove-annotations/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'remove-annotations' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forRemove = ["net.corda.gradle.jarfilter.RemoveMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/remove-annotations/kotlin/net/corda/gradle/HasUnwantedAnnotations.kt b/buildSrc/jarfilter/src/test/resources/remove-annotations/kotlin/net/corda/gradle/HasUnwantedAnnotations.kt new file mode 100644 index 0000000000..e0732943ee --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/remove-annotations/kotlin/net/corda/gradle/HasUnwantedAnnotations.kt @@ -0,0 +1,33 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.RemoveMe +import net.corda.gradle.unwanted.HasUnwantedFun +import net.corda.gradle.unwanted.HasUnwantedVal +import net.corda.gradle.unwanted.HasUnwantedVar + +@RemoveMe +class HasUnwantedAnnotations @RemoveMe constructor( + longValue: Long, message: String +) : HasUnwantedVar, HasUnwantedVal, HasUnwantedFun { + @RemoveMe + constructor() : this(999L, "") + + @field:RemoveMe + @JvmField + val longField: Long = longValue + + @get:RemoveMe + @property:RemoveMe + override val unwantedVal: String = message + + @get:RemoveMe + @set:RemoveMe + @property:RemoveMe + override var unwantedVar: String = message + + @RemoveMe + override fun unwantedFun(str: String): String { + return "[$str]" + } +} diff --git a/buildSrc/jarfilter/src/test/resources/repositories.gradle b/buildSrc/jarfilter/src/test/resources/repositories.gradle new file mode 100644 index 0000000000..2a25f5bbd4 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/repositories.gradle @@ -0,0 +1,4 @@ +repositories { + mavenLocal() + jcenter() +} diff --git a/buildSrc/jarfilter/src/test/resources/settings.gradle b/buildSrc/jarfilter/src/test/resources/settings.gradle new file mode 100644 index 0000000000..7e0a88c8e3 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/settings.gradle @@ -0,0 +1,6 @@ +// Common settings for all Gradle test projects. +pluginManagement { + repositories { + gradlePluginPortal() + } +} diff --git a/buildSrc/jarfilter/src/test/resources/stub-constructor/build.gradle b/buildSrc/jarfilter/src/test/resources/stub-constructor/build.gradle new file mode 100644 index 0000000000..857ce08d50 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-constructor/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/stub-constructor/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'stub-constructor' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forStub = ["net.corda.gradle.jarfilter.StubMeOut"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/stub-constructor/kotlin/net/corda/gradle/HasConstructorToStub.kt b/buildSrc/jarfilter/src/test/resources/stub-constructor/kotlin/net/corda/gradle/HasConstructorToStub.kt new file mode 100644 index 0000000000..5683a97243 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-constructor/kotlin/net/corda/gradle/HasConstructorToStub.kt @@ -0,0 +1,15 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.StubMeOut +import net.corda.gradle.unwanted.HasAll + +class HasConstructorToStub(private val message: String, private val data: Long) : HasAll { + @StubMeOut constructor(message: String) : this(message, 0) + @StubMeOut constructor(data: Long) : this("", data) + constructor(data: Int) : this("", data.toLong()) + + override fun stringData(): String = message + override fun longData(): Long = data + override fun intData(): Int = data.toInt() +} diff --git a/buildSrc/jarfilter/src/test/resources/stub-constructor/kotlin/net/corda/gradle/PrimaryConstructorsToStub.kt b/buildSrc/jarfilter/src/test/resources/stub-constructor/kotlin/net/corda/gradle/PrimaryConstructorsToStub.kt new file mode 100644 index 0000000000..1e25467643 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-constructor/kotlin/net/corda/gradle/PrimaryConstructorsToStub.kt @@ -0,0 +1,21 @@ +@file:JvmName("PrimaryConstructorsToStub") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.StubMeOut +import net.corda.gradle.unwanted.HasInt +import net.corda.gradle.unwanted.HasLong +import net.corda.gradle.unwanted.HasString + +class PrimaryIntConstructorToStub @StubMeOut constructor(private val value: Int) : HasInt { + override fun intData() = value +} + +class PrimaryLongConstructorToStub @StubMeOut constructor(private val value: Long) : HasLong { + override fun longData() = value +} + +class PrimaryStringConstructorToStub @StubMeOut constructor(private val value: String) : HasString { + override fun stringData() = value +} + diff --git a/buildSrc/jarfilter/src/test/resources/stub-function/build.gradle b/buildSrc/jarfilter/src/test/resources/stub-function/build.gradle new file mode 100644 index 0000000000..98a3d6275a --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-function/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/stub-function/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'stub-function' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forStub = ["net.corda.gradle.jarfilter.StubMeOut"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/stub-function/kotlin/net/corda/gradle/HasFunctionToStub.kt b/buildSrc/jarfilter/src/test/resources/stub-function/kotlin/net/corda/gradle/HasFunctionToStub.kt new file mode 100644 index 0000000000..32c3a17533 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-function/kotlin/net/corda/gradle/HasFunctionToStub.kt @@ -0,0 +1,14 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.StubMeOut +import net.corda.gradle.unwanted.HasUnwantedFun +import javax.annotation.Resource + +class HasFunctionToStub : HasUnwantedFun { + @StubMeOut + @Resource + override fun unwantedFun(@Parameter str: String): String { + return str + } +} diff --git a/buildSrc/jarfilter/src/test/resources/stub-function/kotlin/net/corda/gradle/RuntimeAnnotations.kt b/buildSrc/jarfilter/src/test/resources/stub-function/kotlin/net/corda/gradle/RuntimeAnnotations.kt new file mode 100644 index 0000000000..ef1e66b89b --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-function/kotlin/net/corda/gradle/RuntimeAnnotations.kt @@ -0,0 +1,11 @@ +@file:JvmName("RuntimeAnnotations") +package net.corda.gradle + +import kotlin.annotation.AnnotationRetention.* +import kotlin.annotation.AnnotationTarget.* +import kotlin.annotation.Retention +import kotlin.annotation.Target + +@Target(VALUE_PARAMETER) +@Retention(RUNTIME) +annotation class Parameter diff --git a/buildSrc/jarfilter/src/test/resources/stub-static-function/build.gradle b/buildSrc/jarfilter/src/test/resources/stub-static-function/build.gradle new file mode 100644 index 0000000000..75eb1e2cfe --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-static-function/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/stub-static-function/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'stub-static-function' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forStub = ["net.corda.gradle.jarfilter.StubMeOut"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/stub-static-function/kotlin/net/corda/gradle/StaticFunctionsToStub.kt b/buildSrc/jarfilter/src/test/resources/stub-static-function/kotlin/net/corda/gradle/StaticFunctionsToStub.kt new file mode 100644 index 0000000000..ffeece6a04 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-static-function/kotlin/net/corda/gradle/StaticFunctionsToStub.kt @@ -0,0 +1,22 @@ +@file:JvmName("StaticFunctionsToStub") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.StubMeOut + +@StubMeOut +fun unwantedStringToStub(value: String): String = value + +@StubMeOut +fun unwantedIntToStub(value: Int): Int = value + +@StubMeOut +fun unwantedLongToStub(value: Long): Long = value + +private var seed: Int = 0 +val staticSeed: Int get() = seed + +@StubMeOut +fun unwantedVoidToStub() { + ++seed +} diff --git a/buildSrc/jarfilter/src/test/resources/stub-val-property/build.gradle b/buildSrc/jarfilter/src/test/resources/stub-val-property/build.gradle new file mode 100644 index 0000000000..e92e2b68c0 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-val-property/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/stub-val-property/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'stub-val-property' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forStub = ["net.corda.gradle.jarfilter.StubMeOut"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/stub-val-property/kotlin/net/corda/gradle/HasValPropertyForStub.kt b/buildSrc/jarfilter/src/test/resources/stub-val-property/kotlin/net/corda/gradle/HasValPropertyForStub.kt new file mode 100644 index 0000000000..b9a6c042b4 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-val-property/kotlin/net/corda/gradle/HasValPropertyForStub.kt @@ -0,0 +1,7 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.StubMeOut +import net.corda.gradle.unwanted.HasUnwantedVal + +class HasValPropertyForStub(@get:StubMeOut override val unwantedVal: String) : HasUnwantedVal diff --git a/buildSrc/jarfilter/src/test/resources/stub-var-property/build.gradle b/buildSrc/jarfilter/src/test/resources/stub-var-property/build.gradle new file mode 100644 index 0000000000..d02be34534 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-var-property/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/stub-var-property/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'stub-var-property' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forStub = ["net.corda.gradle.jarfilter.StubMeOut"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/stub-var-property/kotlin/net/corda/gradle/HasVarPropertyForStub.kt b/buildSrc/jarfilter/src/test/resources/stub-var-property/kotlin/net/corda/gradle/HasVarPropertyForStub.kt new file mode 100644 index 0000000000..8f346142b0 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/stub-var-property/kotlin/net/corda/gradle/HasVarPropertyForStub.kt @@ -0,0 +1,10 @@ +@file:JvmName("HasVarPropertyForStub") +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.StubMeOut +import net.corda.gradle.unwanted.HasUnwantedVar + +class HasUnwantedGetForStub(@get:StubMeOut override var unwantedVar: String) : HasUnwantedVar + +class HasUnwantedSetForStub(@set:StubMeOut override var unwantedVar: String) : HasUnwantedVar \ No newline at end of file diff --git a/buildSrc/jarfilter/unwanteds/build.gradle b/buildSrc/jarfilter/unwanteds/build.gradle new file mode 100644 index 0000000000..0f2deeb1c6 --- /dev/null +++ b/buildSrc/jarfilter/unwanteds/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'kotlin' + +description 'Test artifacts for the jar-filter plugin.' + +repositories { + mavenLocal() + jcenter() +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" +} diff --git a/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasData.kt b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasData.kt new file mode 100644 index 0000000000..87e52bbbf7 --- /dev/null +++ b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasData.kt @@ -0,0 +1,16 @@ +@file:JvmName("HasData") +package net.corda.gradle.unwanted + +interface HasString { + fun stringData(): String +} + +interface HasLong { + fun longData(): Long +} + +interface HasInt { + fun intData(): Int +} + +interface HasAll : HasInt, HasLong, HasString \ No newline at end of file diff --git a/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasUnwantedFun.kt b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasUnwantedFun.kt new file mode 100644 index 0000000000..f66b53c8b7 --- /dev/null +++ b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasUnwantedFun.kt @@ -0,0 +1,5 @@ +package net.corda.gradle.unwanted + +interface HasUnwantedFun { + fun unwantedFun(str: String): String +} diff --git a/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasUnwantedVal.kt b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasUnwantedVal.kt new file mode 100644 index 0000000000..b19d043345 --- /dev/null +++ b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasUnwantedVal.kt @@ -0,0 +1,5 @@ +package net.corda.gradle.unwanted + +interface HasUnwantedVal { + val unwantedVal: String +} diff --git a/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasUnwantedVar.kt b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasUnwantedVar.kt new file mode 100644 index 0000000000..7406028cb8 --- /dev/null +++ b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasUnwantedVar.kt @@ -0,0 +1,5 @@ +package net.corda.gradle.unwanted + +interface HasUnwantedVar { + var unwantedVar: String +} diff --git a/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasVal.kt b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasVal.kt new file mode 100644 index 0000000000..7aeb75ca35 --- /dev/null +++ b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasVal.kt @@ -0,0 +1,17 @@ +@file:JvmName("HasVal") +@file:Suppress("UNUSED") +package net.corda.gradle.unwanted + +interface HasStringVal { + val stringVal: String +} + +interface HasLongVal { + val longVal: Long +} + +interface HasIntVal { + val intVal: Int +} + +interface HasAllVal : HasIntVal, HasLongVal, HasStringVal \ No newline at end of file diff --git a/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasVar.kt b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasVar.kt new file mode 100644 index 0000000000..74fa87062c --- /dev/null +++ b/buildSrc/jarfilter/unwanteds/src/main/kotlin/net/corda/gradle/unwanted/HasVar.kt @@ -0,0 +1,17 @@ +@file:JvmName("HasVar") +@file:Suppress("UNUSED") +package net.corda.gradle.unwanted + +interface HasStringVar { + var stringVar: String +} + +interface HasLongVar { + var longVar: Long +} + +interface HasIntVar { + var intVar: Int +} + +interface HasAllVar : HasIntVar, HasLongVar, HasStringVar diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle index c46d96de90..e493d16467 100644 --- a/buildSrc/settings.gradle +++ b/buildSrc/settings.gradle @@ -1,2 +1,5 @@ rootProject.name = 'buildSrc' include 'canonicalizer' +include 'jarfilter' +include 'jarfilter:unwanteds' +include 'jarfilter:kotlin-metadata' diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt index dfb5f7f1fb..d6538d1ffc 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt @@ -22,10 +22,10 @@ class AMQPClientSerializationScheme( cordappCustomSerializers: Set>, serializerFactoriesForContexts: MutableMap, SerializerFactory> ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { - constructor(cordapps: List) : this(cordapps.customSerializers, ConcurrentHashMap()) + constructor(cordapps: List) : this(cordapps.customSerializers, ConcurrentHashMap()) - @Suppress("UNUSED") - constructor() : this(emptySet(), ConcurrentHashMap()) + @Suppress("UNUSED") + constructor() : this(emptySet(), ConcurrentHashMap()) companion object { /** Call from main only. */ diff --git a/constants.properties b/constants.properties index 5ac0a4a997..52144277de 100644 --- a/constants.properties +++ b/constants.properties @@ -1,7 +1,8 @@ -gradlePluginsVersion=4.0.20 +gradlePluginsVersion=4.0.23 kotlinVersion=1.2.41 platformVersion=4 guavaVersion=21.0 +proguardVersion=6.0.3 bouncycastleVersion=1.57 typesafeConfigVersion=1.3.1 jsr305Version=3.0.2 diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle new file mode 100644 index 0000000000..8a78dd5c51 --- /dev/null +++ b/core-deterministic/build.gradle @@ -0,0 +1,199 @@ +description 'Corda core (deterministic)' + +apply plugin: 'kotlin' +apply plugin: 'com.jfrog.artifactory' +apply plugin: 'net.corda.plugins.publish-utils' + +evaluationDependsOn(':jdk8u-deterministic') +evaluationDependsOn(":core") + +def javaHome = System.getProperty('java.home') +def jarBaseName = "corda-${project.name}".toString() +def jdkTask = project(':jdk8u-deterministic').assemble +def deterministic_jdk_home = project(':jdk8u-deterministic').jdk_home + +configurations { + runtimeLibraries + runtimeArtifacts.extendsFrom runtimeLibraries +} + +dependencies { + compileOnly project(':core') + compileOnly "com.google.guava:guava:$guava_version" + compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8" + + // Configure these by hand. It should be a minimal subset of core's dependencies, + // and without any obviously non-deterministic ones such as Hibernate. + runtimeLibraries "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + runtimeLibraries "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + runtimeLibraries "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final" + runtimeLibraries "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version" + runtimeLibraries "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version" + runtimeLibraries "com.google.code.findbugs:jsr305:$jsr305_version" + runtimeLibraries "com.google.guava:guava:$guava_version" + runtimeLibraries "net.i2p.crypto:eddsa:$eddsa_version" + runtimeLibraries "org.slf4j:slf4j-api:$slf4j_version" +} + +tasks.withType(AbstractCompile) { + dependsOn jdkTask +} + +tasks.withType(JavaCompile) { + options.compilerArgs << '-bootclasspath' << "$deterministic_jdk_home/jre/lib/rt.jar".toString() +} + +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { + kotlinOptions.jdkHome = deterministic_jdk_home +} + +jar { + baseName 'DOES-NOT-EXIST' + // Don't build a jar here because it would be the wrong one. + // The jar we really want will be built by the metafix task. + enabled = false +} + +def coreJarTask = tasks.getByPath(':core:jar') +def originalJar = coreJarTask.outputs.files.singleFile + +task patchCore(type: Zip, dependsOn: coreJarTask) { + destinationDir file("$buildDir/source-libs") + metadataCharset 'UTF-8' + classifier 'transient' + extension 'jar' + + from(compileKotlin) + from(zipTree(originalJar)) { + exclude 'net/corda/core/internal/*ToggleField*.class' + exclude 'net/corda/core/serialization/*SerializationFactory*.class' + } + + reproducibleFileOrder = true + includeEmptyDirs = false +} + +import proguard.gradle.ProGuardTask +task predeterminise(type: ProGuardTask) { + injars patchCore + outjars "$buildDir/proguard/pre-deterministic-${project.version}.jar" + + libraryjars "$javaHome/lib/rt.jar" + libraryjars "$javaHome/lib/jce.jar" + configurations.compileOnly.forEach { + if (originalJar.path != it.path) { + libraryjars it.path, filter: '!META-INF/versions/**' + } + } + + keepattributes '*' + keepdirectories + dontwarn '**$1$1' + dontpreverify + dontobfuscate + dontoptimize + printseeds + verbose + + keep '@interface net.corda.core.* { *; }' + keep '@interface net.corda.core.contracts.** { *; }' + keep '@interface net.corda.core.serialization.** { *; }' + keep '@net.corda.core.KeepForDJVM class * { *; }', includedescriptorclasses:true + keepclassmembers 'class net.corda.core.** { public synthetic ; }' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars predeterminise + annotations { + forDelete = [ + "net.corda.core.DeleteForDJVM" + ] + forStub = [ + "net.corda.core.StubOutForDJVM" + ] + forRemove = [ + "co.paralleluniverse.fibers.Suspendable" + ] + } +} + +task determinise(type: ProGuardTask) { + injars jarFilter + outjars "$buildDir/proguard/$jarBaseName-${project.version}.jar" + + libraryjars "$javaHome/lib/rt.jar" + libraryjars "$javaHome/lib/jce.jar" + configurations.runtimeLibraries.forEach { + libraryjars it.path, filter: '!META-INF/versions/**' + } + + // Analyse the JAR for dead code, and remove (some of) it. + optimizations 'code/removal/simple,code/removal/advanced' + printconfiguration + + keepattributes '*' + keepdirectories + dontobfuscate + printseeds + verbose + + keep '@interface net.corda.core.CordaInternal { *; }' + keep '@interface net.corda.core.DoNotImplement { *; }' + keep '@interface net.corda.core.KeepForDJVM { *; }' + keep '@interface net.corda.core.contracts.** { *; }' + keep '@interface net.corda.core.serialization.** { *; }' + keep '@net.corda.core.KeepForDJVM class * { *; }', includedescriptorclasses:true + keepclassmembers 'class net.corda.core.** { public synthetic ; }' +} + +import net.corda.gradle.jarfilter.MetaFixerTask +task metafix(type: MetaFixerTask) { + outputDir file("$buildDir/libs") + jars determinise + suffix "" + + // Strip timestamps from the JAR to make it reproducible. + preserveTimestamps = false +} + +// DOCSTART 01 +task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) { + injars metafix + + libraryjars "$deterministic_jdk_home/jre/lib/rt.jar" + + configurations.runtimeLibraries.forEach { + libraryjars it.path, filter: '!META-INF/versions/**' + } + + keepattributes '*' + dontpreverify + dontobfuscate + dontoptimize + verbose + + keep 'class *' +} +// DOCEND 01 + +defaultTasks "determinise" +determinise.finalizedBy metafix +metafix.finalizedBy checkDeterminism +assemble.dependsOn checkDeterminism + +def deterministicJar = metafix.outputs.files.singleFile +artifacts { + runtimeArtifacts file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix + publish file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix +} + +publish { + dependenciesFrom configurations.runtimeArtifacts + publishSources = false + publishJavadoc = false + name jarBaseName +} + +// Must be after publish {} so that the previous install task exists for overwriting. +task install(overwrite: true, dependsOn: 'publishToMavenLocal') diff --git a/core-deterministic/src/main/kotlin/net/corda/core/internal/ToggleField.kt b/core-deterministic/src/main/kotlin/net/corda/core/internal/ToggleField.kt new file mode 100644 index 0000000000..1912bd895e --- /dev/null +++ b/core-deterministic/src/main/kotlin/net/corda/core/internal/ToggleField.kt @@ -0,0 +1,62 @@ +package net.corda.core.internal + +import net.corda.core.KeepForDJVM +import net.corda.core.utilities.contextLogger +import org.slf4j.Logger +import kotlin.reflect.KProperty + +/** May go from null to non-null and vice-versa, and that's it. */ +abstract class ToggleField(val name: String) { + abstract fun get(): T? + fun set(value: T?) { + if (value != null) { + check(get() == null) { "$name already has a value." } + setImpl(value) + } else { + check(get() != null) { "$name is already null." } + clear() + } + } + + protected abstract fun setImpl(value: T) + protected abstract fun clear() + operator fun getValue(thisRef: Any?, property: KProperty<*>) = get() + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) = set(value) +} + +@KeepForDJVM +class SimpleToggleField(name: String, private val once: Boolean = false) : ToggleField(name) { + private var holder: T? = null // Force T? in API for safety. + override fun get() = holder + override fun setImpl(value: T) { holder = value } + override fun clear() { + check(!once) { "Value of $name cannot be changed." } + holder = null + } +} + +@KeepForDJVM +class ThreadLocalToggleField(name: String) : ToggleField(name) { + private var holder: T? = null // Force T? in API for safety. + override fun get() = holder + override fun setImpl(value: T) { holder = value } + override fun clear() { + holder = null + } +} + +@Suppress("UNUSED") +@KeepForDJVM +class InheritableThreadLocalToggleField(name: String, + private val log: Logger = staticLog, + private val isAGlobalThreadBeingCreated: (Array) -> Boolean) : ToggleField(name) { + private companion object { + private val staticLog = contextLogger() + } + private var holder: T? = null // Force T? in API for safety. + override fun get() = holder + override fun setImpl(value: T) { holder = value } + override fun clear() { + holder = null + } +} \ No newline at end of file diff --git a/core-deterministic/src/main/kotlin/net/corda/core/serialization/SerializationFactory.kt b/core-deterministic/src/main/kotlin/net/corda/core/serialization/SerializationFactory.kt new file mode 100644 index 0000000000..69ddb8887b --- /dev/null +++ b/core-deterministic/src/main/kotlin/net/corda/core/serialization/SerializationFactory.kt @@ -0,0 +1,95 @@ +package net.corda.core.serialization + +import net.corda.core.KeepForDJVM +import net.corda.core.serialization.internal.effectiveSerializationEnv +import net.corda.core.utilities.ByteSequence + +/** + * An abstraction for serializing and deserializing objects, with support for versioning of the wire format via + * a header / prefix in the bytes. + */ +@KeepForDJVM +abstract class SerializationFactory { + /** + * Deserialize the bytes in to an object, using the prefixed bytes to determine the format. + * + * @param byteSequence The bytes to deserialize, including a format header prefix. + * @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown. + * @param context A context that configures various parameters to deserialization. + */ + abstract fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T + + /** + * Deserialize the bytes in to an object, using the prefixed bytes to determine the format. + * + * @param byteSequence The bytes to deserialize, including a format header prefix. + * @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown. + * @param context A context that configures various parameters to deserialization. + * @return deserialized object along with [SerializationContext] to identify encoding used. + */ + abstract fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext + + /** + * Serialize an object to bytes using the preferred serialization format version from the context. + * + * @param obj The object to be serialized. + * @param context A context that configures various parameters to serialization, including the serialization format version. + */ + abstract fun serialize(obj: T, context: SerializationContext): SerializedBytes + + /** + * If there is a need to nest serialization/deserialization with a modified context during serialization or deserialization, + * this will return the current context used to start serialization/deserialization. + */ + val currentContext: SerializationContext? get() = _currentContext + + /** + * A context to use as a default if you do not require a specially configured context. It will be the current context + * if the use is somehow nested (see [currentContext]). + */ + val defaultContext: SerializationContext get() = currentContext ?: effectiveSerializationEnv.p2pContext + + private var _currentContext: SerializationContext? = null + + /** + * Change the current context inside the block to that supplied. + */ + fun withCurrentContext(context: SerializationContext?, block: () -> T): T { + val priorContext = _currentContext + if (context != null) _currentContext = context + try { + return block() + } finally { + if (context != null) _currentContext = priorContext + } + } + + /** + * Allow subclasses to temporarily mark themselves as the current factory for the current thread during serialization/deserialization. + * Will restore the prior context on exiting the block. + */ + fun asCurrent(block: SerializationFactory.() -> T): T { + val priorContext = _currentFactory + _currentFactory= this + try { + return this.block() + } finally { + _currentFactory = priorContext + } + } + + companion object { + private var _currentFactory: SerializationFactory? = null + + /** + * A default factory for serialization/deserialization, taking into account the [currentFactory] if set. + */ + val defaultFactory: SerializationFactory get() = currentFactory ?: effectiveSerializationEnv.serializationFactory + + /** + * If there is a need to nest serialization/deserialization with a modified context during serialization or deserialization, + * this will return the current factory used to start serialization/deserialization. + */ + val currentFactory: SerializationFactory? get() = _currentFactory + } +} diff --git a/core-deterministic/testing/build.gradle b/core-deterministic/testing/build.gradle new file mode 100644 index 0000000000..0222e0bf2b --- /dev/null +++ b/core-deterministic/testing/build.gradle @@ -0,0 +1,16 @@ +apply plugin: 'kotlin' + +dependencies { + testCompile project(path: ':core-deterministic', configuration: 'runtimeArtifacts') + testCompile project(path: ':serialization-deterministic', configuration: 'runtimeArtifacts') + testCompile project(path: ':core-deterministic:testing:data', configuration: 'testData') + testCompile project(':core-deterministic:testing:common') + testCompile(project(':finance')) { + transitive = false + } + + testCompile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" + testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + testCompile "org.assertj:assertj-core:$assertj_version" + testCompile "junit:junit:$junit_version" +} diff --git a/core-deterministic/testing/common/build.gradle b/core-deterministic/testing/common/build.gradle new file mode 100644 index 0000000000..49f2ceb748 --- /dev/null +++ b/core-deterministic/testing/common/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'kotlin' + +evaluationDependsOn(':jdk8u-deterministic') + +def jdkTask = project(':jdk8u-deterministic').assemble +def deterministic_jdk_home = project(':jdk8u-deterministic').jdk_home + +dependencies { + compileOnly project(path: ':core-deterministic', configuration: 'runtimeArtifacts') + compileOnly project(path: ':serialization-deterministic', configuration: 'runtimeArtifacts') + compileOnly "junit:junit:$junit_version" +} + +tasks.withType(AbstractCompile) { + dependsOn jdkTask +} + +tasks.withType(JavaCompile) { + options.compilerArgs << '-bootclasspath' << "$deterministic_jdk_home/jre/lib/rt.jar".toString() +} + +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { + kotlinOptions.jdkHome = deterministic_jdk_home +} diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/LocalSerializationRule.kt b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/LocalSerializationRule.kt new file mode 100644 index 0000000000..184f7f72d9 --- /dev/null +++ b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/LocalSerializationRule.kt @@ -0,0 +1,85 @@ +package net.corda.deterministic.common + +import net.corda.core.serialization.ClassWhitelist +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationContext.UseCase.* +import net.corda.core.serialization.SerializationCustomSerializer +import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.serialization.internal.* +import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.amqpMagic +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import kotlin.reflect.KClass +import kotlin.reflect.jvm.jvmName + +class LocalSerializationRule(private val label: String) : TestRule { + constructor(klass: KClass<*>) : this(klass.jvmName) + + private companion object { + private val AMQP_P2P_CONTEXT = SerializationContextImpl( + amqpMagic, + LocalSerializationRule::class.java.classLoader, + GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), + emptyMap(), + true, + P2P, + null + ) + } + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + init() + try { + base.evaluate() + } finally { + clear() + } + } + } + } + + fun reset() { + clear() + init() + } + + private fun init() { + _contextSerializationEnv.set(createTestSerializationEnv()) + } + + private fun clear() { + _contextSerializationEnv.set(null) + } + + private fun createTestSerializationEnv(): SerializationEnvironmentImpl { + val factory = SerializationFactoryImpl(mutableMapOf()).apply { + registerScheme(AMQPSerializationScheme(emptySet(), mutableMapOf())) + } + return object : SerializationEnvironmentImpl(factory, AMQP_P2P_CONTEXT) { + override fun toString() = "testSerializationEnv($label)" + } + } + + private class AMQPSerializationScheme( + cordappCustomSerializers: Set>, + serializerFactoriesForContexts: MutableMap, SerializerFactory> + ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { + override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory { + throw UnsupportedOperationException() + } + + override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { + throw UnsupportedOperationException() + } + + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return canDeserializeVersion(magic) && target == P2P + } + } +} \ No newline at end of file diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/MockContractAttachment.kt b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/MockContractAttachment.kt new file mode 100644 index 0000000000..1526825683 --- /dev/null +++ b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/MockContractAttachment.kt @@ -0,0 +1,15 @@ +package net.corda.deterministic.common + +import net.corda.core.contracts.Attachment +import net.corda.core.contracts.ContractClassName +import net.corda.core.crypto.SecureHash +import net.corda.core.identity.Party +import net.corda.core.serialization.CordaSerializable +import java.io.ByteArrayInputStream +import java.io.InputStream + +@CordaSerializable +class MockContractAttachment(override val id: SecureHash = SecureHash.zeroHash, val contract: ContractClassName, override val signers: List = ArrayList()) : Attachment { + override fun open(): InputStream = ByteArrayInputStream(id.bytes) + override val size = id.size +} diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/SampleData.kt b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/SampleData.kt new file mode 100644 index 0000000000..025fa148fa --- /dev/null +++ b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/SampleData.kt @@ -0,0 +1,6 @@ +@file:JvmName("SampleData") +package net.corda.deterministic.common + +import net.corda.core.contracts.TypeOnlyCommandData + +object SampleCommandData : TypeOnlyCommandData() diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/TransactionVerificationRequest.kt b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/TransactionVerificationRequest.kt new file mode 100644 index 0000000000..fb3743688d --- /dev/null +++ b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/TransactionVerificationRequest.kt @@ -0,0 +1,32 @@ +package net.corda.deterministic.common + +import net.corda.core.contracts.Attachment +import net.corda.core.contracts.ContractAttachment +import net.corda.core.contracts.ContractClassName +import net.corda.core.internal.TEST_UPLOADER +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.WireTransaction + +@Suppress("MemberVisibilityCanBePrivate") +@CordaSerializable +class TransactionVerificationRequest(val wtxToVerify: SerializedBytes, + val dependencies: Array>, + val attachments: Array) { + fun toLedgerTransaction(): LedgerTransaction { + val deps = dependencies.map { it.deserialize() }.associateBy(WireTransaction::id) + val attachments = attachments.map { it.deserialize() } + val attachmentMap = attachments.mapNotNull { it as? MockContractAttachment } + .associateBy(Attachment::id, { ContractAttachment(it, it.contract, uploader=TEST_UPLOADER) }) + val contractAttachmentMap = emptyMap() + @Suppress("DEPRECATION") + return wtxToVerify.deserialize().toLedgerTransaction( + resolveIdentity = { null }, + resolveAttachment = { attachmentMap[it] }, + resolveStateRef = { deps[it.txhash]?.outputs?.get(it.index) }, + resolveContractAttachment = { contractAttachmentMap[it.contract]?.id } + ) + } +} diff --git a/core-deterministic/testing/data/build.gradle b/core-deterministic/testing/data/build.gradle new file mode 100644 index 0000000000..7b94ae6c74 --- /dev/null +++ b/core-deterministic/testing/data/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'kotlin' + +configurations { + testData +} + +dependencies { + testCompile project(':core') + testCompile project(':finance') + testCompile project(':node-driver') + testCompile project(':core-deterministic:testing:common') + + testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + testCompile "org.jetbrains.kotlin:kotlin-reflect" + testCompile "junit:junit:$junit_version" +} + +jar.enabled = false + +test { + filter { + // Running this class is the whole point, so include it explicitly. + includeTestsMatching "net.corda.deterministic.data.GenerateData" + } +} + +artifacts { + testData file: file("$buildDir/test-data.jar"), type: 'jar', builtBy: test +} diff --git a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/GenerateData.kt b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/GenerateData.kt new file mode 100644 index 0000000000..0304661183 --- /dev/null +++ b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/GenerateData.kt @@ -0,0 +1,92 @@ +package net.corda.deterministic.data + +import net.corda.core.serialization.deserialize +import net.corda.deterministic.common.LocalSerializationRule +import net.corda.deterministic.common.TransactionVerificationRequest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.io.FileNotFoundException +import java.net.URLClassLoader +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.attribute.FileTime +import java.util.* +import java.util.Calendar.* +import java.util.jar.JarOutputStream +import java.util.zip.Deflater.NO_COMPRESSION +import java.util.zip.ZipEntry +import java.util.zip.ZipEntry.* +import kotlin.reflect.jvm.jvmName + +/** + * Use the JUnit framework to generate a JAR of test data. + */ +class GenerateData { + companion object { + private val CONSTANT_TIME: FileTime = FileTime.fromMillis( + GregorianCalendar(1980, FEBRUARY, 1).apply { timeZone = TimeZone.getTimeZone("UTC") }.timeInMillis + ) + private const val KEYSTORE_ALIAS = "tx" + private val KEYSTORE_PASSWORD = "deterministic".toCharArray() + private val TEST_DATA: Path = Paths.get("build", "test-data.jar") + + private fun compressed(name: String) = ZipEntry(name).apply { + lastModifiedTime = CONSTANT_TIME + method = DEFLATED + } + + private fun directory(name: String) = ZipEntry(name).apply { + lastModifiedTime = CONSTANT_TIME + method = STORED + compressedSize = 0 + size = 0 + crc = 0 + } + } + + @Rule + @JvmField + val testSerialization = LocalSerializationRule(GenerateData::class.jvmName) + + @Before + fun createTransactions() { + JarOutputStream(Files.newOutputStream(TEST_DATA)).use { jar -> + jar.setComment("Test data for Deterministic Corda") + jar.setLevel(NO_COMPRESSION) + + // Serialised transactions for the Enclavelet + jar.putNextEntry(directory("txverify")) + jar.putNextEntry(compressed("txverify/tx-success.bin")) + TransactionGenerator.writeSuccess(jar) + jar.putNextEntry(compressed("txverify/tx-failure.bin")) + TransactionGenerator.writeFailure(jar) + + // KeyStore containing an EC private key. + jar.putNextEntry(directory("keystore")) + jar.putNextEntry(compressed("keystore/txsignature.pfx")) + KeyStoreGenerator.writeKeyStore(jar, KEYSTORE_ALIAS, KEYSTORE_PASSWORD) + } + testSerialization.reset() + } + + @Test + fun verifyTransactions() { + URLClassLoader(arrayOf(TEST_DATA.toUri().toURL())).use { cl -> + cl.loadResource("txverify/tx-success.bin") + .deserialize() + .toLedgerTransaction() + .verify() + + cl.loadResource("txverify/tx-failure.bin") + .deserialize() + .toLedgerTransaction() + } + } + + private fun ClassLoader.loadResource(resourceName: String): ByteArray { + return getResourceAsStream(resourceName)?.use { it.readBytes() } + ?: throw FileNotFoundException(resourceName) + } +} diff --git a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/KeyStoreGenerator.kt b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/KeyStoreGenerator.kt new file mode 100644 index 0000000000..f14f933f10 --- /dev/null +++ b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/KeyStoreGenerator.kt @@ -0,0 +1,51 @@ +package net.corda.deterministic.data + +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder +import java.io.OutputStream +import java.math.BigInteger +import java.math.BigInteger.TEN +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.spec.ECGenParameterSpec +import java.util.* +import java.util.Calendar.* + +object KeyStoreGenerator { + private val keyPairGenerator: KeyPairGenerator = KeyPairGenerator.getInstance("EC").apply { + initialize(ECGenParameterSpec("secp256k1")) + } + + fun writeKeyStore(output: OutputStream, alias: String, password: CharArray) { + val keyPair = keyPairGenerator.generateKeyPair() + val signer = JcaContentSignerBuilder("SHA256WithECDSA").build(keyPair.private) + val dname = X500Name("CN=Enclavelet") + val startDate = Calendar.getInstance().let { cal -> + cal.time = Date() + cal.add(HOUR, -1) + cal.time + } + val endDate = Calendar.getInstance().let { cal -> + cal.time = startDate + cal.add(YEAR, 1) + cal.time + } + val certificate = JcaX509v3CertificateBuilder( + dname, + TEN, + startDate, + endDate, + dname, + keyPair.public + ).build(signer) + val x509 = JcaX509CertificateConverter().getCertificate(certificate) + + KeyStore.getInstance("PKCS12").apply { + load(null, password) + setKeyEntry(alias, keyPair.private, password, arrayOf(x509)) + store(output, password) + } + } +} \ No newline at end of file diff --git a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt new file mode 100644 index 0000000000..a6b704077e --- /dev/null +++ b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt @@ -0,0 +1,112 @@ +package net.corda.deterministic.data + +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever +import net.corda.core.crypto.entropyToKeyPair +import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.serialization.serialize +import net.corda.deterministic.common.MockContractAttachment +import net.corda.deterministic.common.SampleCommandData +import net.corda.deterministic.common.TransactionVerificationRequest +import net.corda.finance.POUNDS +import net.corda.finance.`issued by` +import net.corda.finance.contracts.asset.Cash.* +import net.corda.finance.contracts.asset.Cash.Commands.* +import net.corda.finance.contracts.asset.Cash.Companion.PROGRAM_ID +import net.corda.node.services.api.IdentityServiceInternal +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.core.TestIdentity +import net.corda.testing.core.getTestPartyAndCertificate +import net.corda.testing.internal.rigorousMock +import net.corda.testing.node.MockServices +import net.corda.testing.node.ledger +import java.io.OutputStream +import java.math.BigInteger +import java.security.KeyPair +import java.security.PublicKey + +object TransactionGenerator { + private val DUMMY_NOTARY: Party = TestIdentity(DUMMY_NOTARY_NAME, 20).party + + private val DUMMY_CASH_ISSUER_KEY: KeyPair = entropyToKeyPair(BigInteger.valueOf(10)) + private val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), DUMMY_CASH_ISSUER_KEY.public)) + private val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1) + + private val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) + private val MEGA_CORP: Party = megaCorp.party + private val MEGA_CORP_PUBKEY: PublicKey = megaCorp.keyPair.public + private val MINI_CORP_PUBKEY: PublicKey = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).keyPair.public + + private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + doReturn(DUMMY_CASH_ISSUER.party).whenever(it).partyFromKey(DUMMY_CASH_ISSUER_KEY.public) + }) + + fun writeSuccess(output: OutputStream) { + ledgerServices.ledger(DUMMY_NOTARY) { + // Issue a couple of cash states and spend them. + val wtx1 = transaction { + attachments(PROGRAM_ID) + output(PROGRAM_ID, "c1", State(1000.POUNDS `issued by` DUMMY_CASH_ISSUER, AnonymousParty(MEGA_CORP_PUBKEY))) + command(DUMMY_CASH_ISSUER.party.owningKey, Issue()) + verifies() + } + val wtx2 = transaction { + attachments(PROGRAM_ID) + output(PROGRAM_ID, "c2", State(2000.POUNDS `issued by` DUMMY_CASH_ISSUER, AnonymousParty(MEGA_CORP_PUBKEY))) + command(DUMMY_CASH_ISSUER.party.owningKey, Issue()) + verifies() + } + val wtx3 = transaction { + attachments(PROGRAM_ID) + input("c1") + input("c2") + output(PROGRAM_ID, "c3", State(3000.POUNDS `issued by` DUMMY_CASH_ISSUER, AnonymousParty(MINI_CORP_PUBKEY))) + command(MEGA_CORP_PUBKEY, Move()) + verifies() + } + val contractAttachment = MockContractAttachment(interpreter.services.cordappProvider.getContractAttachmentID(PROGRAM_ID)!!, PROGRAM_ID) + TransactionVerificationRequest( + wtx3.serialize(), + arrayOf(wtx1.serialize(), wtx2.serialize()), + arrayOf(contractAttachment.serialize().bytes)) + .serialize() + .writeTo(output) + } + } + + fun writeFailure(output: OutputStream) { + ledgerServices.ledger(DUMMY_NOTARY) { + // Issue a couple of cash states and spend them. + val wtx1 = transaction { + attachments(PROGRAM_ID) + output(PROGRAM_ID, "c1", State(1000.POUNDS `issued by` DUMMY_CASH_ISSUER, AnonymousParty(MEGA_CORP_PUBKEY))) + command(DUMMY_CASH_ISSUER.party.owningKey, Issue()) + verifies() + } + val wtx2 = transaction { + attachments(PROGRAM_ID) + output(PROGRAM_ID, "c2", State(2000.POUNDS `issued by` DUMMY_CASH_ISSUER, AnonymousParty(MEGA_CORP_PUBKEY))) + command(DUMMY_CASH_ISSUER.party.owningKey, Issue()) + verifies() + } + val wtx3 = transaction { + attachments(PROGRAM_ID) + input("c1") + input("c2") + command(DUMMY_CASH_ISSUER.party.owningKey, SampleCommandData) + output(PROGRAM_ID, "c3", State(3000.POUNDS `issued by` DUMMY_CASH_ISSUER, AnonymousParty(MINI_CORP_PUBKEY))) + failsWith("Required ${Move::class.java.canonicalName} command") + } + val contractAttachment = MockContractAttachment(interpreter.services.cordappProvider.getContractAttachmentID(PROGRAM_ID)!!, PROGRAM_ID) + TransactionVerificationRequest( + wtx3.serialize(), + arrayOf(wtx1.serialize(), wtx2.serialize()), + arrayOf(contractAttachment.serialize().bytes)) + .serialize() + .writeTo(output) + } + } +} \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/CheatingSecurityProvider.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/CheatingSecurityProvider.kt new file mode 100644 index 0000000000..048c0cae68 --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/CheatingSecurityProvider.kt @@ -0,0 +1,43 @@ +package net.corda.deterministic + +import org.junit.Assert.* +import java.security.Provider +import java.security.SecureRandom +import java.security.SecureRandomSpi +import java.security.Security + +/** + * Temporarily restore Sun's [SecureRandom] provider. + * This is ONLY for allowing us to generate test data, e.g. signatures. + */ +class CheatingSecurityProvider : Provider(NAME, 1.8, "$NAME security provider"), AutoCloseable { + private companion object { + private const val NAME = "Cheat!" + } + + init { + putService(CheatingSecureRandomService(this)) + assertEquals(1, Security.insertProviderAt(this, 1)) + } + + override fun close() { + Security.removeProvider(NAME) + } + + private class SunSecureRandom : SecureRandom(sun.security.provider.SecureRandom(), null) + + private class CheatingSecureRandomService(provider: Provider) + : Provider.Service(provider, "SecureRandom", "CheatingPRNG", CheatingSecureRandomSpi::javaClass.name, null, null) { + + private val instance: SecureRandomSpi = CheatingSecureRandomSpi() + override fun newInstance(constructorParameter: Any?) = instance + } + + private class CheatingSecureRandomSpi : SecureRandomSpi() { + private val secureRandom: SecureRandom = SunSecureRandom() + + override fun engineSetSeed(seed: ByteArray) = secureRandom.setSeed(seed) + override fun engineNextBytes(bytes: ByteArray) = secureRandom.nextBytes(bytes) + override fun engineGenerateSeed(numBytes: Int): ByteArray = secureRandom.generateSeed(numBytes) + } +} \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/CordaExceptionTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/CordaExceptionTest.kt new file mode 100644 index 0000000000..2a7351ae08 --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/CordaExceptionTest.kt @@ -0,0 +1,70 @@ +package net.corda.deterministic + +import net.corda.core.CordaException +import net.corda.core.contracts.AttachmentResolutionException +import net.corda.core.contracts.TransactionResolutionException +import net.corda.core.contracts.TransactionVerificationException.* +import net.corda.core.crypto.SecureHash +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import org.junit.Assert.* +import org.junit.Test +import java.security.PublicKey +import kotlin.test.assertFailsWith + +class CordaExceptionTest { + companion object { + const val CONTRACT_CLASS = "com.r3.corda.contracts.TestContract" + val TEST_HASH = SecureHash.zeroHash + val TX_ID = SecureHash.allOnesHash + + val ALICE_NAME = CordaX500Name("Alice Corp", "Madrid", "ES") + val ALICE_KEY: PublicKey = object : PublicKey { + override fun getAlgorithm(): String = "TEST-256" + override fun getFormat(): String = "" + override fun getEncoded() = byteArrayOf() + } + val ALICE = Party(ALICE_NAME, ALICE_KEY) + + val BOB_NAME = CordaX500Name("Bob Plc", "Rome", "IT") + val BOB_KEY: PublicKey = object : PublicKey { + override fun getAlgorithm(): String = "TEST-512" + override fun getFormat(): String = "" + override fun getEncoded() = byteArrayOf() + } + val BOB = Party(BOB_NAME, BOB_KEY) + } + + @Test + fun testCordaException() { + val ex = assertFailsWith { throw CordaException("BAD THING") } + assertEquals("BAD THING", ex.message) + } + + @Test + fun testAttachmentResolutionException() { + val ex = assertFailsWith { throw AttachmentResolutionException(TEST_HASH) } + assertEquals(TEST_HASH, ex.hash) + } + + @Test + fun testTransactionResolutionException() { + val ex = assertFailsWith { throw TransactionResolutionException(TEST_HASH) } + assertEquals(TEST_HASH, ex.hash) + } + + @Test + fun testConflictingAttachmentsRejection() { + val ex = assertFailsWith { throw ConflictingAttachmentsRejection(TX_ID, CONTRACT_CLASS) } + assertEquals(TX_ID, ex.txId) + assertEquals(CONTRACT_CLASS, ex.contractClass) + } + + @Test + fun testNotaryChangeInWrongTransactionType() { + val ex = assertFailsWith { throw NotaryChangeInWrongTransactionType(TX_ID, ALICE, BOB) } + assertEquals(TX_ID, ex.txId) + assertEquals(ALICE, ex.txNotary) + assertEquals(BOB, ex.outputNotary) + } +} \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/KeyStoreProvider.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/KeyStoreProvider.kt new file mode 100644 index 0000000000..20929a557a --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/KeyStoreProvider.kt @@ -0,0 +1,44 @@ +package net.corda.deterministic + +import org.junit.AssumptionViolatedException +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import java.security.KeyPair +import java.security.KeyStore +import java.security.PrivateKey +import java.security.cert.TrustAnchor +import java.security.cert.X509Certificate + +class KeyStoreProvider(private val storeName: String, private val storePassword: String) : TestRule { + private lateinit var keyStore: KeyStore + + private fun loadKeyStoreResource(resourceName: String, password: CharArray, type: String = "PKCS12"): KeyStore { + return KeyStore.getInstance(type).apply { + // Skip these tests if we cannot load the keystore. + val keyStream = KeyStoreProvider::class.java.classLoader.getResourceAsStream(resourceName) + ?: throw AssumptionViolatedException("KeyStore $resourceName not found") + keyStream.use { input -> + load(input, password) + } + } + } + + override fun apply(statement: Statement, description: Description?): Statement { + return object : Statement() { + override fun evaluate() { + keyStore = loadKeyStoreResource(storeName, storePassword.toCharArray()) + statement.evaluate() + } + } + } + + fun getKeyPair(alias: String): KeyPair { + val privateKey = keyStore.getKey(alias, storePassword.toCharArray()) as PrivateKey + return KeyPair(keyStore.getCertificate(alias).publicKey, privateKey) + } + + @Suppress("UNUSED") + fun trustAnchorsFor(vararg aliases: String): Set + = aliases.map { alias -> TrustAnchor(keyStore.getCertificate(alias) as X509Certificate, null) }.toSet() +} diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/Utilities.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/Utilities.kt new file mode 100644 index 0000000000..bb7290206e --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/Utilities.kt @@ -0,0 +1,14 @@ +package net.corda.deterministic + +import java.io.ByteArrayOutputStream +import java.io.IOException + +private val classLoader: ClassLoader = object {}.javaClass.classLoader + +@Throws(IOException::class) +fun bytesOfResource(resourceName: String): ByteArray { + return ByteArrayOutputStream().let { baos -> + classLoader.getResourceAsStream(resourceName).copyTo(baos) + baos.toByteArray() + } +} diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/AttachmentTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/AttachmentTest.kt new file mode 100644 index 0000000000..21ad6a3e45 --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/AttachmentTest.kt @@ -0,0 +1,77 @@ +package net.corda.deterministic.contracts + +import net.corda.core.contracts.Attachment +import net.corda.core.crypto.SecureHash +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.security.PublicKey +import java.util.jar.JarOutputStream +import java.util.zip.Deflater.* +import java.util.zip.ZipEntry + +class AttachmentTest { + private companion object { + private val data = byteArrayOf(0x73, 0x71, 0x18, 0x5F, 0x3A, 0x47, -0x22, 0x38) + private val jarData: ByteArray = ByteArrayOutputStream().let { baos -> + JarOutputStream(baos).use { jar -> + jar.setLevel(BEST_COMPRESSION) + jar.putNextEntry(ZipEntry("data.bin").apply { method = DEFLATED }) + data.inputStream().copyTo(jar) + } + baos.toByteArray() + } + + private val ALICE_NAME = CordaX500Name("Alice Corp", "Madrid", "ES") + private val ALICE_KEY: PublicKey = object : PublicKey { + override fun getAlgorithm(): String = "TEST-256" + override fun getFormat(): String = "" + override fun getEncoded() = byteArrayOf() + } + private val ALICE = Party(ALICE_NAME, ALICE_KEY) + } + + private lateinit var attachment: Attachment + + @Before + fun setup() { + attachment = object : Attachment { + override val id: SecureHash + get() = SecureHash.allOnesHash + override val signers: List + get() = listOf(ALICE) + override val size: Int + get() = jarData.size + + override fun open(): InputStream { + return jarData.inputStream() + } + } + } + + @Test + fun testAttachmentJar() { + attachment.openAsJAR().use { jar -> + val entry = jar.nextJarEntry ?: return@use + assertEquals("data.bin", entry.name) + val entryData = ByteArrayOutputStream().use { + jar.copyTo(it) + it.toByteArray() + } + assertArrayEquals(data, entryData) + } + } + + @Test + fun testExtractFromAttachment() { + val resultData = ByteArrayOutputStream().use { + attachment.extractFile("data.bin", it) + it.toByteArray() + } + assertArrayEquals(data, resultData) + } +} \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt new file mode 100644 index 0000000000..5b66cf520e --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt @@ -0,0 +1,34 @@ +package net.corda.deterministic.contracts + +import net.corda.core.contracts.PrivacySalt +import org.junit.Test +import kotlin.test.* + +class PrivacySaltTest { + private companion object { + private const val SALT_SIZE = 32 + } + + @Test + fun testValidSalt() { + PrivacySalt(ByteArray(SALT_SIZE, { 0x14 })) + } + + @Test + fun testInvalidSaltWithAllZeros() { + val ex = assertFailsWith { PrivacySalt(ByteArray(SALT_SIZE)) } + assertEquals("Privacy salt should not be all zeros.", ex.message) + } + + @Test + fun testTooShortPrivacySalt() { + val ex = assertFailsWith { PrivacySalt(ByteArray(SALT_SIZE - 1, { 0x7f })) } + assertEquals("Privacy salt should be 32 bytes.", ex.message) + } + + @Test + fun testTooLongPrivacySalt() { + val ex = assertFailsWith { PrivacySalt(ByteArray(SALT_SIZE + 1, { 0x7f })) } + assertEquals("Privacy salt should be 32 bytes.", ex.message) + } +} \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/MerkleTreeTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/MerkleTreeTest.kt new file mode 100644 index 0000000000..432a89280d --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/MerkleTreeTest.kt @@ -0,0 +1,14 @@ +package net.corda.deterministic.crypto + +import net.corda.core.crypto.MerkleTree +import net.corda.core.crypto.SecureHash +import org.junit.Assert.assertEquals +import org.junit.Test + +class MerkleTreeTest { + @Test + fun testCreate() { + val merkle = MerkleTree.getMerkleTree(listOf(SecureHash.allOnesHash, SecureHash.zeroHash)) + assertEquals(SecureHash.parse("A5DE9B714ACCD8AFAAABF1CBD6E1014C9D07FF95C2AE154D91EC68485B31E7B5"), merkle.hash) + } +} \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt new file mode 100644 index 0000000000..a9a069f589 --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt @@ -0,0 +1,42 @@ +package net.corda.deterministic.crypto + +import net.corda.core.crypto.SecureHash +import org.bouncycastle.util.encoders.Hex +import org.junit.Assert.* +import org.junit.Test +import java.security.MessageDigest + +class SecureHashTest { + @Test + fun testSHA256() { + val hash = SecureHash.sha256(byteArrayOf(0x64, -0x13, 0x42, 0x3a)) + assertEquals(SecureHash.parse("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F"), hash) + assertEquals("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F", hash.toString()) + } + + @Test + fun testPrefix() { + val data = byteArrayOf(0x7d, 0x03, -0x21, 0x32, 0x56, 0x47) + val digest = data.digestFor("SHA-256") + val prefix = SecureHash.sha256(data).prefixChars(8) + assertEquals(Hex.toHexString(digest).substring(0, 8).toUpperCase(), prefix) + } + + @Test + fun testConcat() { + val hash1 = SecureHash.sha256(byteArrayOf(0x7d, 0x03, -0x21, 0x32, 0x56, 0x47)) + val hash2 = SecureHash.sha256(byteArrayOf(0x63, 0x01, 0x7f, -0x29, 0x1e, 0x3c)) + val combined = hash1.hashConcat(hash2) + assertArrayEquals((hash1.bytes + hash2.bytes).digestFor("SHA-256"), combined.bytes) + } + + @Test + fun testConstants() { + assertArrayEquals(SecureHash.zeroHash.bytes, ByteArray(32)) + assertArrayEquals(SecureHash.allOnesHash.bytes, ByteArray(32, { 0xFF.toByte() })) + } +} + +private fun ByteArray.digestFor(algorithm: String): ByteArray { + return MessageDigest.getInstance(algorithm).digest(this) +} \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureRandomTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureRandomTest.kt new file mode 100644 index 0000000000..bd5ae65a49 --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureRandomTest.kt @@ -0,0 +1,22 @@ +package net.corda.deterministic.crypto + +import net.corda.core.crypto.CordaSecurityProvider +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.security.NoSuchAlgorithmException +import java.security.SecureRandom +import kotlin.test.assertFailsWith + +class SecureRandomTest { + private companion object { + init { + CordaSecurityProvider() + } + } + + @Test + fun testNoCordaPRNG() { + val error = assertFailsWith { SecureRandom.getInstance("CordaPRNG") } + assertThat(error).hasMessage("CordaPRNG SecureRandom not available") + } +} \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt new file mode 100644 index 0000000000..5935c13b3c --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt @@ -0,0 +1,143 @@ +package net.corda.deterministic.crypto + +import net.corda.core.crypto.* +import net.corda.deterministic.KeyStoreProvider +import net.corda.deterministic.CheatingSecurityProvider +import net.corda.deterministic.common.LocalSerializationRule +import org.junit.* +import org.junit.rules.RuleChain +import java.security.* +import kotlin.test.* + +class TransactionSignatureTest { + companion object { + private const val KEYSTORE_PASSWORD = "deterministic" + private val testBytes = "12345678901234567890123456789012".toByteArray() + + private val keyStoreProvider = KeyStoreProvider("keystore/txsignature.pfx", KEYSTORE_PASSWORD) + private lateinit var keyPair: KeyPair + + @ClassRule + @JvmField + val rules: RuleChain = RuleChain.outerRule(LocalSerializationRule(TransactionSignatureTest::class)) + .around(keyStoreProvider) + + @BeforeClass + @JvmStatic + fun setupClass() { + keyPair = keyStoreProvider.getKeyPair("tx") + } + } + + /** Valid sign and verify. */ + @Test + fun `Signature metadata full sign and verify`() { + // Create a SignableData object. + val signableData = SignableData(testBytes.sha256(), SignatureMetadata(1, Crypto.findSignatureScheme(keyPair.public).schemeNumberID)) + + // Sign the meta object. + val transactionSignature: TransactionSignature = CheatingSecurityProvider().use { + keyPair.sign(signableData) + } + + // Check auto-verification. + assertTrue(transactionSignature.verify(testBytes.sha256())) + + // Check manual verification. + assertTrue(Crypto.doVerify(testBytes.sha256(), transactionSignature)) + } + + /** Verification should fail; corrupted metadata - clearData (Merkle root) has changed. */ + @Test(expected = SignatureException::class) + fun `Signature metadata full failure clearData has changed`() { + val signableData = SignableData(testBytes.sha256(), SignatureMetadata(1, Crypto.findSignatureScheme(keyPair.public).schemeNumberID)) + val transactionSignature = CheatingSecurityProvider().use { + keyPair.sign(signableData) + } + Crypto.doVerify((testBytes + testBytes).sha256(), transactionSignature) + } + + @Test + fun `Verify multi-tx signature`() { + // Deterministically create 5 txIds. + val txIds: List = IntRange(0, 4).map { byteArrayOf(it.toByte()).sha256() } + // Multi-tx signature. + val txSignature = signMultipleTx(txIds, keyPair) + + // The hash of all txIds are used as leaves. + val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.sha256() }) + + // We haven't added the partial tree yet. + assertNull(txSignature.partialMerkleTree) + // Because partial tree is still null, but we signed over a block of txs, verifying a single tx will fail. + assertFailsWith { Crypto.doVerify(txIds[3], txSignature) } + + // Create a partial tree for one tx. + val pmt = PartialMerkleTree.build(merkleTree, listOf(txIds[0].sha256())) + // Add the partial Merkle tree to the tx signature. + val txSignatureWithTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmt) + + // Verify the corresponding txId with every possible way. + assertTrue(Crypto.doVerify(txIds[0], txSignatureWithTree)) + assertTrue(txSignatureWithTree.verify(txIds[0])) + assertTrue(Crypto.isValid(txIds[0], txSignatureWithTree)) + assertTrue(txSignatureWithTree.isValid(txIds[0])) + + // Verify the rest txs in the block, which are not included in the partial Merkle tree. + txIds.subList(1, txIds.size).forEach { + assertFailsWith { Crypto.doVerify(it, txSignatureWithTree) } + } + + // Test that the Merkle tree consists of hash(txId), not txId. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(txIds[0])) } + + // What if we send the Full tree. This could be used if notaries didn't want to create a per tx partial tree. + // Create a partial tree for all txs, thus all leaves are included. + val pmtFull = PartialMerkleTree.build(merkleTree, txIds.map { it.sha256() }) + // Add the partial Merkle tree to the tx. + val txSignatureWithFullTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmtFull) + + // All txs can be verified, as they are all included in the provided partial tree. + txIds.forEach { + assertTrue(Crypto.doVerify(it, txSignatureWithFullTree)) + } + } + + @Test + fun `Verify one-tx signature`() { + val txId = "aTransaction".toByteArray().sha256() + // One-tx signature. + val txSignature = try { + signOneTx(txId, keyPair) + } catch (e: Throwable) { + e.cause?.printStackTrace() + throw e + } + + // partialMerkleTree should be null. + assertNull(txSignature.partialMerkleTree) + // Verify the corresponding txId with every possible way. + assertTrue(Crypto.doVerify(txId, txSignature)) + assertTrue(txSignature.verify(txId)) + assertTrue(Crypto.isValid(txId, txSignature)) + assertTrue(txSignature.isValid(txId)) + + // We signed the txId itself, not its hash (because it was a signature over one tx only and no partial tree has been received). + assertFailsWith { Crypto.doVerify(txId.sha256(), txSignature) } + } + + // Returns a TransactionSignature over the Merkle root, but the partial tree is null. + private fun signMultipleTx(txIds: List, keyPair: KeyPair): TransactionSignature { + val merkleTreeRoot = MerkleTree.getMerkleTree(txIds.map { it.sha256() }).hash + return signOneTx(merkleTreeRoot, keyPair) + } + + // Returns a TransactionSignature over one SecureHash. + // Note that if one tx is to be signed, we don't create a Merkle tree and we directly sign over the txId. + private fun signOneTx(txId: SecureHash, keyPair: KeyPair): TransactionSignature { + val signableData = SignableData(txId, SignatureMetadata(3, Crypto.findSignatureScheme(keyPair.public).schemeNumberID)) + return CheatingSecurityProvider().use { + keyPair.sign(signableData) + } + } +} diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/transactions/TransactionWithSignaturesTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/transactions/TransactionWithSignaturesTest.kt new file mode 100644 index 0000000000..f1ec9630db --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/transactions/TransactionWithSignaturesTest.kt @@ -0,0 +1,30 @@ +package net.corda.deterministic.transactions + +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.TransactionSignature +import net.corda.core.transactions.TransactionWithSignatures +import org.junit.Test +import java.security.PublicKey + +class TransactionWithSignaturesTest { + @Test + fun txWithSigs() { + val tx = object : TransactionWithSignatures { + override val id: SecureHash + get() = SecureHash.zeroHash + override val requiredSigningKeys: Set + get() = emptySet() + override val sigs: List + get() = emptyList() + + override fun getKeyDescriptions(keys: Set): List { + return emptyList() + } + } + tx.verifyRequiredSignatures() + tx.checkSignaturesAreValid() + tx.getMissingSigners() + tx.verifySignaturesExcept() + tx.verifySignaturesExcept(emptySet()) + } +} \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/EnclaveletTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/EnclaveletTest.kt new file mode 100644 index 0000000000..88125a4072 --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/EnclaveletTest.kt @@ -0,0 +1,52 @@ +@file:JvmName("Enclavelet") +package net.corda.deterministic.txverify + +import net.corda.core.serialization.deserialize +import net.corda.core.transactions.LedgerTransaction +import net.corda.deterministic.bytesOfResource +import net.corda.deterministic.common.LocalSerializationRule +import net.corda.deterministic.common.TransactionVerificationRequest +import net.corda.finance.contracts.asset.Cash.Commands.* +import org.assertj.core.api.Assertions.assertThat +import org.junit.ClassRule +import org.junit.Test +import kotlin.test.assertFailsWith + +class EnclaveletTest { + companion object { + @ClassRule + @JvmField + val serialization = LocalSerializationRule(EnclaveletTest::class) + } + + @Test + fun success() { + verifyInEnclave(bytesOfResource("txverify/tx-success.bin")) + } + + @Test + fun failure() { + val e = assertFailsWith { verifyInEnclave(bytesOfResource("txverify/tx-failure.bin")) } + assertThat(e).hasMessageContaining("Required ${Move::class.java.canonicalName} command") + } +} + +/** + * Returns either null to indicate success when the transactions are validated, or a string with the + * contents of the error. Invoked via JNI in response to an enclave RPC. The argument is a serialised + * [TransactionVerificationRequest]. + * + * Note that it is assumed the signatures were already checked outside the sandbox: the purpose of this code + * is simply to check the sensitive, app specific parts of a transaction. + * + * TODO: Transaction data is meant to be encrypted under an enclave-private key. + */ +@Throws(Exception::class) +private fun verifyInEnclave(reqBytes: ByteArray) { + deserialize(reqBytes).verify() +} + +private fun deserialize(reqBytes: ByteArray): LedgerTransaction { + return reqBytes.deserialize() + .toLedgerTransaction() +} diff --git a/core-deterministic/testing/src/test/resources/log4j2-test.xml b/core-deterministic/testing/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..4e309bf567 --- /dev/null +++ b/core-deterministic/testing/src/test/resources/log4j2-test.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/core/build.gradle b/core/build.gradle index 468a8dac45..32d253d978 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -53,12 +53,6 @@ processSmokeTestResources { } } -buildscript { - repositories { - mavenCentral() - } -} - dependencies { testCompile "junit:junit:$junit_version" testCompile "commons-fileupload:commons-fileupload:$fileupload_version" @@ -124,7 +118,10 @@ task copyQuasarJar(type: Copy) { rename { filename -> "quasar.jar"} } -jar.finalizedBy(copyQuasarJar) +jar { + finalizedBy(copyQuasarJar) + baseName 'corda-core' +} configurations { testArtifacts.extendsFrom testRuntime @@ -155,10 +152,6 @@ artifacts { testArtifacts testJar } -jar { - baseName 'corda-core' -} - scanApi { excludeClasses = [ // Kotlin should probably have declared this class as "synthetic". diff --git a/core/src/main/java/net/corda/core/crypto/Base58.java b/core/src/main/java/net/corda/core/crypto/Base58.java index 9037505bb2..76dc0feb69 100644 --- a/core/src/main/java/net/corda/core/crypto/Base58.java +++ b/core/src/main/java/net/corda/core/crypto/Base58.java @@ -1,5 +1,6 @@ package net.corda.core.crypto; +import net.corda.core.KeepForDJVM; import java.math.*; import java.util.*; @@ -27,6 +28,7 @@ import java.util.*; * NB: This class originally comes from the Apache licensed bitcoinj library. The original author of this code is the * same as the original author of the R3 repository. */ +@KeepForDJVM public class Base58 { private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); private static final char ENCODED_ZERO = ALPHABET[0]; diff --git a/core/src/main/java/net/corda/core/flows/IdentifiableException.java b/core/src/main/java/net/corda/core/flows/IdentifiableException.java index 93c9549685..2e87a8957b 100644 --- a/core/src/main/java/net/corda/core/flows/IdentifiableException.java +++ b/core/src/main/java/net/corda/core/flows/IdentifiableException.java @@ -1,11 +1,14 @@ package net.corda.core.flows; +import net.corda.core.KeepForDJVM; + import javax.annotation.Nullable; /** * An exception that may be identified with an ID. If an exception originates in a counter-flow this ID will be * propagated. This allows correlation of error conditions across different flows. */ +@KeepForDJVM public interface IdentifiableException { /** * @return the ID of the error, or null if the error doesn't have it set (yet). diff --git a/core/src/main/kotlin/net/corda/core/ClientRelevantError.kt b/core/src/main/kotlin/net/corda/core/ClientRelevantError.kt index 409944fd23..049bb60c1c 100644 --- a/core/src/main/kotlin/net/corda/core/ClientRelevantError.kt +++ b/core/src/main/kotlin/net/corda/core/ClientRelevantError.kt @@ -6,4 +6,5 @@ import net.corda.core.serialization.CordaSerializable * Allows an implementing [Throwable] to be propagated to clients. */ @CordaSerializable +@KeepForDJVM interface ClientRelevantError \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/CordaException.kt b/core/src/main/kotlin/net/corda/core/CordaException.kt index 52bbd82172..7b5f822452 100644 --- a/core/src/main/kotlin/net/corda/core/CordaException.kt +++ b/core/src/main/kotlin/net/corda/core/CordaException.kt @@ -4,6 +4,7 @@ import net.corda.core.serialization.CordaSerializable import java.util.* @CordaSerializable +@KeepForDJVM interface CordaThrowable { var originalExceptionClassName: String? val originalMessage: String? @@ -12,6 +13,7 @@ interface CordaThrowable { fun addSuppressed(suppressed: Array) } +@KeepForDJVM open class CordaException internal constructor(override var originalExceptionClassName: String? = null, private var _message: String? = null, private var _cause: Throwable? = null) : Exception(null, null, true, true), CordaThrowable { @@ -59,6 +61,7 @@ open class CordaException internal constructor(override var originalExceptionCla } } +@KeepForDJVM open class CordaRuntimeException(override var originalExceptionClassName: String?, private var _message: String?, private var _cause: Throwable?) : RuntimeException(null, null, true, true), CordaThrowable { diff --git a/core/src/main/kotlin/net/corda/core/CordaInternal.kt b/core/src/main/kotlin/net/corda/core/CordaInternal.kt index eb13b5b3c5..4b3cbf44c4 100644 --- a/core/src/main/kotlin/net/corda/core/CordaInternal.kt +++ b/core/src/main/kotlin/net/corda/core/CordaInternal.kt @@ -1,11 +1,14 @@ package net.corda.core +import kotlin.annotation.AnnotationRetention.* +import kotlin.annotation.AnnotationTarget.* + /** - * These methods are not part of Corda's API compatibility guarantee and applications should not use them. + * These methods and annotations are not part of Corda's API compatibility guarantee and applications should not use them. * - * These fields are only meant to be used by Corda internally, and are not intended to be part of the public API. + * These are only meant to be used by Corda internally, and are not intended to be part of the public API. */ -@Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FUNCTION) +@Target(PROPERTY_GETTER, PROPERTY_SETTER, FUNCTION, ANNOTATION_CLASS) +@Retention(BINARY) @MustBeDocumented annotation class CordaInternal \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/CordaOID.kt b/core/src/main/kotlin/net/corda/core/CordaOID.kt index 0292c940b6..644ade5fef 100644 --- a/core/src/main/kotlin/net/corda/core/CordaOID.kt +++ b/core/src/main/kotlin/net/corda/core/CordaOID.kt @@ -4,6 +4,7 @@ package net.corda.core * OIDs used for the Corda platform. Entries MUST NOT be removed from this file; if an OID is incorrectly assigned it * should be marked deprecated. */ +@KeepForDJVM object CordaOID { /** Assigned to R3, see http://www.oid-info.com/cgi-bin/display?oid=1.3.6.1.4.1.50530&action=display */ const val R3_ROOT = "1.3.6.1.4.1.50530" diff --git a/core/src/main/kotlin/net/corda/core/DeleteForDJVM.kt b/core/src/main/kotlin/net/corda/core/DeleteForDJVM.kt new file mode 100644 index 0000000000..36f94ac707 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/DeleteForDJVM.kt @@ -0,0 +1,26 @@ +package net.corda.core + +import kotlin.annotation.AnnotationRetention.* +import kotlin.annotation.AnnotationTarget.* +import kotlin.annotation.Retention +import kotlin.annotation.Target + +/** + * Declare the annotated element to unsuitable for the deterministic version of Corda. + */ +// DOCSTART 01 +@Target( + FILE, + CLASS, + CONSTRUCTOR, + FUNCTION, + PROPERTY_GETTER, + PROPERTY_SETTER, + PROPERTY, + FIELD, + TYPEALIAS +) +@Retention(BINARY) +@CordaInternal +annotation class DeleteForDJVM +// DOCEND 01 diff --git a/core/src/main/kotlin/net/corda/core/KeepForDJVM.kt b/core/src/main/kotlin/net/corda/core/KeepForDJVM.kt new file mode 100644 index 0000000000..0b25143247 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/KeepForDJVM.kt @@ -0,0 +1,19 @@ +package net.corda.core + +import kotlin.annotation.AnnotationRetention.* +import kotlin.annotation.AnnotationTarget.* +import kotlin.annotation.Retention +import kotlin.annotation.Target + +/** + * This annotates a class or file that we want to include into the deterministic version of Corda Core. + * We don't expect everything within that class/file to be deterministic; those non-deterministic + * elements need to be annotated with either [DeleteForDJVM] or [StubOutForDJVM] so that they + * can be deleted. + */ +// DOCSTART 01 +@Target(FILE, CLASS) +@Retention(BINARY) +@CordaInternal +annotation class KeepForDJVM +// DOCEND 01 \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/StubOutForDJVM.kt b/core/src/main/kotlin/net/corda/core/StubOutForDJVM.kt new file mode 100644 index 0000000000..d5e3fe7c5a --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/StubOutForDJVM.kt @@ -0,0 +1,24 @@ +package net.corda.core + +import kotlin.annotation.AnnotationRetention.* +import kotlin.annotation.AnnotationTarget.* +import kotlin.annotation.Retention +import kotlin.annotation.Target + +/** + * We expect that almost every non-deterministic element can have its bytecode + * deleted entirely from the deterministic version of Corda. This annotation is + * for those (hopefully!) few occasions where the non-deterministic function + * cannot be deleted. In these cases, the function will be stubbed out instead. + */ +// DOCSTART 01 +@Target( + CONSTRUCTOR, + FUNCTION, + PROPERTY_GETTER, + PROPERTY_SETTER +) +@Retention(BINARY) +@CordaInternal +annotation class StubOutForDJVM +// DOCEND 01 diff --git a/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt b/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt index 93aca8e49a..d225f66e92 100644 --- a/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt +++ b/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt @@ -1,5 +1,4 @@ @file:JvmName("ConcurrencyUtils") - package net.corda.core.concurrent import net.corda.core.internal.concurrent.openFuture diff --git a/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt b/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt index 6a86741c6f..64cdee0792 100644 --- a/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt +++ b/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt @@ -1,5 +1,7 @@ package net.corda.core.context +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.contracts.ScheduledStateRef import net.corda.core.identity.CordaX500Name import net.corda.core.serialization.CordaSerializable @@ -21,36 +23,42 @@ data class InvocationContext(val origin: InvocationOrigin, val trace: Trace, val /** * Creates an [InvocationContext] with a [Trace] that defaults to a [java.util.UUID] as value and [java.time.Instant.now] timestamp. */ + @DeleteForDJVM @JvmStatic fun newInstance(origin: InvocationOrigin, trace: Trace = Trace.newInstance(), actor: Actor? = null, externalTrace: Trace? = null, impersonatedActor: Actor? = null) = InvocationContext(origin, trace, actor, externalTrace, impersonatedActor) /** * Creates an [InvocationContext] with [InvocationOrigin.RPC] origin. */ + @DeleteForDJVM @JvmStatic fun rpc(actor: Actor, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(InvocationOrigin.RPC(actor), trace, actor, externalTrace, impersonatedActor) /** * Creates an [InvocationContext] with [InvocationOrigin.Peer] origin. */ + @DeleteForDJVM @JvmStatic fun peer(party: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(InvocationOrigin.Peer(party), trace, null, externalTrace, impersonatedActor) /** * Creates an [InvocationContext] with [InvocationOrigin.Service] origin. */ + @DeleteForDJVM @JvmStatic fun service(serviceClassName: String, owningLegalIdentity: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(InvocationOrigin.Service(serviceClassName, owningLegalIdentity), trace, null, externalTrace) /** * Creates an [InvocationContext] with [InvocationOrigin.Scheduled] origin. */ + @DeleteForDJVM @JvmStatic fun scheduled(scheduledState: ScheduledStateRef, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(InvocationOrigin.Scheduled(scheduledState), trace, null, externalTrace) /** * Creates an [InvocationContext] with [InvocationOrigin.Shell] origin. */ + @DeleteForDJVM @JvmStatic fun shell(trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = InvocationContext(InvocationOrigin.Shell, trace, null, externalTrace) } @@ -64,6 +72,7 @@ data class InvocationContext(val origin: InvocationOrigin, val trace: Trace, val /** * Models an initiator in Corda, can be a user, a service, etc. */ +@KeepForDJVM @CordaSerializable data class Actor(val id: Id, val serviceId: AuthServiceId, val owningLegalIdentity: CordaX500Name) { @@ -75,6 +84,7 @@ data class Actor(val id: Id, val serviceId: AuthServiceId, val owningLegalIdenti /** * Actor id. */ + @KeepForDJVM @CordaSerializable data class Id(val value: String) } @@ -82,6 +92,7 @@ data class Actor(val id: Id, val serviceId: AuthServiceId, val owningLegalIdenti /** * Represents the source of an action such as a flow start, an RPC, a shell command etc. */ +@DeleteForDJVM @CordaSerializable sealed class InvocationOrigin { /** @@ -129,5 +140,6 @@ sealed class InvocationOrigin { /** * Authentication / Authorisation Service ID. */ +@KeepForDJVM @CordaSerializable data class AuthServiceId(val value: String) \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/context/Trace.kt b/core/src/main/kotlin/net/corda/core/context/Trace.kt index d06e1c59c8..281d7fae28 100644 --- a/core/src/main/kotlin/net/corda/core/context/Trace.kt +++ b/core/src/main/kotlin/net/corda/core/context/Trace.kt @@ -1,5 +1,6 @@ package net.corda.core.context +import net.corda.core.DeleteForDJVM import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.Id import net.corda.core.utilities.UuidGenerator @@ -16,6 +17,7 @@ data class Trace(val invocationId: InvocationId, val sessionId: SessionId) { /** * Creates a trace using a [InvocationId.newInstance] with default arguments and a [SessionId] matching the value and timestamp from the invocation id.. */ + @DeleteForDJVM @JvmStatic fun newInstance(invocationId: InvocationId = InvocationId.newInstance(), sessionId: SessionId = SessionId(invocationId.value, invocationId.timestamp)) = Trace(invocationId, sessionId) } @@ -32,6 +34,7 @@ data class Trace(val invocationId: InvocationId, val sessionId: SessionId) { /** * Creates an invocation id using a [java.util.UUID] as value and [Instant.now] as timestamp. */ + @DeleteForDJVM @JvmStatic fun newInstance(value: String = UuidGenerator.next().toString(), timestamp: Instant = Instant.now()) = InvocationId(value, timestamp) } @@ -49,6 +52,7 @@ data class Trace(val invocationId: InvocationId, val sessionId: SessionId) { /** * Creates a session id using a [java.util.UUID] as value and [Instant.now] as timestamp. */ + @DeleteForDJVM @JvmStatic fun newInstance(value: String = UuidGenerator.next().toString(), timestamp: Instant = Instant.now()) = SessionId(value, timestamp) } diff --git a/core/src/main/kotlin/net/corda/core/contracts/Amount.kt b/core/src/main/kotlin/net/corda/core/contracts/Amount.kt index 3355954ca8..d010ab5aa5 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Amount.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Amount.kt @@ -1,5 +1,6 @@ package net.corda.core.contracts +import net.corda.core.KeepForDJVM import net.corda.core.crypto.CompositeKey import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable @@ -36,6 +37,7 @@ interface TokenizableAssetInfo { * @property token the type of token this is an amount of. This is usually a singleton. * @param T the type of the token, for example [Currency]. T should implement [TokenizableAssetInfo] if automatic conversion to/from a display format is required. */ +@KeepForDJVM @CordaSerializable data class Amount(val quantity: Long, val displayTokenSize: BigDecimal, val token: T) : Comparable> { // TODO Proper lookup of currencies in a locale and context sensitive fashion is not supported and is left to the application. diff --git a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt index 654fd51a89..66bdf08a30 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt @@ -1,5 +1,6 @@ package net.corda.core.contracts +import net.corda.core.KeepForDJVM import net.corda.core.identity.Party import net.corda.core.internal.extractFile import net.corda.core.serialization.CordaSerializable @@ -27,6 +28,7 @@ import java.util.jar.JarInputStream * Finally, using ZIPs ensures files have a timestamp associated with them, and enables informational attachments * to be password protected (although in current releases password protected ZIPs are likely to fail to work). */ +@KeepForDJVM @CordaSerializable interface Attachment : NamedByHash { fun open(): InputStream diff --git a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt index a3465f4bf3..4a07afa1be 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt @@ -1,6 +1,7 @@ package net.corda.core.contracts import net.corda.core.DoNotImplement +import net.corda.core.KeepForDJVM import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint.isSatisfiedBy import net.corda.core.crypto.SecureHash import net.corda.core.internal.AttachmentWithContext @@ -16,6 +17,7 @@ interface AttachmentConstraint { } /** An [AttachmentConstraint] where [isSatisfiedBy] always returns true. */ +@KeepForDJVM object AlwaysAcceptAttachmentConstraint : AttachmentConstraint { override fun isSatisfiedBy(attachment: Attachment) = true } @@ -25,6 +27,7 @@ object AlwaysAcceptAttachmentConstraint : AttachmentConstraint { * The state protected by this constraint can only be used in a transaction created with that version of the jar. * And a receiving node will only accept it if a cordapp with that hash has (is) been deployed on the node. */ +@KeepForDJVM data class HashAttachmentConstraint(val attachmentId: SecureHash) : AttachmentConstraint { override fun isSatisfiedBy(attachment: Attachment): Boolean { return if (attachment is AttachmentWithContext) { @@ -38,6 +41,7 @@ data class HashAttachmentConstraint(val attachmentId: SecureHash) : AttachmentCo * See: [net.corda.core.node.NetworkParameters.whitelistedContractImplementations] * It allows for centralized control over the cordapps that can be used. */ +@KeepForDJVM object WhitelistedByZoneAttachmentConstraint : AttachmentConstraint { override fun isSatisfiedBy(attachment: Attachment): Boolean { return if (attachment is AttachmentWithContext) { @@ -57,6 +61,7 @@ object WhitelistedByZoneAttachmentConstraint : AttachmentConstraint { * intent of this class is that it should be replaced by a correct [HashAttachmentConstraint] and verify against an * actual [Attachment]. */ +@KeepForDJVM object AutomaticHashConstraint : AttachmentConstraint { override fun isSatisfiedBy(attachment: Attachment): Boolean { throw UnsupportedOperationException("Contracts cannot be satisfied by an AutomaticHashConstraint placeholder") diff --git a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt index 9899bf27dd..7a19128c32 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt @@ -1,5 +1,6 @@ package net.corda.core.contracts +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializable /** @@ -9,6 +10,7 @@ import net.corda.core.serialization.CordaSerializable * @property contract The contract name contained within the JAR. A Contract attachment has to contain at least 1 contract. * @property additionalContracts Additional contract names contained within the JAR. */ +@KeepForDJVM @CordaSerializable class ContractAttachment @JvmOverloads constructor(val attachment: Attachment, val contract: ContractClassName, val additionalContracts: Set = emptySet(), val uploader: String? = null) : Attachment by attachment { diff --git a/core/src/main/kotlin/net/corda/core/contracts/ContractState.kt b/core/src/main/kotlin/net/corda/core/contracts/ContractState.kt index b9f3d48fc4..c932d503ad 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ContractState.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ContractState.kt @@ -1,5 +1,6 @@ package net.corda.core.contracts +import net.corda.core.KeepForDJVM import net.corda.core.identity.AbstractParty import net.corda.core.serialization.CordaSerializable @@ -11,6 +12,7 @@ import net.corda.core.serialization.CordaSerializable * notary is responsible for ensuring there is no "double spending" by only signing a transaction if the input states * are all free. */ +@KeepForDJVM @CordaSerializable interface ContractState { /** diff --git a/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt b/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt index 44ac7008ff..d7288b1c99 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ContractsDSL.kt @@ -1,7 +1,8 @@ @file:JvmName("ContractsDSL") - +@file:KeepForDJVM package net.corda.core.contracts +import net.corda.core.KeepForDJVM import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.internal.uncheckedCast diff --git a/core/src/main/kotlin/net/corda/core/contracts/FungibleAsset.kt b/core/src/main/kotlin/net/corda/core/contracts/FungibleAsset.kt index dea478ecc6..d5f522a7e1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/FungibleAsset.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/FungibleAsset.kt @@ -1,5 +1,6 @@ package net.corda.core.contracts +import net.corda.core.KeepForDJVM import net.corda.core.flows.FlowException import net.corda.core.identity.AbstractParty import java.security.PublicKey @@ -20,6 +21,7 @@ class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException * @param T a type that represents the asset in question. This should describe the basic type of the asset * (GBP, USD, oil, shares in company , etc.) and any additional metadata (issuer, grade, class, etc.). */ +@KeepForDJVM interface FungibleAsset : OwnableState { /** * Amount represents a positive quantity of some issued product which can be cash, tokens, assets, or generally diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index 1f2c907069..84c1a3de33 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -1,7 +1,9 @@ @file:JvmName("Structures") - +@file:KeepForDJVM package net.corda.core.contracts +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.crypto.SecureHash import net.corda.core.crypto.secureRandomBytes import net.corda.core.crypto.toStringShort @@ -43,6 +45,7 @@ interface NamedByHash { * of product may differentiate different kinds of asset within the same logical class e.g the currency, or * it may just be a type marker for a single custom asset. */ +@KeepForDJVM @CordaSerializable data class Issued(val issuer: PartyAndReference, val product: P) { init { @@ -69,11 +72,13 @@ fun Amount>.withoutIssuer(): Amount = Amount(quantity, di /** * Return structure for [OwnableState.withNewOwner] */ +@KeepForDJVM data class CommandAndState(val command: CommandData, val ownableState: OwnableState) /** * A contract state that can have a single owner. */ +@KeepForDJVM interface OwnableState : ContractState { /** There must be a MoveCommand signed by this key to claim the amount. */ val owner: AbstractParty @@ -110,6 +115,7 @@ data class ScheduledStateRef(val ref: StateRef, override val scheduledAt: Instan * for a particular [ContractState] have been processed/fired etc. If the activity is not "on ledger" then the * scheduled activity shouldn't be either. */ +@DeleteForDJVM data class ScheduledActivity(val logicRef: FlowLogicRef, override val scheduledAt: Instant) : Scheduled // DOCSTART 2 @@ -118,6 +124,7 @@ data class ScheduledActivity(val logicRef: FlowLogicRef, override val scheduledA * * This simplifies the job of tracking the current version of certain types of state in e.g. a vault. */ +@KeepForDJVM interface LinearState : ContractState { /** * Unique id shared by all LinearState states throughout history within the vaults of all parties. @@ -128,6 +135,7 @@ interface LinearState : ContractState { } // DOCEND 2 +@KeepForDJVM interface SchedulableState : ContractState { /** * Indicate whether there is some activity to be performed at some future point in time with respect to this @@ -138,6 +146,7 @@ interface SchedulableState : ContractState { * * @return null if there is no activity to schedule. */ + @DeleteForDJVM fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? } @@ -148,6 +157,7 @@ fun ContractState.hash(): SecureHash = SecureHash.sha256(serialize().bytes) * A stateref is a pointer (reference) to a state, this is an equivalent of an "outpoint" in Bitcoin. It records which * transaction defined the state and where in that transaction it was. */ +@KeepForDJVM @CordaSerializable // DOCSTART 8 data class StateRef(val txhash: SecureHash, val index: Int) { @@ -156,6 +166,7 @@ data class StateRef(val txhash: SecureHash, val index: Int) { // DOCEND 8 /** A StateAndRef is simply a (state, ref) pair. For instance, a vault (which holds available assets) contains these. */ +@KeepForDJVM @CordaSerializable // DOCSTART 7 data class StateAndRef(val state: TransactionState, val ref: StateRef) @@ -170,6 +181,7 @@ inline fun Iterable>.filt * Reference to something being stored or issued by a party e.g. in a vault or (more likely) on their normal * ledger. The reference is intended to be encrypted so it's meaningless to anyone other than the party. */ +@KeepForDJVM @CordaSerializable data class PartyAndReference(val party: AbstractParty, val reference: OpaqueBytes) { override fun toString() = "$party$reference" @@ -180,12 +192,14 @@ data class PartyAndReference(val party: AbstractParty, val reference: OpaqueByte interface CommandData /** Commands that inherit from this are intended to have no data items: it's only their presence that matters. */ +@KeepForDJVM abstract class TypeOnlyCommandData : CommandData { override fun equals(other: Any?) = other?.javaClass == javaClass override fun hashCode() = javaClass.name.hashCode() } /** Command data/content plus pubkey pair: the signature is stored at the end of the serialized bytes */ +@KeepForDJVM @CordaSerializable data class Command(val value: T, val signers: List) { // TODO Introduce NonEmptyList? @@ -200,6 +214,7 @@ data class Command(val value: T, val signers: List) } /** A common move command for contract states which can change owner. */ +@KeepForDJVM interface MoveCommand : CommandData { /** * Contract code the moved state(s) are for the attention of, for example to indicate that the states are moved in @@ -211,6 +226,7 @@ interface MoveCommand : CommandData { // DOCSTART 6 /** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */ +@KeepForDJVM @CordaSerializable data class CommandWithParties( val signers: List, @@ -229,6 +245,7 @@ data class CommandWithParties( * * TODO: Contract serialization is likely to change, so the annotation is likely temporary. */ +@KeepForDJVM @CordaSerializable interface Contract { /** @@ -260,6 +277,7 @@ annotation class LegalProseReference(val uri: String) * more than one state). * @param NewState the upgraded contract state. */ +@KeepForDJVM interface UpgradedContract : Contract { /** * Name of the contract this is an upgraded version of, used as part of verification of upgrade transactions. @@ -278,6 +296,7 @@ interface UpgradedContract : UpgradedContract { /** * A validator for the legacy (pre-upgrade) contract attachments on the transaction. @@ -295,8 +314,10 @@ interface UpgradedContractWithLegacyConstraint) : TransactionVerificationException(txId, "Duplicate inputs: ${duplicates.joinToString()}", null) /** @suppress This class is obsolete and nothing has ever used it. */ @Deprecated("This class is obsolete and nothing has ever used it.") + @DeleteForDJVM class MoreThanOneNotary(txId: SecureHash) : TransactionVerificationException(txId, "More than one notary", null) /** @suppress This class is obsolete and nothing has ever used it. */ @Deprecated("This class is obsolete and nothing has ever used it.") + @DeleteForDJVM class SignersMissing(txId: SecureHash, val missing: List) : TransactionVerificationException(txId, "Signers missing: ${missing.joinToString()}", null) /** @suppress This class is obsolete and nothing has ever used it. */ @Deprecated("This class is obsolete and nothing has ever used it.") + @DeleteForDJVM class InvalidNotaryChange(txId: SecureHash) : TransactionVerificationException(txId, "Detected a notary change. Outputs must use the same notary as inputs", null) } diff --git a/core/src/main/kotlin/net/corda/core/contracts/UniqueIdentifier.kt b/core/src/main/kotlin/net/corda/core/contracts/UniqueIdentifier.kt index 5f62e064de..f3a928ac17 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/UniqueIdentifier.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/UniqueIdentifier.kt @@ -1,5 +1,7 @@ package net.corda.core.contracts +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.internal.VisibleForTesting import net.corda.core.serialization.CordaSerializable import java.util.* @@ -17,7 +19,11 @@ import java.util.* * Subsequent copies and evolutions of a state should just copy the [externalId] and [id] fields unmodified. */ @CordaSerializable -data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID()) : Comparable { +@KeepForDJVM +data class UniqueIdentifier(val externalId: String?, val id: UUID) : Comparable { + @DeleteForDJVM constructor(externalId: String?) : this(externalId, UUID.randomUUID()) + @DeleteForDJVM constructor() : this(null) + override fun toString(): String = if (externalId != null) "${externalId}_$id" else id.toString() companion object { diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index a970c42451..bfd85d732c 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -1,5 +1,6 @@ package net.corda.core.cordapp +import net.corda.core.DeleteForDJVM import net.corda.core.DoNotImplement import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic @@ -31,6 +32,7 @@ import java.net.URL * @property jarHash Hash of the jar */ @DoNotImplement +@DeleteForDJVM interface Cordapp { val name: String val contractClassNames: List diff --git a/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt b/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt index b91acec452..ef14f417e0 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt @@ -1,5 +1,6 @@ package net.corda.core.cordapp +import net.corda.core.DeleteForDJVM import net.corda.core.crypto.SecureHash /** @@ -15,6 +16,7 @@ import net.corda.core.crypto.SecureHash * @property classLoader the classloader used to load this cordapp's classes * @property config Configuration for this CorDapp */ +@DeleteForDJVM class CordappContext internal constructor( val cordapp: Cordapp, val attachmentId: SecureHash?, diff --git a/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt b/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt index bf7864ee95..bec2d6ab59 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt @@ -1,5 +1,6 @@ package net.corda.core.cordapp +import net.corda.core.DeleteForDJVM import net.corda.core.DoNotImplement import net.corda.core.contracts.ContractClassName import net.corda.core.node.services.AttachmentId @@ -8,6 +9,7 @@ import net.corda.core.node.services.AttachmentId * Provides access to what the node knows about loaded applications. */ @DoNotImplement +@DeleteForDJVM interface CordappProvider { /** * Exposes the current CorDapp context which will contain information and configuration of the CorDapp that diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt index 59ae9c7766..be28292ff1 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt @@ -1,5 +1,7 @@ package net.corda.core.crypto +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.exactAdd import net.corda.core.utilities.sequence @@ -26,6 +28,7 @@ import java.util.* * @property threshold specifies the minimum total weight required (in the simple case – the minimum number of child * signatures required) to satisfy the sub-tree rooted at this node. */ +@KeepForDJVM @CordaSerializable class CompositeKey private constructor(val threshold: Int, children: List) : PublicKey { companion object { @@ -143,6 +146,7 @@ class CompositeKey private constructor(val threshold: Int, children: List, ASN1Object() { init { @@ -241,6 +245,7 @@ class CompositeKey private constructor(val threshold: Int, children: List = mutableListOf() diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeKeyFactory.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKeyFactory.kt index df7fc7c433..3e35032900 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CompositeKeyFactory.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeKeyFactory.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.DeleteForDJVM import java.security.* import java.security.spec.InvalidKeySpecException import java.security.spec.KeySpec @@ -8,6 +9,7 @@ import java.security.spec.X509EncodedKeySpec /** * Factory for generating composite keys from ASN.1 format key specifications. This is used by [CordaSecurityProvider]. */ +@DeleteForDJVM class CompositeKeyFactory : KeyFactorySpi() { @Throws(InvalidKeySpecException::class) diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt index b359e31991..56ececd801 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeSignature.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import net.corda.core.serialization.deserialize import java.io.ByteArrayOutputStream import java.security.* @@ -8,6 +9,7 @@ import java.security.spec.AlgorithmParameterSpec /** * Dedicated class for storing a set of signatures that comprise [CompositeKey]. */ +@KeepForDJVM class CompositeSignature : Signature(SIGNATURE_ALGORITHM) { companion object { const val SIGNATURE_ALGORITHM = "COMPOSITESIG" diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeSignaturesWithKeys.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeSignaturesWithKeys.kt index 0ea62b1f0e..1175f80837 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CompositeSignaturesWithKeys.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeSignaturesWithKeys.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializable /** @@ -7,6 +8,7 @@ import net.corda.core.serialization.CordaSerializable * serialization format. */ @CordaSerializable +@KeepForDJVM data class CompositeSignaturesWithKeys(val sigs: List) { companion object { val EMPTY = CompositeSignaturesWithKeys(emptyList()) diff --git a/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt index 5b195cf52f..3cffa20584 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt @@ -1,25 +1,37 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_KEY import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_SIGNATURE import org.bouncycastle.asn1.ASN1ObjectIdentifier +import net.corda.core.StubOutForDJVM import java.security.Provider +@KeepForDJVM class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") { companion object { const val PROVIDER_NAME = "Corda" } init { - put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", CompositeKeyFactory::class.java.name) + provideNonDeterministic(this) put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", CompositeSignature::class.java.name) - put("Alg.Alias.KeyFactory.$COMPOSITE_KEY", CompositeKey.KEY_ALGORITHM) - put("Alg.Alias.KeyFactory.OID.$COMPOSITE_KEY", CompositeKey.KEY_ALGORITHM) put("Alg.Alias.Signature.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM) put("Alg.Alias.Signature.OID.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM) } } +/** + * The core-deterministic module is not allowed to generate keys. + */ +@StubOutForDJVM +private fun provideNonDeterministic(provider: Provider) { + provider["KeyFactory.${CompositeKey.KEY_ALGORITHM}"] = CompositeKeyFactory::class.java.name + provider["Alg.Alias.KeyFactory.$COMPOSITE_KEY"] = CompositeKey.KEY_ALGORITHM + provider["Alg.Alias.KeyFactory.OID.$COMPOSITE_KEY"] = CompositeKey.KEY_ALGORITHM +} + +@KeepForDJVM object CordaObjectIdentifier { // UUID-based OID // TODO: Register for an OID space and issue our own shorter OID. diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index 43c7c0544e..a172f31747 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -1,5 +1,7 @@ package net.corda.core.crypto +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.crypto.internal.* import net.corda.core.serialization.serialize import net.i2p.crypto.eddsa.EdDSAEngine @@ -58,6 +60,7 @@ import javax.crypto.spec.SecretKeySpec *
  • SPHINCS256_SHA512 (SPHINCS-256 hash-based signature scheme using SHA512 as hash algorithm). * */ +@KeepForDJVM object Crypto { /** * RSA PKCS#1 signature scheme using SHA256 for message hashing. @@ -613,6 +616,7 @@ object Crypto { * @return a KeyPair for the requested signature scheme code name. * @throws IllegalArgumentException if the requested signature scheme is not supported. */ + @DeleteForDJVM @JvmStatic fun generateKeyPair(schemeCodeName: String): KeyPair = generateKeyPair(findSignatureScheme(schemeCodeName)) @@ -623,6 +627,7 @@ object Crypto { * @return a new [KeyPair] for the requested [SignatureScheme]. * @throws IllegalArgumentException if the requested signature scheme is not supported. */ + @DeleteForDJVM @JvmOverloads @JvmStatic fun generateKeyPair(signatureScheme: SignatureScheme = DEFAULT_SIGNATURE_SCHEME): KeyPair { @@ -789,6 +794,7 @@ object Crypto { * @return a new [KeyPair] from an entropy input. * @throws IllegalArgumentException if the requested signature scheme is not supported for KeyPair generation using an entropy input. */ + @DeleteForDJVM @JvmStatic fun deriveKeyPairFromEntropy(signatureScheme: SignatureScheme, entropy: BigInteger): KeyPair { return when (signatureScheme) { @@ -804,6 +810,7 @@ object Crypto { * @param entropy a [BigInteger] value. * @return a new [KeyPair] from an entropy input. */ + @DeleteForDJVM @JvmStatic fun deriveKeyPairFromEntropy(entropy: BigInteger): KeyPair = deriveKeyPairFromEntropy(DEFAULT_SIGNATURE_SCHEME, entropy) diff --git a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt index e7f7898dce..0f6b87e6d8 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt @@ -1,7 +1,10 @@ +@file:KeepForDJVM @file:JvmName("CryptoUtils") package net.corda.core.crypto +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.contracts.PrivacySalt import net.corda.core.crypto.internal.platformSecureRandomFactory import net.corda.core.serialization.SerializationDefaults @@ -124,6 +127,7 @@ operator fun KeyPair.component1(): PrivateKey = this.private operator fun KeyPair.component2(): PublicKey = this.public /** A simple wrapper that will make it easier to swap out the signature algorithm we use in future. */ +@DeleteForDJVM fun generateKeyPair(): KeyPair = Crypto.generateKeyPair() /** @@ -132,6 +136,7 @@ fun generateKeyPair(): KeyPair = Crypto.generateKeyPair() * @param entropy a [BigInteger] value. * @return a deterministically generated [KeyPair] for the [Crypto.DEFAULT_SIGNATURE_SCHEME]. */ +@DeleteForDJVM fun entropyToKeyPair(entropy: BigInteger): KeyPair = Crypto.deriveKeyPairFromEntropy(entropy) /** @@ -168,6 +173,7 @@ fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Cr * or if no strong [SecureRandom] implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty, * which should never happen and suggests an unusual JVM or non-standard Java library. */ +@DeleteForDJVM @Throws(NoSuchAlgorithmException::class) fun secureRandomBytes(numOfBytes: Int): ByteArray = ByteArray(numOfBytes).apply { newSecureRandom().nextBytes(this) } @@ -189,6 +195,7 @@ fun secureRandomBytes(numOfBytes: Int): ByteArray = ByteArray(numOfBytes).apply * or if no strong SecureRandom implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty, * which should never happen and suggests an unusual JVM or non-standard Java library. */ +@DeleteForDJVM @Throws(NoSuchAlgorithmException::class) fun newSecureRandom(): SecureRandom = platformSecureRandomFactory() @@ -196,6 +203,7 @@ fun newSecureRandom(): SecureRandom = platformSecureRandomFactory() * Returns a random positive non-zero long generated using a secure RNG. This function sacrifies a bit of entropy in order * to avoid potential bugs where the value is used in a context where negative numbers or zero are not expected. */ +@DeleteForDJVM fun random63BitValue(): Long { while (true) { val candidate = Math.abs(newSecureRandom().nextLong()) diff --git a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt index 7bd11ec661..24e4ce1587 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes import java.security.InvalidKeyException @@ -11,8 +12,10 @@ import java.security.SignatureException // should be renamed to match. /** A wrapper around a digital signature. */ @CordaSerializable +@KeepForDJVM open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) { /** A digital signature that identifies who the public key is owned by. */ + @KeepForDJVM open class WithKey(val by: PublicKey, bytes: ByteArray) : DigitalSignature(bytes) { /** * Utility to simplify the act of verifying a signature. diff --git a/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt index 73359726a8..8f32a0e8c3 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import java.util.* /** @@ -14,8 +15,8 @@ import java.util.* sealed class MerkleTree { abstract val hash: SecureHash - data class Leaf(override val hash: SecureHash) : MerkleTree() - data class Node(override val hash: SecureHash, val left: MerkleTree, val right: MerkleTree) : MerkleTree() + @KeepForDJVM data class Leaf(override val hash: SecureHash) : MerkleTree() + @KeepForDJVM data class Node(override val hash: SecureHash, val left: MerkleTree, val right: MerkleTree) : MerkleTree() companion object { private fun isPow2(num: Int): Boolean = num and (num - 1) == 0 diff --git a/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt b/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt index 002a357ced..151f00b950 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt @@ -1,9 +1,11 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import net.corda.core.identity.AnonymousParty import net.corda.core.serialization.CordaSerializable import java.security.PublicKey +@KeepForDJVM object NullKeys { @CordaSerializable object NullPublicKey : PublicKey, Comparable { diff --git a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt index 5ba48577dd..1ef11c341b 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt @@ -1,10 +1,12 @@ package net.corda.core.crypto import net.corda.core.CordaException +import net.corda.core.KeepForDJVM import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.serialization.CordaSerializable import java.util.* +@KeepForDJVM @CordaSerializable class MerkleTreeException(val reason: String) : CordaException("Partial Merkle Tree exception. Reason: $reason") @@ -42,6 +44,7 @@ class MerkleTreeException(val reason: String) : CordaException("Partial Merkle T * (there can be a difference in obtained leaves ordering - that's why it's a set comparison not hashing leaves into a tree). * If both equalities hold, we can assume that l3 and l5 belong to the transaction with root h15. */ +@KeepForDJVM @CordaSerializable class PartialMerkleTree(val root: PartialTree) { /** @@ -53,9 +56,9 @@ class PartialMerkleTree(val root: PartialTree) { */ @CordaSerializable sealed class PartialTree { - data class IncludedLeaf(val hash: SecureHash) : PartialTree() - data class Leaf(val hash: SecureHash) : PartialTree() - data class Node(val left: PartialTree, val right: PartialTree) : PartialTree() + @KeepForDJVM data class IncludedLeaf(val hash: SecureHash) : PartialTree() + @KeepForDJVM data class Leaf(val hash: SecureHash) : PartialTree() + @KeepForDJVM data class Node(val left: PartialTree, val right: PartialTree) : PartialTree() } companion object { diff --git a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt index 78ccc8b3ca..91b1e5ce55 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt @@ -1,5 +1,8 @@ +@file:KeepForDJVM package net.corda.core.crypto +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.parseAsHex @@ -10,6 +13,7 @@ import java.security.MessageDigest * Container for a cryptographically secure hash value. * Provides utilities for generating a cryptographic hash using different algorithms (currently only SHA-256 supported). */ +@KeepForDJVM @CordaSerializable sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { /** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes). */ @@ -77,6 +81,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { /** * Generates a random SHA-256 value. */ + @DeleteForDJVM @JvmStatic fun randomSHA256() = sha256(secureRandomBytes(32)) diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt b/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt index 124794b730..cfe0ec96a8 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SignableData.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializable /** @@ -12,4 +13,5 @@ import net.corda.core.serialization.CordaSerializable * @param signatureMetadata meta data required. */ @CordaSerializable +@KeepForDJVM data class SignableData(val txId: SecureHash, val signatureMetadata: SignatureMetadata) diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignatureMetadata.kt b/core/src/main/kotlin/net/corda/core/crypto/SignatureMetadata.kt index 99335bde4c..6c8d9c33e6 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SignatureMetadata.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SignatureMetadata.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializable /** @@ -12,4 +13,5 @@ import net.corda.core.serialization.CordaSerializable * @param schemeNumberID number id of the signature scheme used based on signer's key-pair, see [SignatureScheme.schemeNumberID]. */ @CordaSerializable +@KeepForDJVM data class SignatureMetadata(val platformVersion: Int, val schemeNumberID: Int) diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt index 6c1f87ed68..c14568f9fa 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import org.bouncycastle.asn1.x509.AlgorithmIdentifier import java.security.Signature import java.security.spec.AlgorithmParameterSpec @@ -20,6 +21,7 @@ import java.security.spec.AlgorithmParameterSpec * @param keySize the private key size (currently used for RSA only). * @param desc a human-readable description for this scheme. */ +@KeepForDJVM data class SignatureScheme( val schemeNumberID: Int, val schemeCodeName: String, diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt b/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt index 7e17b7b295..9a914405e6 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializedBytes @@ -14,6 +15,7 @@ import java.security.SignatureException * @param sig the (unverified) signature for the data. */ @CordaSerializable +@KeepForDJVM open class SignedData(val raw: SerializedBytes, val sig: DigitalSignature.WithKey) { /** * Return the deserialized data if the signature can be verified. diff --git a/core/src/main/kotlin/net/corda/core/crypto/TransactionSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/TransactionSignature.kt index 29d0a0d212..a26139db1d 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/TransactionSignature.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/TransactionSignature.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializable import java.security.InvalidKeyException import java.security.PublicKey @@ -15,6 +16,7 @@ import java.util.* * @property partialMerkleTree required when multi-transaction signing is utilised. */ @CordaSerializable +@KeepForDJVM class TransactionSignature(bytes: ByteArray, val by: PublicKey, val signatureMetadata: SignatureMetadata, val partialMerkleTree: PartialMerkleTree?) : DigitalSignature(bytes) { /** * Construct a [TransactionSignature] with [partialMerkleTree] set to null. diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt new file mode 100644 index 0000000000..755569df0d --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt @@ -0,0 +1,18 @@ +@file:JvmName("PlatformSecureRandom") +@file:DeleteForDJVM +package net.corda.core.crypto.internal + +import net.corda.core.DeleteForDJVM +import org.apache.commons.lang.SystemUtils +import java.security.SecureRandom + +/** + * This has been migrated into a separate class so that it + * is easier to delete from the core-deterministic module. + */ +internal val platformSecureRandom: () -> SecureRandom = when { + SystemUtils.IS_OS_LINUX -> { + { SecureRandom.getInstance("NativePRNGNonBlocking") } + } + else -> SecureRandom::getInstanceStrong +} diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt index 2981ebd65e..010d894453 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto.internal +import net.corda.core.DeleteForDJVM import net.corda.core.crypto.CordaSecurityProvider import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 import net.corda.core.crypto.Crypto.decodePrivateKey @@ -7,14 +8,12 @@ import net.corda.core.crypto.Crypto.decodePublicKey import net.corda.core.internal.X509EdDSAEngine import net.i2p.crypto.eddsa.EdDSAEngine import net.i2p.crypto.eddsa.EdDSASecurityProvider -import org.apache.commons.lang.SystemUtils import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider -import java.security.SecureRandom import java.security.Security internal val cordaSecurityProvider = CordaSecurityProvider().also { @@ -45,9 +44,6 @@ internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { // i.e. if someone removes a Provider and then he/she adds a new one with the same name. // The val is private to avoid any harmful state changes. internal val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap() -internal val platformSecureRandomFactory: () -> SecureRandom = when { - SystemUtils.IS_OS_LINUX -> { - { SecureRandom.getInstance("NativePRNGNonBlocking") } - } - else -> SecureRandom::getInstanceStrong -} + +@DeleteForDJVM +internal fun platformSecureRandomFactory() = platformSecureRandom() // To minimise diff of CryptoUtils against open-source. \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/StateMachineRunId.kt b/core/src/main/kotlin/net/corda/core/flows/StateMachineRunId.kt index 30f0d9599a..98956e1c24 100644 --- a/core/src/main/kotlin/net/corda/core/flows/StateMachineRunId.kt +++ b/core/src/main/kotlin/net/corda/core/flows/StateMachineRunId.kt @@ -1,5 +1,6 @@ package net.corda.core.flows +import net.corda.core.DeleteForDJVM import net.corda.core.serialization.CordaSerializable import java.util.* @@ -10,6 +11,7 @@ import java.util.* @CordaSerializable data class StateMachineRunId(val uuid: UUID) { companion object { + @DeleteForDJVM fun createRandom(): StateMachineRunId = StateMachineRunId(UUID.randomUUID()) } diff --git a/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt b/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt index 086d06c6c9..9e4ae47a0a 100644 --- a/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt +++ b/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt @@ -1,5 +1,6 @@ package net.corda.core.identity +import net.corda.core.KeepForDJVM import net.corda.core.contracts.PartyAndReference import net.corda.core.crypto.toStringShort import net.corda.core.utilities.OpaqueBytes @@ -9,6 +10,7 @@ import java.security.PublicKey * The [AnonymousParty] class contains enough information to uniquely identify a [Party] while excluding private * information such as name. It is intended to represent a party on the distributed ledger. */ +@KeepForDJVM class AnonymousParty(owningKey: PublicKey) : AbstractParty(owningKey) { override fun nameOrNull(): CordaX500Name? = null override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes) diff --git a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt index 3215129d3f..a304ef1595 100644 --- a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt +++ b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt @@ -1,6 +1,7 @@ package net.corda.core.identity import com.google.common.collect.ImmutableSet +import net.corda.core.KeepForDJVM import net.corda.core.internal.LegalNameValidator import net.corda.core.internal.unspecifiedCountry import net.corda.core.internal.x500Name @@ -30,6 +31,7 @@ import javax.security.auth.x500.X500Principal * attribute type. */ @CordaSerializable +@KeepForDJVM data class CordaX500Name(val commonName: String?, val organisationUnit: String?, val organisation: String, diff --git a/core/src/main/kotlin/net/corda/core/identity/Party.kt b/core/src/main/kotlin/net/corda/core/identity/Party.kt index 9316391ae7..828128bf5f 100644 --- a/core/src/main/kotlin/net/corda/core/identity/Party.kt +++ b/core/src/main/kotlin/net/corda/core/identity/Party.kt @@ -1,5 +1,6 @@ package net.corda.core.identity +import net.corda.core.KeepForDJVM import net.corda.core.contracts.PartyAndReference import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.Crypto @@ -26,6 +27,7 @@ import java.security.cert.X509Certificate * * @see CompositeKey */ +@KeepForDJVM class Party(val name: CordaX500Name, owningKey: PublicKey) : AbstractParty(owningKey) { constructor(certificate: X509Certificate) : this(CordaX500Name.build(certificate.subjectX500Principal), Crypto.toSupportedPublicKey(certificate.publicKey)) diff --git a/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt b/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt index 60d11c613f..a282390c88 100644 --- a/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt +++ b/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt @@ -1,5 +1,6 @@ package net.corda.core.identity +import net.corda.core.KeepForDJVM import net.corda.core.internal.CertRole import net.corda.core.internal.uncheckedCast import net.corda.core.internal.validate @@ -13,6 +14,7 @@ import java.security.cert.* * not part of the identifier themselves. */ @CordaSerializable +@KeepForDJVM class PartyAndCertificate(val certPath: CertPath) { @Transient val certificate: X509Certificate diff --git a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt index 1e05c8c4c2..b86890eb70 100644 --- a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt @@ -1,5 +1,8 @@ +@file:KeepForDJVM package net.corda.core.internal +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party @@ -23,8 +26,10 @@ const val UNKNOWN_UPLOADER = "unknown" fun isUploaderTrusted(uploader: String?) = uploader?.let { it in listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, TEST_UPLOADER) } ?: false +@KeepForDJVM abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment { companion object { + @DeleteForDJVM fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray { return { val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id)) diff --git a/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt b/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt index 7b3283b28a..231550b26f 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt @@ -1,10 +1,12 @@ package net.corda.core.internal +import net.corda.core.DeleteForDJVM import net.corda.core.contracts.* import net.corda.core.node.ServicesForResolution import net.corda.core.node.services.AttachmentId import net.corda.core.transactions.ContractUpgradeWireTransaction +@DeleteForDJVM object ContractUpgradeUtils { fun assembleUpgradeTx( stateAndRef: StateAndRef, diff --git a/core/src/main/kotlin/net/corda/core/internal/Emoji.kt b/core/src/main/kotlin/net/corda/core/internal/Emoji.kt index 4d7d8b9dcd..7fc84f3e87 100644 --- a/core/src/main/kotlin/net/corda/core/internal/Emoji.kt +++ b/core/src/main/kotlin/net/corda/core/internal/Emoji.kt @@ -1,8 +1,11 @@ package net.corda.core.internal +import net.corda.core.DeleteForDJVM + /** * A simple wrapper class that contains icons and support for printing them only when we're connected to a terminal. */ +@DeleteForDJVM object Emoji { // Unfortunately only Apple has a terminal that can do colour emoji AND an emoji font installed by default. // However the JediTerm java terminal emulator can also do emoji on OS X when using the JetBrains JRE. diff --git a/core/src/main/kotlin/net/corda/core/internal/FlowIORequest.kt b/core/src/main/kotlin/net/corda/core/internal/FlowIORequest.kt index 0f159be31f..2c70cd54d1 100644 --- a/core/src/main/kotlin/net/corda/core/internal/FlowIORequest.kt +++ b/core/src/main/kotlin/net/corda/core/internal/FlowIORequest.kt @@ -1,5 +1,6 @@ package net.corda.core.internal +import net.corda.core.DeleteForDJVM import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowInfo import net.corda.core.flows.FlowSession @@ -11,6 +12,7 @@ import java.time.Instant /** * A [FlowIORequest] represents an IO request of a flow when it suspends. It is persisted in checkpoints. */ +@DeleteForDJVM sealed class FlowIORequest { /** * Send messages to sessions. diff --git a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt index fc6a5a529e..1cdf50f088 100644 --- a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt +++ b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt @@ -4,10 +4,7 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.context.InvocationContext -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.FlowSession -import net.corda.core.flows.FlowStackSnapshot -import net.corda.core.flows.StateMachineRunId +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import org.slf4j.Logger diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index a00be517b0..a5fda08f08 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -1,9 +1,11 @@ @file:JvmName("InternalUtils") - +@file:KeepForDJVM package net.corda.core.internal import com.google.common.hash.Hashing import com.google.common.hash.HashingInputStream +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappConfig import net.corda.core.cordapp.CordappContext @@ -108,6 +110,7 @@ fun List.noneOrSingle(): T? { } /** Returns a random element in the list, or `null` if empty */ +@DeleteForDJVM fun List.randomOrNull(): T? { return when (size) { 0 -> null @@ -123,7 +126,7 @@ fun List.indexOfOrThrow(item: T): Int { return i } -fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) +@DeleteForDJVM fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) /** Same as [InputStream.readBytes] but also closes the stream. */ fun InputStream.readFully(): ByteArray = use { it.readBytes() } @@ -154,6 +157,7 @@ fun Iterable.sum(): BigDecimal = fold(BigDecimal.ZERO) { a, b -> a + * Returns an Observable that buffers events until subscribed. * @see UnicastSubject */ +@DeleteForDJVM fun Observable.bufferUntilSubscribed(): Observable { val subject = UnicastSubject.create() val subscription = subscribe(subject) @@ -161,6 +165,7 @@ fun Observable.bufferUntilSubscribed(): Observable { } /** Copy an [Observer] to multiple other [Observer]s. */ +@DeleteForDJVM fun Observer.tee(vararg teeTo: Observer): Observer { val subject = PublishSubject.create() subject.subscribe(this) @@ -169,6 +174,7 @@ fun Observer.tee(vararg teeTo: Observer): Observer { } /** Executes the given code block and returns a [Duration] of how long it took to execute in nanosecond precision. */ +@DeleteForDJVM inline fun elapsedTime(block: () -> Unit): Duration { val start = System.nanoTime() block() @@ -181,6 +187,7 @@ fun Logger.logElapsedTime(label: String, body: () -> T): T = logElapsedTime( // TODO: Add inline back when a new Kotlin version is released and check if the java.lang.VerifyError // returns in the IRSSimulationTest. If not, commit the inline back. +@DeleteForDJVM fun logElapsedTime(label: String, logger: Logger? = null, body: () -> T): T { // Use nanoTime as it's monotonic. val now = System.nanoTime() @@ -208,6 +215,7 @@ fun ByteArrayOutputStream.toInputStreamAndHash(): InputStreamAndHash { return InputStreamAndHash(bytes.inputStream(), bytes.sha256()) } +@KeepForDJVM data class InputStreamAndHash(val inputStream: InputStream, val sha256: SecureHash.SHA256) { companion object { /** @@ -215,6 +223,7 @@ data class InputStreamAndHash(val inputStream: InputStream, val sha256: SecureHa * called "z" that contains the given content byte repeated the given number of times. * Note that a slightly bigger than numOfExpectedBytes size is expected. */ + @DeleteForDJVM fun createInMemoryTestZip(numOfExpectedBytes: Int, content: Byte): InputStreamAndHash { require(numOfExpectedBytes > 0) val baos = ByteArrayOutputStream() @@ -268,24 +277,29 @@ inline fun Stream.mapNotNull(crossinline transform: (T) -> R?): fun Class.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null /** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */ +@DeleteForDJVM fun Class<*>.staticField(name: String): DeclaredField = DeclaredField(this, name, null) /** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [KClass]. */ +@DeleteForDJVM fun KClass<*>.staticField(name: String): DeclaredField = DeclaredField(java, name, null) /** Returns a [DeclaredField] wrapper around the declared (possibly non-public) instance field of the receiver object. */ +@DeleteForDJVM fun Any.declaredField(name: String): DeclaredField = DeclaredField(javaClass, name, this) /** * Returns a [DeclaredField] wrapper around the (possibly non-public) instance field of the receiver object, but declared * in its superclass [clazz]. */ +@DeleteForDJVM fun Any.declaredField(clazz: KClass<*>, name: String): DeclaredField = DeclaredField(clazz.java, name, this) /** * Returns a [DeclaredField] wrapper around the (possibly non-public) instance field of the receiver object, but declared * in its superclass [clazz]. */ +@DeleteForDJVM fun Any.declaredField(clazz: Class<*>, name: String): DeclaredField = DeclaredField(clazz, name, this) /** creates a new instance if not a Kotlin object */ @@ -314,6 +328,7 @@ val Class.kotlinObjectInstance: T? get() { * A simple wrapper around a [Field] object providing type safe read and write access using [value], ignoring the field's * visibility. */ +@DeleteForDJVM class DeclaredField(clazz: Class<*>, name: String, private val receiver: Any?) { private val javaField = findField(name, clazz) var value: T @@ -370,12 +385,12 @@ fun uncheckedCast(obj: T) = obj as U fun Iterable>.toMultiMap(): Map> = this.groupBy({ it.first }) { it.second } /** Provide access to internal method for AttachmentClassLoaderTests */ -fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction { +@DeleteForDJVM fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction { return toWireTransactionWithContext(services, serializationContext) } /** Provide access to internal method for AttachmentClassLoaderTests */ -fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext) +@DeleteForDJVM fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext) /** Convenience method to get the package name of a class literal. */ val KClass<*>.packageName: String get() = java.packageName @@ -391,12 +406,14 @@ inline val Member.isStatic: Boolean get() = Modifier.isStatic(modifiers) inline val Member.isFinal: Boolean get() = Modifier.isFinal(modifiers) -fun URI.toPath(): Path = Paths.get(this) +@DeleteForDJVM fun URI.toPath(): Path = Paths.get(this) -fun URL.toPath(): Path = toURI().toPath() +@DeleteForDJVM fun URL.toPath(): Path = toURI().toPath() +@DeleteForDJVM fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection +@DeleteForDJVM fun URL.post(serializedData: OpaqueBytes, vararg properties: Pair): ByteArray { return openHttpConnection().run { doOutput = true @@ -409,20 +426,24 @@ fun URL.post(serializedData: OpaqueBytes, vararg properties: Pair HttpURLConnection.responseAs(): T { checkOkResponse() return inputStream.readObject() } /** Analogous to [Thread.join]. */ +@DeleteForDJVM fun ExecutorService.join() { shutdown() // Do not change to shutdownNow, tests use this method to assert the executor has no more tasks. while (!awaitTermination(1, TimeUnit.SECONDS)) { diff --git a/core/src/main/kotlin/net/corda/core/internal/LazyPool.kt b/core/src/main/kotlin/net/corda/core/internal/LazyPool.kt index 196342194f..bc2d0132a6 100644 --- a/core/src/main/kotlin/net/corda/core/internal/LazyPool.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LazyPool.kt @@ -1,5 +1,6 @@ package net.corda.core.internal +import net.corda.core.DeleteForDJVM import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.Semaphore @@ -14,6 +15,7 @@ import java.util.concurrent.Semaphore * instance is released. * @param newInstance The function to call to lazily newInstance a pooled resource. */ +@DeleteForDJVM class LazyPool( private val clear: ((A) -> Unit)? = null, private val shouldReturnToPool: ((A) -> Boolean)? = null, diff --git a/core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt b/core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt index 5d6a156803..52ec2226ae 100644 --- a/core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt @@ -1,5 +1,6 @@ package net.corda.core.internal +import net.corda.core.DeleteForDJVM import java.util.* import java.util.concurrent.LinkedBlockingQueue @@ -11,6 +12,7 @@ import java.util.concurrent.LinkedBlockingQueue * @param newInstance The function to call to create a pooled resource. */ // TODO This could be implemented more efficiently. Currently the "non-sticky" use case is not optimised, it just chooses a random instance to wait on. +@DeleteForDJVM class LazyStickyPool( size: Int, private val newInstance: () -> A diff --git a/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt b/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt index 9f97612852..db15d33356 100644 --- a/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt @@ -1,5 +1,6 @@ package net.corda.core.internal +import net.corda.core.KeepForDJVM import net.corda.core.internal.LegalNameValidator.normalize import java.text.Normalizer import javax.security.auth.x500.X500Principal @@ -105,12 +106,14 @@ object LegalNameValidator { abstract fun validate(legalName: T) + @KeepForDJVM private class UnicodeNormalizationRule : Rule() { override fun validate(legalName: String) { require(legalName == normalize(legalName)) { "Legal name must be normalized. Please use 'normalize' to normalize the legal name before validation." } } } + @KeepForDJVM private class UnicodeRangeRule(vararg supportScripts: Character.UnicodeBlock) : Rule() { val supportScriptsSet = supportScripts.toSet() @@ -121,6 +124,7 @@ object LegalNameValidator { } } + @KeepForDJVM private class CharacterRule(vararg val bannedChars: Char) : Rule() { override fun validate(legalName: String) { bannedChars.forEach { @@ -129,6 +133,7 @@ object LegalNameValidator { } } + @KeepForDJVM private class WordRule(vararg val bannedWords: String) : Rule() { override fun validate(legalName: String) { bannedWords.forEach { @@ -137,12 +142,14 @@ object LegalNameValidator { } } + @KeepForDJVM private class LengthRule(val maxLength: Int) : Rule() { override fun validate(legalName: String) { require(legalName.length <= maxLength) { "Legal name longer then $maxLength characters." } } } + @KeepForDJVM private class CapitalLetterRule : Rule() { override fun validate(legalName: String) { val capitalizedLegalName = legalName.capitalize() @@ -150,6 +157,7 @@ object LegalNameValidator { } } + @KeepForDJVM private class X500NameRule : Rule() { override fun validate(legalName: String) { // This will throw IllegalArgumentException if the name does not comply with X500 name format. @@ -157,6 +165,7 @@ object LegalNameValidator { } } + @KeepForDJVM private class MustHaveAtLeastTwoLettersRule : Rule() { override fun validate(legalName: String) { // Try to exclude names like "/", "£", "X" etc. diff --git a/core/src/main/kotlin/net/corda/core/internal/LifeCycle.kt b/core/src/main/kotlin/net/corda/core/internal/LifeCycle.kt index 96786ea3e9..da6e334f27 100644 --- a/core/src/main/kotlin/net/corda/core/internal/LifeCycle.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LifeCycle.kt @@ -1,5 +1,6 @@ package net.corda.core.internal +import net.corda.core.DeleteForDJVM import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.withLock @@ -9,6 +10,7 @@ import kotlin.concurrent.withLock * * @param initial The initial state. */ +@DeleteForDJVM class LifeCycle>(initial: S) { private val lock = ReentrantReadWriteLock() private var state = initial diff --git a/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt b/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt index 2b60d96ff1..43a681eb57 100644 --- a/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt @@ -1,5 +1,7 @@ +@file:DeleteForDJVM package net.corda.core.internal +import net.corda.core.DeleteForDJVM import net.corda.core.crypto.SecureHash import net.corda.core.serialization.deserialize import java.io.* diff --git a/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt b/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt index 00c5a56d70..8413caa3b1 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt @@ -1,6 +1,7 @@ package net.corda.core.internal import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DeleteForDJVM import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic @@ -22,6 +23,7 @@ import kotlin.math.min * * @return a list of verified [SignedTransaction] objects, in a depth-first order. */ +@DeleteForDJVM class ResolveTransactionsFlow(txHashesArg: Set, private val otherSide: FlowSession) : FlowLogic() { @@ -38,6 +40,7 @@ class ResolveTransactionsFlow(txHashesArg: Set, this.signedTransaction = signedTransaction } + @DeleteForDJVM companion object { private fun dependencyIDs(stx: SignedTransaction) = stx.inputs.map { it.txhash }.toSet() diff --git a/core/src/main/kotlin/net/corda/core/internal/ThreadBox.kt b/core/src/main/kotlin/net/corda/core/internal/ThreadBox.kt index eedd576694..b19439710b 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ThreadBox.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ThreadBox.kt @@ -1,5 +1,6 @@ package net.corda.core.internal +import net.corda.core.DeleteForDJVM import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -21,6 +22,7 @@ import kotlin.concurrent.withLock * val ii = state.locked { i } * ``` */ +@DeleteForDJVM class ThreadBox(val content: T, val lock: ReentrantLock = ReentrantLock()) { inline fun locked(body: T.() -> R): R = lock.withLock { body(content) } inline fun alreadyLocked(body: T.() -> R): R { diff --git a/core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt b/core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt index 87ae81d527..5c0d6ebe80 100644 --- a/core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt +++ b/core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt @@ -1,5 +1,6 @@ package net.corda.core.internal +import net.corda.core.DeleteForDJVM import net.i2p.crypto.eddsa.EdDSAEngine import net.i2p.crypto.eddsa.EdDSAPublicKey import java.security.* @@ -24,6 +25,7 @@ class X509EdDSAEngine : Signature { } override fun engineInitSign(privateKey: PrivateKey) = engine.initSign(privateKey) + @DeleteForDJVM override fun engineInitSign(privateKey: PrivateKey, random: SecureRandom) = engine.initSign(privateKey, random) override fun engineInitVerify(publicKey: PublicKey) { diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt index d4092478cf..cf8ddc85a0 100644 --- a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt @@ -1,5 +1,6 @@ package net.corda.core.internal.cordapp +import net.corda.core.DeleteForDJVM import net.corda.core.cordapp.Cordapp import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic @@ -10,6 +11,7 @@ import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import java.net.URL +@DeleteForDJVM data class CordappImpl( override val contractClassNames: List, override val initiatedFlows: List>>, diff --git a/core/src/main/kotlin/net/corda/core/internal/notary/NotaryService.kt b/core/src/main/kotlin/net/corda/core/internal/notary/NotaryService.kt index 68041a760a..5b1c1b35af 100644 --- a/core/src/main/kotlin/net/corda/core/internal/notary/NotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/internal/notary/NotaryService.kt @@ -1,5 +1,6 @@ package net.corda.core.internal.notary +import net.corda.core.DeleteForDJVM import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryFlow @@ -8,6 +9,7 @@ import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializeAsToken import java.security.PublicKey +@DeleteForDJVM abstract class NotaryService : SingletonSerializeAsToken() { abstract val services: ServiceHub abstract val notaryIdentityKey: PublicKey diff --git a/core/src/main/kotlin/net/corda/core/node/AppServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/AppServiceHub.kt index e8dabb91ce..62e5e2f1f6 100644 --- a/core/src/main/kotlin/net/corda/core/node/AppServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/AppServiceHub.kt @@ -1,5 +1,6 @@ package net.corda.core.node +import net.corda.core.DeleteForDJVM import net.corda.core.flows.FlowLogic import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowProgressHandle @@ -11,6 +12,7 @@ import rx.Observable * With the [AppServiceHub] parameter a [CordaService] is able to access to privileged operations. * In particular such a [CordaService] can initiate and track flows marked with [net.corda.core.flows.StartableByService]. */ +@DeleteForDJVM interface AppServiceHub : ServiceHub { /** diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt index 11970f62f4..05cabdbed1 100644 --- a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt +++ b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt @@ -1,5 +1,6 @@ package net.corda.core.node +import net.corda.core.KeepForDJVM import net.corda.core.identity.Party import net.corda.core.node.services.AttachmentId import net.corda.core.serialization.CordaSerializable @@ -23,6 +24,7 @@ import java.time.Instant * @property eventHorizon Time after which nodes will be removed from the network map if they have not been seen * during this period */ +@KeepForDJVM @CordaSerializable data class NetworkParameters( val minimumPlatformVersion: Int, @@ -100,5 +102,6 @@ data class NetworkParameters( * @property identity Identity of the notary (note that it can be an identity of the distributed node). * @property validating Indicates if the notary is validating. */ +@KeepForDJVM @CordaSerializable data class NotaryInfo(val identity: Party, val validating: Boolean) diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 30badc8807..8c304d81d2 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -1,5 +1,6 @@ package net.corda.core.node +import net.corda.core.DeleteForDJVM import net.corda.core.DoNotImplement import net.corda.core.contracts.* import net.corda.core.cordapp.CordappContext @@ -22,6 +23,7 @@ import java.time.Clock * Subset of node services that are used for loading transactions from the wire into fully resolved, looked up * forms ready for verification. */ +@DeleteForDJVM @DoNotImplement interface ServicesForResolution { /** @@ -93,6 +95,7 @@ enum class StatesToRecord { * * In unit test environments, some of those services may be missing or mocked out. */ +@DeleteForDJVM interface ServiceHub : ServicesForResolution { // NOTE: Any services exposed to flows (public view) need to implement [SerializeAsToken] or similar to avoid // their internal state from being serialized in checkpoints. diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index 395e5cbccb..0ab6627f0e 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DeleteForDJVM import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.AbstractParty @@ -58,7 +59,7 @@ interface NetworkMapCacheBase { /** Tracks changes to the network map cache. */ val changed: Observable /** Future to track completion of the NetworkMapService registration. */ - val nodeReady: CordaFuture + @get:DeleteForDJVM val nodeReady: CordaFuture /** * Atomically get the current party nodes and a stream of updates. Note that the Observable buffers updates until the diff --git a/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt b/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt index a4eeb0b487..b7bf3611fa 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DeleteForDJVM import net.corda.core.contracts.TimeWindow import java.time.Clock @@ -7,6 +8,7 @@ import java.time.Clock * Checks if the current instant provided by the input clock falls within the provided time-window. */ @Deprecated("This class is no longer used") +@DeleteForDJVM class TimeWindowChecker(val clock: Clock = Clock.systemUTC()) { fun isValid(timeWindow: TimeWindow): Boolean = clock.instant() in timeWindow } diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt index b04c96729f..5bdb494be6 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DeleteForDJVM import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.SecureHash @@ -10,6 +11,7 @@ import rx.Observable /** * Thread-safe storage of transactions. */ +@DeleteForDJVM @DoNotImplement interface TransactionStorage { /** diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt index d72eec72f2..e1c731ce86 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DeleteForDJVM import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.transactions.LedgerTransaction @@ -9,6 +10,7 @@ import net.corda.core.transactions.LedgerTransaction * @suppress */ @DoNotImplement +@DeleteForDJVM interface TransactionVerifierService { /** * @param transaction The transaction to be verified. diff --git a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt index b7606475f3..1865a2e61c 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt @@ -1,6 +1,7 @@ package net.corda.core.node.services import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DeleteForDJVM import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* @@ -177,6 +178,7 @@ interface VaultService { /** * Provide a [CordaFuture] for when a [StateRef] is consumed, which can be very useful in building tests. */ + @DeleteForDJVM fun whenConsumed(ref: StateRef): CordaFuture> { return updates.filter { it.consumed.any { it.ref == ref } }.toFuture() } diff --git a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt index fccbbf1ffb..6e6e7ea34f 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt @@ -1,5 +1,6 @@ package net.corda.core.schemas +import net.corda.core.KeepForDJVM import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.serialization.CordaSerializable @@ -15,6 +16,7 @@ import javax.persistence.MappedSuperclass * A contract state that may be mapped to database schemas configured for this node to support querying for, * or filtering of, states. */ +@KeepForDJVM interface QueryableState : ContractState { /** * Enumerate the schemas this state can export representations of itself as. @@ -37,6 +39,7 @@ interface QueryableState : ContractState { * @param version The version number of this instance within the family. * @param mappedTypes The JPA entity classes that the ORM layer needs to be configure with for this schema. */ +@KeepForDJVM open class MappedSchema(schemaFamily: Class<*>, val version: Int, val mappedTypes: Iterable>) { @@ -69,6 +72,7 @@ open class MappedSchema(schemaFamily: Class<*>, * A super class for all mapped states exported to a schema that ensures the [StateRef] appears on the database row. The * [StateRef] will be set to the correct value by the framework (there's no need to set during mapping generation by the state itself). */ +@KeepForDJVM @MappedSuperclass @CordaSerializable class PersistentState(@EmbeddedId var stateRef: PersistentStateRef? = null) : StatePersistable @@ -76,6 +80,7 @@ class PersistentState(@EmbeddedId var stateRef: PersistentStateRef? = null) : St /** * Embedded [StateRef] representation used in state mapping. */ +@KeepForDJVM @Embeddable data class PersistentStateRef( @Column(name = "transaction_id", length = 64, nullable = false) @@ -90,8 +95,8 @@ data class PersistentStateRef( /** * Marker interface to denote a persistable Corda state entity that will always have a transaction id and index */ +@KeepForDJVM interface StatePersistable : Serializable - object MappedSchemaValidator { fun fieldsFromOtherMappedSchema(schema: MappedSchema) : List = schema.mappedTypes.map { entity -> diff --git a/core/src/main/kotlin/net/corda/core/serialization/MissingAttachmentsException.kt b/core/src/main/kotlin/net/corda/core/serialization/MissingAttachmentsException.kt index 0799e9a270..a3b137227d 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/MissingAttachmentsException.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/MissingAttachmentsException.kt @@ -1,8 +1,10 @@ package net.corda.core.serialization import net.corda.core.CordaException +import net.corda.core.KeepForDJVM import net.corda.core.crypto.SecureHash /** Thrown during deserialization to indicate that an attachment needed to construct the [WireTransaction] is not found. */ +@KeepForDJVM @CordaSerializable class MissingAttachmentsException(val ids: List) : CordaException() \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index 82755a9efd..7e92f3cf15 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -1,7 +1,10 @@ +@file:KeepForDJVM package net.corda.core.serialization import net.corda.core.CordaInternal +import net.corda.core.DeleteForDJVM import net.corda.core.DoNotImplement +import net.corda.core.KeepForDJVM import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.serialization.internal.effectiveSerializationEnv @@ -107,6 +110,7 @@ interface SerializationEncoding /** * Parameters to serialization and deserialization. */ +@KeepForDJVM @DoNotImplement interface SerializationContext { /** @@ -156,6 +160,7 @@ interface SerializationContext { /** * Helper method to return a new context based on this context with the deserialization class loader changed. */ + @DeleteForDJVM fun withClassLoader(classLoader: ClassLoader): SerializationContext /** @@ -183,6 +188,7 @@ interface SerializationContext { /** * The use case that we are serializing for, since it influences the implementations chosen. */ + @KeepForDJVM enum class UseCase { P2P, RPCServer, RPCClient, Storage, Checkpoint, Testing } } @@ -191,6 +197,7 @@ interface SerializationContext { * others being set that aren't keyed on this enumeration, but for general use properties adding a * well known key here is preferred. */ +@KeepForDJVM enum class ContextPropertyKeys { SERIALIZERS } @@ -198,13 +205,14 @@ enum class ContextPropertyKeys { /** * Global singletons to be used as defaults that are injected elsewhere (generally, in the node or in RPC client). */ +@KeepForDJVM object SerializationDefaults { val SERIALIZATION_FACTORY get() = effectiveSerializationEnv.serializationFactory val P2P_CONTEXT get() = effectiveSerializationEnv.p2pContext - val RPC_SERVER_CONTEXT get() = effectiveSerializationEnv.rpcServerContext - val RPC_CLIENT_CONTEXT get() = effectiveSerializationEnv.rpcClientContext - val STORAGE_CONTEXT get() = effectiveSerializationEnv.storageContext - val CHECKPOINT_CONTEXT get() = effectiveSerializationEnv.checkpointContext + @DeleteForDJVM val RPC_SERVER_CONTEXT get() = effectiveSerializationEnv.rpcServerContext + @DeleteForDJVM val RPC_CLIENT_CONTEXT get() = effectiveSerializationEnv.rpcClientContext + @DeleteForDJVM val STORAGE_CONTEXT get() = effectiveSerializationEnv.storageContext + @DeleteForDJVM val CHECKPOINT_CONTEXT get() = effectiveSerializationEnv.checkpointContext } /** @@ -244,6 +252,7 @@ inline fun ByteArray.deserialize(serializationFactory: Seriali /** * Convenience extension method for deserializing a JDBC Blob, utilising the defaults. */ +@DeleteForDJVM inline fun Blob.deserialize(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, context: SerializationContext = serializationFactory.defaultContext): T { return this.getBytes(1, this.length().toInt()).deserialize(serializationFactory, context) @@ -262,6 +271,7 @@ fun T.serialize(serializationFactory: SerializationFactory = Serializa * to get the original object back. */ @Suppress("unused") +@KeepForDJVM class SerializedBytes(bytes: ByteArray) : OpaqueBytes(bytes) { companion object { /** @@ -285,10 +295,12 @@ class SerializedBytes(bytes: ByteArray) : OpaqueBytes(bytes) { val hash: SecureHash by lazy { bytes.sha256() } } +@KeepForDJVM interface ClassWhitelist { fun hasListed(type: Class<*>): Boolean } +@KeepForDJVM @DoNotImplement interface EncodingWhitelist { fun acceptEncoding(encoding: SerializationEncoding): Boolean diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt index 05852302ce..d0c910b638 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt @@ -1,5 +1,7 @@ package net.corda.core.serialization +import net.corda.core.KeepForDJVM + /** * Allows CorDapps to provide custom serializers for third party libraries where those libraries cannot * be recompiled with the -parameters flag rendering their classes natively serializable by Corda. In this case @@ -9,6 +11,7 @@ package net.corda.core.serialization * NOTE: The proxy object should be specified as a separate class. However, this can be defined within the * scope of the custom serializer. */ +@KeepForDJVM interface SerializationCustomSerializer { /** * Should facilitate the conversion of the third party object into the serializable diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt index 9ec9fb75c0..1bc11091f3 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt @@ -1,5 +1,6 @@ package net.corda.core.serialization +import net.corda.core.DeleteForDJVM import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializationToken.Companion.singletonSerializationToken @@ -18,6 +19,7 @@ import net.corda.core.serialization.SingletonSerializationToken.Companion.single * * This models a similar pattern to the readReplace/writeReplace methods in Java serialization. */ +@DeleteForDJVM @CordaSerializable interface SerializeAsToken { fun toToken(context: SerializeAsTokenContext): SerializationToken @@ -26,6 +28,7 @@ interface SerializeAsToken { /** * This represents a token in the serialized stream for an instance of a type that implements [SerializeAsToken]. */ +@DeleteForDJVM interface SerializationToken { fun fromToken(context: SerializeAsTokenContext): Any } @@ -33,6 +36,7 @@ interface SerializationToken { /** * A context for mapping SerializationTokens to/from SerializeAsTokens. */ +@DeleteForDJVM interface SerializeAsTokenContext { val serviceHub: ServiceHub fun putSingleton(toBeTokenized: SerializeAsToken) @@ -43,6 +47,7 @@ interface SerializeAsTokenContext { * A class representing a [SerializationToken] for some object that is not serializable but can be looked up * (when deserialized) via just the class name. */ +@DeleteForDJVM class SingletonSerializationToken private constructor(private val className: String) : SerializationToken { override fun fromToken(context: SerializeAsTokenContext) = context.getSingleton(className) @@ -58,6 +63,7 @@ class SingletonSerializationToken private constructor(private val className: Str * A base class for implementing large objects / components / services that need to serialize themselves to a string token * to indicate which instance the token is a serialized form of. */ +@DeleteForDJVM abstract class SingletonSerializeAsToken : SerializeAsToken { private val token = singletonSerializationToken(javaClass) diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationWhitelist.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationWhitelist.kt index 0c2be0c16b..cc0b56e32e 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationWhitelist.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationWhitelist.kt @@ -1,10 +1,13 @@ package net.corda.core.serialization +import net.corda.core.KeepForDJVM + /** * Provide a subclass of this via the [java.util.ServiceLoader] mechanism to be able to whitelist types for * serialisation that you cannot otherwise annotate. The name of the class must appear in a text file on the * classpath under the path META-INF/services/net.corda.core.serialization.SerializationWhitelist */ +@KeepForDJVM interface SerializationWhitelist { /** * Optionally whitelist types for use in object serialization, as we lock down the types that can be serialized. diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt index a8cbfd5724..28c6ad7900 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/internal/SerializationEnvironment.kt @@ -1,5 +1,7 @@ +@file:KeepForDJVM package net.corda.core.serialization.internal +import net.corda.core.KeepForDJVM import net.corda.core.internal.InheritableThreadLocalToggleField import net.corda.core.internal.SimpleToggleField import net.corda.core.internal.ThreadLocalToggleField @@ -7,6 +9,7 @@ import net.corda.core.internal.VisibleForTesting import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationFactory +@KeepForDJVM interface SerializationEnvironment { val serializationFactory: SerializationFactory val p2pContext: SerializationContext @@ -16,6 +19,7 @@ interface SerializationEnvironment { val checkpointContext: SerializationContext } +@KeepForDJVM open class SerializationEnvironmentImpl( override val serializationFactory: SerializationFactory, override val p2pContext: SerializationContext, diff --git a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt index 14483543b2..16580cfdd4 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt @@ -1,6 +1,7 @@ package net.corda.core.transactions import net.corda.core.DoNotImplement +import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.identity.Party import net.corda.core.internal.castIfPossible @@ -11,6 +12,7 @@ import java.util.function.Predicate /** * An abstract class defining fields shared by all transaction types in the system. */ +@KeepForDJVM @DoNotImplement abstract class BaseTransaction : NamedByHash { /** The inputs of this transaction. Note that in BaseTransaction subclasses the type of this list may change! */ diff --git a/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt index 2af0347e8c..a019a31068 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.TransactionSignature @@ -22,6 +23,7 @@ import java.security.PublicKey // TODO: check transaction size is within limits /** A special transaction for upgrading the contract of a state. */ +@KeepForDJVM @CordaSerializable data class ContractUpgradeWireTransaction( /** @@ -110,6 +112,7 @@ data class ContractUpgradeWireTransaction( * is no flexibility on what parts of the transaction to reveal – the inputs and notary field are always visible and the * rest of the transaction is always hidden. Its only purpose is to hide transaction data when using a non-validating notary. */ +@KeepForDJVM @CordaSerializable data class ContractUpgradeFilteredTransaction( /** Transaction components that are exposed. */ @@ -158,6 +161,7 @@ data class ContractUpgradeFilteredTransaction( * In contrast with a regular transaction, signatures are checked against the signers specified by input states' * *participants* fields, so full resolution is needed for signature verification. */ +@KeepForDJVM data class ContractUpgradeLedgerTransaction( override val inputs: List>, override val notary: Party, diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index 1f86e50b27..bf3deadcbb 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party @@ -26,6 +27,7 @@ import java.util.function.Predicate // TODO LedgerTransaction is not supposed to be serialisable as it references attachments, etc. The verification logic // currently sends this across to out-of-process verifiers. We'll need to change that first. // DOCSTART 1 +@KeepForDJVM @CordaSerializable data class LedgerTransaction @JvmOverloads constructor( /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */ @@ -58,10 +60,14 @@ data class LedgerTransaction @JvmOverloads constructor( .asSubclass(Contract::class.java) } } + + private fun stateToContractClass(state: TransactionState): Try> { + return contractClassFor(state.contract, state.data::class.java.classLoader) + } } private val contracts: Map>> = (inputs.map { it.state } + outputs) - .map { it.contract to contractClassFor(it.contract, it.data::class.java.classLoader) }.toMap() + .map { it.contract to stateToContractClass(it) }.toMap() val inputStates: List get() = inputs.map { it.state.data } @@ -237,6 +243,7 @@ data class LedgerTransaction @JvmOverloads constructor( * be used to simplify this logic. */ // DOCSTART 3 + @KeepForDJVM data class InOutGroup(val inputs: List, val outputs: List, val groupingKey: K) // DOCEND 3 diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index b61daf9963..c484781d19 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -1,6 +1,7 @@ package net.corda.core.transactions import net.corda.core.CordaException +import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.identity.Party @@ -107,6 +108,7 @@ abstract class TraversableTransaction(open val componentGroups: List, val nonces: List, val partialMerkleTree: PartialMerkleTree) : ComponentGroup(groupIndex, components) { init { @@ -342,6 +345,7 @@ data class FilteredComponentGroup(override val groupIndex: Int, override val com * @param id transaction's id. * @param reason information about the exception. */ +@KeepForDJVM @CordaSerializable class ComponentVisibilityException(val id: SecureHash, val reason: String) : CordaException("Component visibility error for transaction with id:$id. Reason: $reason") @@ -349,5 +353,6 @@ class ComponentVisibilityException(val id: SecureHash, val reason: String) : Cor * @param id transaction's id. * @param reason information about the exception. */ +@KeepForDJVM @CordaSerializable class FilteredTransactionVerificationException(val id: SecureHash, val reason: String) : CordaException("Transaction with id:$id cannot be verified. Reason: $reason") diff --git a/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt b/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt index 973de9e942..209d312e19 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.KeepForDJVM import net.corda.core.contracts.ContractState import net.corda.core.contracts.TransactionState import net.corda.core.flows.FlowException @@ -11,6 +12,7 @@ import net.corda.core.serialization.CordaSerializable * @property states States which have contracts that do not have corresponding attachments in the attachment store. */ @CordaSerializable +@KeepForDJVM class MissingContractAttachments(val states: List>) : FlowException("Cannot find contract attachments for ${states.map { it.contract }.distinct()}. " + "See https://docs.corda.net/api-contract-constraints.html#debugging") \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt index fbee73d680..80667b5286 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt @@ -1,5 +1,7 @@ package net.corda.core.transactions +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.TransactionSignature @@ -21,6 +23,7 @@ import java.security.PublicKey * on the fly. */ @CordaSerializable +@KeepForDJVM data class NotaryChangeWireTransaction( /** * Contains all of the transaction components in serialized form. @@ -61,12 +64,14 @@ data class NotaryChangeWireTransaction( } /** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */ + @DeleteForDJVM fun resolve(services: ServicesForResolution, sigs: List): NotaryChangeLedgerTransaction { val resolvedInputs = services.loadStates(inputs.toSet()).toList() return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs) } /** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */ + @DeleteForDJVM fun resolve(services: ServiceHub, sigs: List) = resolve(services as ServicesForResolution, sigs) enum class Component { @@ -82,6 +87,7 @@ data class NotaryChangeWireTransaction( * signatures are checked against the signers specified by input states' *participants* fields, so full resolution is * needed for signature verification. */ +@KeepForDJVM data class NotaryChangeLedgerTransaction( override val inputs: List>, override val notary: Party, diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt index 94a993e328..440163f0e0 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt @@ -2,6 +2,8 @@ package net.corda.core.transactions import net.corda.core.CordaException import net.corda.core.CordaThrowable +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.identity.Party @@ -33,6 +35,7 @@ import java.util.function.Predicate * sign. */ // DOCSTART 1 +@KeepForDJVM @CordaSerializable data class SignedTransaction(val txBits: SerializedBytes, override val sigs: List @@ -131,6 +134,7 @@ data class SignedTransaction(val txBits: SerializedBytes, * @throws SignaturesMissingException if any signatures that should have been present are missing. */ @JvmOverloads + @DeleteForDJVM @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class) fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true): LedgerTransaction { // TODO: We could probably optimise the below by @@ -159,6 +163,7 @@ data class SignedTransaction(val txBits: SerializedBytes, * @throws SignaturesMissingException if any signatures that should have been present are missing. */ @JvmOverloads + @DeleteForDJVM @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class) fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) { when (coreTransaction) { @@ -169,6 +174,7 @@ data class SignedTransaction(val txBits: SerializedBytes, } /** No contract code is run when verifying notary change transactions, it is sufficient to check invariants during initialisation. */ + @DeleteForDJVM private fun verifyNotaryChangeTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) { val ntx = resolveNotaryChangeTransaction(services) if (checkSufficientSignatures) ntx.verifyRequiredSignatures() @@ -176,6 +182,7 @@ data class SignedTransaction(val txBits: SerializedBytes, } /** No contract code is run when verifying contract upgrade transactions, it is sufficient to check invariants during initialisation. */ + @DeleteForDJVM private fun verifyContractUpgradeTransaction(services: ServicesForResolution, checkSufficientSignatures: Boolean) { val ctx = resolveContractUpgradeTransaction(services) if (checkSufficientSignatures) ctx.verifyRequiredSignatures() @@ -185,6 +192,7 @@ data class SignedTransaction(val txBits: SerializedBytes, // TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised // from the attachment is trusted. This will require some partial serialisation work to not load the ContractState // objects from the TransactionState. + @DeleteForDJVM private fun verifyRegularTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) { val ltx = toLedgerTransaction(services, checkSufficientSignatures) // TODO: allow non-blocking verification. @@ -195,6 +203,7 @@ data class SignedTransaction(val txBits: SerializedBytes, * Resolves the underlying base transaction and then returns it, handling any special case transactions such as * [NotaryChangeWireTransaction]. */ + @DeleteForDJVM fun resolveBaseTransaction(servicesForResolution: ServicesForResolution): BaseTransaction { return when (coreTransaction) { is NotaryChangeWireTransaction -> resolveNotaryChangeTransaction(servicesForResolution) @@ -210,6 +219,7 @@ data class SignedTransaction(val txBits: SerializedBytes, * Resolves the underlying transaction with signatures and then returns it, handling any special case transactions * such as [NotaryChangeWireTransaction]. */ + @DeleteForDJVM fun resolveTransactionWithSignatures(services: ServicesForResolution): TransactionWithSignatures { return when (coreTransaction) { is NotaryChangeWireTransaction -> resolveNotaryChangeTransaction(services) @@ -224,6 +234,7 @@ data class SignedTransaction(val txBits: SerializedBytes, * If [transaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a * [NotaryChangeLedgerTransaction] so the signatures can be verified. */ + @DeleteForDJVM fun resolveNotaryChangeTransaction(services: ServicesForResolution): NotaryChangeLedgerTransaction { val ntx = coreTransaction as? NotaryChangeWireTransaction ?: throw IllegalStateException("Expected a ${NotaryChangeWireTransaction::class.simpleName} but found ${coreTransaction::class.simpleName}") @@ -234,12 +245,14 @@ data class SignedTransaction(val txBits: SerializedBytes, * If [transaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a * [NotaryChangeLedgerTransaction] so the signatures can be verified. */ + @DeleteForDJVM fun resolveNotaryChangeTransaction(services: ServiceHub) = resolveNotaryChangeTransaction(services as ServicesForResolution) /** * If [coreTransaction] is a [ContractUpgradeWireTransaction], loads the input states and resolves it to a * [ContractUpgradeLedgerTransaction] so the signatures can be verified. */ + @DeleteForDJVM fun resolveContractUpgradeTransaction(services: ServicesForResolution): ContractUpgradeLedgerTransaction { val ctx = coreTransaction as? ContractUpgradeWireTransaction ?: throw IllegalStateException("Expected a ${ContractUpgradeWireTransaction::class.simpleName} but found ${coreTransaction::class.simpleName}") @@ -253,6 +266,7 @@ data class SignedTransaction(val txBits: SerializedBytes, "Missing signatures for $descriptions on transaction ${id.prefixChars()} for ${missing.joinToString()}" } + @KeepForDJVM @CordaSerializable class SignaturesMissingException(val missing: Set, val descriptions: List, override val id: SecureHash) : NamedByHash, SignatureException(missingSignatureMsg(missing, descriptions, id)), CordaThrowable by CordaException(missingSignatureMsg(missing, descriptions, id)) diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt index 12808fc299..8d012e2ea9 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -1,6 +1,7 @@ package net.corda.core.transactions import co.paralleluniverse.strands.Strand +import net.corda.core.DeleteForDJVM import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.SecureHash @@ -33,6 +34,7 @@ import kotlin.collections.ArrayList * When this is set to a non-null value, an output state can be added by just passing in a [ContractState] – a * [TransactionState] with this notary specified will be generated automatically. */ +@DeleteForDJVM open class TransactionBuilder( var notary: Party? = null, var lockId: UUID = (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID(), diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt index 77ceb3840b..237abc3e35 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt @@ -1,6 +1,7 @@ package net.corda.core.transactions import net.corda.core.DoNotImplement +import net.corda.core.KeepForDJVM import net.corda.core.contracts.NamedByHash import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.isFulfilledBy @@ -12,6 +13,7 @@ import java.security.SignatureException import java.util.* /** An interface for transactions containing signatures, with logic for signature verification. */ +@KeepForDJVM @DoNotImplement interface TransactionWithSignatures : NamedByHash { /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index c9c56f2a17..6f4a63b898 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -1,6 +1,8 @@ package net.corda.core.transactions import net.corda.core.CordaInternal +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.contracts.ComponentGroupEnum.* import net.corda.core.crypto.* @@ -41,9 +43,13 @@ import java.util.function.Predicate *

    */ @CordaSerializable -class WireTransaction(componentGroups: List, val privacySalt: PrivacySalt = PrivacySalt()) : TraversableTransaction(componentGroups) { +@KeepForDJVM +class WireTransaction(componentGroups: List, val privacySalt: PrivacySalt) : TraversableTransaction(componentGroups) { + @DeleteForDJVM + constructor(componentGroups: List) : this(componentGroups, PrivacySalt()) @Deprecated("Required only in some unit-tests and for backwards compatibility purposes.", ReplaceWith("WireTransaction(val componentGroups: List, override val privacySalt: PrivacySalt)"), DeprecationLevel.WARNING) + @DeleteForDJVM constructor(inputs: List, attachments: List, outputs: List>, @@ -85,6 +91,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr * @throws TransactionResolutionException if an input points to a transaction not found in storage. */ @Throws(AttachmentResolutionException::class, TransactionResolutionException::class) + @DeleteForDJVM fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction { return toLedgerTransactionInternal( resolveIdentity = { services.identityService.partyFromKey(it) }, @@ -254,6 +261,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr } } + @DeleteForDJVM override fun toString(): String { val buf = StringBuilder() buf.appendln("Transaction:") diff --git a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt index 5eb9e54520..1d5ceb3f73 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt @@ -1,7 +1,9 @@ @file:JvmName("ByteArrays") +@file:KeepForDJVM package net.corda.core.utilities +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializable import java.io.ByteArrayInputStream import java.io.OutputStream @@ -19,6 +21,7 @@ import javax.xml.bind.DatatypeConverter * @property size The number of bytes this sequence represents. */ @CordaSerializable +@KeepForDJVM sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val size: Int) : Comparable { /** * The underlying bytes. Some implementations may choose to make a copy of the underlying [ByteArray] for @@ -142,6 +145,7 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si * In an ideal JVM this would be a value type and be completely overhead free. Project Valhalla is adding such * functionality to Java, but it won't arrive for a few years yet! */ +@KeepForDJVM open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes, 0, bytes.size) { companion object { /** @@ -187,6 +191,7 @@ fun String.parseAsHex(): ByteArray = DatatypeConverter.parseHexBinary(this) /** * Class is public for serialization purposes */ +@KeepForDJVM class OpaqueBytesSubSequence(override val bytes: ByteArray, offset: Int, size: Int) : ByteSequence(bytes, offset, size) { init { require(offset >= 0 && offset < bytes.size) diff --git a/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt index 902bc8e9bd..c2cf1c151d 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt @@ -1,7 +1,9 @@ @file:JvmName("EncodingUtils") +@file:KeepForDJVM package net.corda.core.utilities +import net.corda.core.KeepForDJVM import net.corda.core.crypto.Base58 import net.corda.core.crypto.Crypto import net.corda.core.internal.hash diff --git a/core/src/main/kotlin/net/corda/core/utilities/Id.kt b/core/src/main/kotlin/net/corda/core/utilities/Id.kt index 5020a08700..a68bfb2d49 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/Id.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/Id.kt @@ -1,5 +1,6 @@ package net.corda.core.utilities +import net.corda.core.DeleteForDJVM import java.time.Instant import java.time.Instant.now @@ -15,6 +16,7 @@ open class Id(val value: VALUE, val entityType: String?, val ti /** * Creates an id using [Instant.now] as timestamp. */ + @DeleteForDJVM @JvmStatic fun newInstance(value: V, entityType: String? = null, timestamp: Instant = now()) = Id(value, entityType, timestamp) } diff --git a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt index b53bdc9f34..240edbfd1e 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -1,5 +1,8 @@ +@file:KeepForDJVM package net.corda.core.utilities +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.internal.concurrent.get import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.CordaSerializable @@ -125,6 +128,7 @@ private class TransientProperty internal constructor(private val initiali fun Collection.toNonEmptySet(): NonEmptySet = NonEmptySet.copyOf(this) /** Same as [Future.get] except that the [ExecutionException] is unwrapped. */ +@DeleteForDJVM fun Future.getOrThrow(timeout: Duration? = null): V = try { get(timeout) } catch (e: ExecutionException) { diff --git a/core/src/main/kotlin/net/corda/core/utilities/NonEmptySet.kt b/core/src/main/kotlin/net/corda/core/utilities/NonEmptySet.kt index ab5f0b86d7..06fb7834d6 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/NonEmptySet.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/NonEmptySet.kt @@ -1,5 +1,6 @@ package net.corda.core.utilities +import net.corda.core.KeepForDJVM import java.util.* import java.util.function.Consumer import java.util.stream.Stream @@ -7,6 +8,7 @@ import java.util.stream.Stream /** * An immutable ordered non-empty set. */ +@KeepForDJVM class NonEmptySet private constructor(private val elements: Set) : Set by elements { companion object { /** diff --git a/core/src/main/kotlin/net/corda/core/utilities/Try.kt b/core/src/main/kotlin/net/corda/core/utilities/Try.kt index cfe471c1b1..7d2d678e5f 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/Try.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/Try.kt @@ -1,5 +1,6 @@ package net.corda.core.utilities +import net.corda.core.KeepForDJVM import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.Try.Failure @@ -59,6 +60,7 @@ sealed class Try { is Failure -> uncheckedCast(this) } + @KeepForDJVM data class Success(val value: A) : Try
    () { override val isSuccess: Boolean get() = true override val isFailure: Boolean get() = false @@ -66,6 +68,7 @@ sealed class Try { override fun toString(): String = "Success($value)" } + @KeepForDJVM data class Failure(val exception: Throwable) : Try() { override val isSuccess: Boolean get() = false override val isFailure: Boolean get() = true diff --git a/core/src/main/kotlin/net/corda/core/utilities/UntrustworthyData.kt b/core/src/main/kotlin/net/corda/core/utilities/UntrustworthyData.kt index bb693e99c0..94fcb2a4cf 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/UntrustworthyData.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/UntrustworthyData.kt @@ -1,10 +1,9 @@ +@file:KeepForDJVM package net.corda.core.utilities import co.paralleluniverse.fibers.Suspendable +import net.corda.core.KeepForDJVM import net.corda.core.flows.FlowException -import net.corda.core.internal.castIfPossible -import net.corda.core.serialization.SerializationDefaults -import net.corda.core.serialization.SerializedBytes import java.io.Serializable /** @@ -18,11 +17,13 @@ import java.io.Serializable * - Are any objects *reachable* from this object mismatched or not what you expected? * - Is it suspiciously large or small? */ +@KeepForDJVM class UntrustworthyData(@PublishedApi internal val fromUntrustedWorld: T) { @Suspendable @Throws(FlowException::class) fun unwrap(validator: Validator) = validator.validate(fromUntrustedWorld) + @KeepForDJVM @FunctionalInterface interface Validator : Serializable { @Suspendable diff --git a/core/src/main/kotlin/net/corda/core/utilities/UuidGenerator.kt b/core/src/main/kotlin/net/corda/core/utilities/UuidGenerator.kt index e851dae583..72beb0d624 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/UuidGenerator.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/UuidGenerator.kt @@ -1,7 +1,9 @@ package net.corda.core.utilities +import net.corda.core.DeleteForDJVM import java.util.* +@DeleteForDJVM class UuidGenerator { companion object { diff --git a/create-jdk8u/.gitignore b/create-jdk8u/.gitignore new file mode 100644 index 0000000000..c5a9347ba7 --- /dev/null +++ b/create-jdk8u/.gitignore @@ -0,0 +1,2 @@ +jdk8u/ +libs/ diff --git a/create-jdk8u/Makefile b/create-jdk8u/Makefile new file mode 100644 index 0000000000..424d073e41 --- /dev/null +++ b/create-jdk8u/Makefile @@ -0,0 +1,25 @@ +.DEFAULT_GOAL=all + +jdk8u: + git clone -b deterministic-jvm8 --single-branch https://github.com/corda/openjdk $@ + +jdk8u/common/autoconf/configure: jdk8u + +jdk8u/build/%/spec.gmk: jdk8u/common/autoconf/configure + cd jdk8u && $(SHELL) configure + +.PHONY: jdk-image clean all +jdk-image: jdk8u/build/linux-x86_64-normal-server-release/spec.gmk + $(MAKE) -C jdk8u images docs + +all: libs/rt.jar libs/jce.jar libs/jsse.jar + +clean: + $(MAKE) -C jdk8u clean + +libs: + mkdir $@ + +libs/rt.jar libs/jce.jar libs/jsse.jar: libs jdk-image + cp -f jdk8u/build/*/images/j2re-image/lib/$(@F) $@ + diff --git a/create-jdk8u/build.gradle b/create-jdk8u/build.gradle new file mode 100644 index 0000000000..30e46cd17f --- /dev/null +++ b/create-jdk8u/build.gradle @@ -0,0 +1,159 @@ +buildscript { + Properties constants = new Properties() + file("../constants.properties").withInputStream { constants.load(it) } + + ext { + artifactory_contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory' + artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') + proguard_version = constants.getProperty("proguardVersion") + } + + repositories { + mavenLocal() + mavenCentral() + jcenter() + maven { + url "$artifactory_contextUrl/corda-dev" + } + } + dependencies { + classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version" + classpath "net.sf.proguard:proguard-gradle:$proguard_version" + } +} + +plugins { + id 'base' + id 'maven-publish' +} +apply plugin: 'com.jfrog.artifactory' + +/* + * This is a nested and independent Gradle project, + * and so has its own group and version. + * + * NOTE: The deterministic APIs are Open Source. + */ +group 'net.corda' +version '1.0-SNAPSHOT' + +task cleanJdk(type: Exec) { + commandLine 'make', 'clean' +} + +task makeJdk(type: Exec) { + // See: https://github.com/corda/openjdk/tree/deterministic-jvm8 + commandLine 'make' +} + +task runtimeJar(type: Jar, dependsOn: makeJdk) { + baseName 'deterministic-rt' + + from(zipTree("libs/rt.jar")) + from(zipTree("libs/jce.jar")) + from(zipTree("libs/jsse.jar")) + + reproducibleFileOrder = true + includeEmptyDirs = false +} + +import proguard.gradle.ProGuardTask +task validate(type: ProGuardTask) { + injars runtimeJar + + dontwarn 'java.lang.invoke.**' + dontwarn 'javax.lang.model.**' + dontwarn 'jdk.Exported' + + keepattributes '*' + dontpreverify + dontobfuscate + dontoptimize + verbose + + keep 'class *' +} +runtimeJar.finalizedBy validate + +task apiJar(type: Jar, dependsOn: runtimeJar) { + baseName 'deterministic-rt' + classifier 'api' + + from(zipTree(runtimeJar.outputs.files.singleFile)) { + include 'java/' + include 'javax/' + exclude 'java/awt/' + exclude 'java/beans/Weak*.class' + exclude 'java/lang/invoke/' + exclude 'java/lang/*Thread*.class' + exclude 'java/lang/Shutdown*.class' + exclude 'java/lang/ref/' + exclude 'java/lang/reflect/InvocationHandler.class' + exclude 'java/lang/reflect/Proxy*.class' + exclude 'java/lang/reflect/Weak*.class' + exclude 'java/io/File.class' + exclude 'java/io/File$*.class' + exclude 'java/io/*FileSystem.class' + exclude 'java/io/Filename*.class' + exclude 'java/io/FileDescriptor*.class' + exclude 'java/io/FileFilter*.class' + exclude 'java/io/FilePermission*.class' + exclude 'java/io/FileReader*.class' + exclude 'java/io/FileSystem*.class' + exclude 'java/io/File*Stream*.class' + exclude 'java/net/*Content*.class' + exclude 'java/net/Host*.class' + exclude 'java/net/Inet*.class' + exclude 'java/nio/file/Path.class' + exclude 'java/nio/file/attribute/' + exclude 'java/util/SplittableRandom*.class' + exclude 'java/util/Random.class' + exclude 'java/util/Random$*.class' + exclude 'java/util/WeakHashMap*.class' + exclude 'java/util/concurrent/*.class' + exclude 'java/util/concurrent/locks/' + exclude 'javax/activation/' + } + + preserveFileTimestamps = false + reproducibleFileOrder = true + includeEmptyDirs = false +} + +defaultTasks "build" +assemble.dependsOn runtimeJar +assemble.dependsOn apiJar +clean.dependsOn cleanJdk + +artifacts { + archives runtimeJar + archives apiJar +} + +artifactory { + contextUrl = artifactory_contextUrl + publish { + repository { + repoKey = 'corda-dev' + username = System.getenv('CORDA_ARTIFACTORY_USERNAME') + password = System.getenv('CORDA_ARTIFACTORY_PASSWORD') + maven = true + } + + defaults { + publications('mavenJava') + } + } +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId 'deterministic-rt' + artifact runtimeJar + artifact apiJar + } + } +} + +task install(dependsOn: publishToMavenLocal) diff --git a/create-jdk8u/settings.gradle b/create-jdk8u/settings.gradle new file mode 100644 index 0000000000..c6e8eefe7d --- /dev/null +++ b/create-jdk8u/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'deterministic-rt' diff --git a/docs/source/deterministic-modules.rst b/docs/source/deterministic-modules.rst index 435b75c7fc..35a579d3da 100644 --- a/docs/source/deterministic-modules.rst +++ b/docs/source/deterministic-modules.rst @@ -30,7 +30,7 @@ JDK 8 Corda Modules ``core-deterministic`` and ``serialization-deterministic`` are generated from Corda's ``core`` and ``serialization`` modules respectively using both `ProGuard `_ and Corda's ``JarFilter`` Gradle - plugin. Corda developers configure these tools by applying Corda's ``@Deterministic`` and ``@NonDeterministic`` + plugin. Corda developers configure these tools by applying Corda's ``@KeepForDJVM`` and ``@DeleteForDJVM`` annotations to elements of ``core`` and ``serialization`` as described `here `_. The build generates each of Corda's deterministic JARs in six steps: @@ -42,12 +42,12 @@ The build generates each of Corda's deterministic JARs in six steps: .. sourcecode:: groovy - keep '@interface net.corda.core.Deterministic { *; }' + keep '@interface net.corda.core.KeepForDJVM { *; }' .. ProGuard works by calculating how much code is reachable from given "entry points", and in our case these entry - points are the ``@Deterministic`` classes. The unreachable classes are then discarded by ProGuard's ``shrink`` + points are the ``@KeepForDJVM`` classes. The unreachable classes are then discarded by ProGuard's ``shrink`` option. #. The remaining classes may still contain non-deterministic code. However, there is no way of writing a ProGuard rule explicitly to discard anything. Consider the following class: @@ -55,10 +55,10 @@ The build generates each of Corda's deterministic JARs in six steps: .. sourcecode:: kotlin @CordaSerializable - @Deterministic + @KeepForDJVM data class UniqueIdentifier(val externalId: String?, val id: UUID) : Comparable { - @NonDeterministic constructor(externalId: String?) : this(externalId, UUID.randomUUID()) - @NonDeterministic constructor() : this(null) + @DeleteForDJVM constructor(externalId: String?) : this(externalId, UUID.randomUUID()) + @DeleteForDJVM constructor() : this(null) ... } @@ -67,9 +67,9 @@ The build generates each of Corda's deterministic JARs in six steps: While CorDapps will definitely need to handle ``UniqueIdentifier`` objects, both of the secondary constructors generate a new random ``UUID`` and so are non-deterministic. Hence the next "determinising" step is to pass the classes to the ``JarFilter`` tool, which strips out all of the elements which have been annotated as - ``@NonDeterministic`` and stubs out any functions annotated with ``@NonDeterministicStub``. (Stub functions that + ``@DeleteForDJVM`` and stubs out any functions annotated with ``@StubOutForDJVM``. (Stub functions that return a value will throw ``UnsupportedOperationException``, whereas ``void`` or ``Unit`` stubs will do nothing.) - #. After the ``@NonDeterministic`` elements have been filtered out, the classes are rescanned using ProGuard to remove + #. After the ``@DeleteForDJVM`` elements have been filtered out, the classes are rescanned using ProGuard to remove any more code that has now become unreachable. #. The remaining classes define our deterministic subset. However, the ``@kotlin.Metadata`` annotations on the compiled Kotlin classes still contain references to all of the functions and properties that ProGuard has deleted. Therefore @@ -77,26 +77,10 @@ The build generates each of Corda's deterministic JARs in six steps: deleted functions and properties are still present. #. Finally, we use ProGuard again to validate our JAR against the deterministic ``rt.jar``: - .. sourcecode:: groovy - - task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) { - injars metafix - - libraryjars "$deterministic_jdk_home/jre/lib/rt.jar" - - configurations.runtimeLibraries.forEach { - libraryjars it.path, filter: '!META-INF/versions/**' - } - - keepattributes '*' - dontpreverify - dontobfuscate - dontoptimize - verbose - - keep 'class *' - } - + .. literalinclude:: ../../core-deterministic/build.gradle + :language: groovy + :start-after: DOCSTART 01 + :end-before: DOCEND 01 .. This step will fail if ProGuard spots any Java API references that still cannot be satisfied by the deterministic @@ -123,7 +107,7 @@ The ``testing`` module also has two sub-modules: .. _deterministic_annotations: -Applying @Deterministic and @NonDeterministic annotations +Applying @KeepForDJVM and @DeleteForDJVM annotations --------------------------------------------------------- Corda developers need to understand how to annotate classes in the ``core`` and ``serialization`` modules correctly @@ -146,14 +130,12 @@ For more information about how ``JarFilter`` is processing the byte-code inside use Gradle's ``--info`` or ``--debug`` command-line options. Deterministic Classes - Classes that *must* be included in the deterministic JAR should be annotated as ``@Deterministic``. + Classes that *must* be included in the deterministic JAR should be annotated as ``@KeepForDJVM``. - .. sourcecode:: kotlin - - @Target(FILE, CLASS) - @Retention(BINARY) - @CordaInternal - annotation class Deterministic + .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/KeepForDJVM.kt + :language: kotlin + :start-after: DOCSTART 01 + :end-before: DOCEND 01 .. To preserve any Kotlin functions, properties or type aliases that have been declared outside of a ``class``, @@ -170,25 +152,12 @@ Deterministic Classes .. Non-Deterministic Elements - Elements that *must* be deleted from classes in the deterministic JAR should be annotated as ``@NonDeterministic``. - - .. sourcecode:: kotlin - - @Target( - FILE, - CLASS, - CONSTRUCTOR, - FUNCTION, - PROPERTY_GETTER, - PROPERTY_SETTER, - PROPERTY, - FIELD, - TYPEALIAS - ) - @Retention(BINARY) - @CordaInternal - annotation class NonDeterministic + Elements that *must* be deleted from classes in the deterministic JAR should be annotated as ``@DeleteForDJVM``. + .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/DeleteForDJVM.kt + :language: kotlin + :start-after: DOCSTART 01 + :end-before: DOCEND 01 .. You must also ensure that a deterministic class's primary constructor does not reference any classes that are @@ -206,32 +175,24 @@ Non-Deterministic Elements package net.corda.core - @NonDeterministic + @DeleteForDJVM val map: MutableMap = ConcurrentHashMap() .. In this case, ``JarFilter`` would delete the ``map`` property but the ```` block would still create an instance of ``ConcurrentHashMap``. The solution here is to refactor the property into its own file and then - annotate the file itself as ``@NonDeterministic`` instead. + annotate the file itself as ``@DeleteForDJVM`` instead. Non-Deterministic Function Stubs Sometimes it is impossible to delete a function entirely. Or a function may have some non-deterministic code - embedded inside it that cannot be removed. For these rare cases, there is the ``@NonDeterministicStub`` + embedded inside it that cannot be removed. For these rare cases, there is the ``@StubOutForDJVM`` annotation: - .. sourcecode:: kotlin - - @Target( - CONSTRUCTOR, - FUNCTION, - PROPERTY_GETTER, - PROPERTY_SETTER - ) - @Retention(BINARY) - @CordaInternal - annotation class NonDeterministicStub - + .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/StubOutForDJVM.kt + :language: kotlin + :start-after: DOCSTART 01 + :end-before: DOCEND 01 .. This annotation instructs ``JarFilter`` to replace the function's body with either an empty body (for functions @@ -244,10 +205,9 @@ Non-Deterministic Function Stubs otherOperations() } - @NonDeterministicStub + @StubOutForDJVM private fun nonDeterministicOperations() { // etc } .. - diff --git a/jdk8u-deterministic/build.gradle b/jdk8u-deterministic/build.gradle new file mode 100644 index 0000000000..298953c30f --- /dev/null +++ b/jdk8u-deterministic/build.gradle @@ -0,0 +1,30 @@ +repositories { + maven { + url "$artifactory_contextUrl/corda-releases" + } + maven { + url "$artifactory_contextUrl/corda-dev" + } +} + +ext { + jdk_home = "$buildDir/jdk" +} + +configurations { + jdk +} + +dependencies { + jdk "net.corda:deterministic-rt:$deterministic_rt_version:api" +} + +task copyJdk(type: Copy) { + from(configurations.jdk.asPath) { + rename 'deterministic-rt-(.*).jar', 'rt.jar' + } + into "$jdk_home/jre/lib" +} + +assemble.dependsOn copyJdk +jar.enabled = false diff --git a/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt index 110bf1ebb9..4abb273b48 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt @@ -1,13 +1,3 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable diff --git a/node/src/integration-test/kotlin/net/corda/testMessage/ScheduledState.kt b/node/src/integration-test/kotlin/net/corda/testMessage/ScheduledState.kt index cc5f14e8ee..189d0fc9f4 100644 --- a/node/src/integration-test/kotlin/net/corda/testMessage/ScheduledState.kt +++ b/node/src/integration-test/kotlin/net/corda/testMessage/ScheduledState.kt @@ -1,13 +1,3 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - package net.corda.testMessage import co.paralleluniverse.fibers.Suspendable diff --git a/node/src/main/kotlin/net/corda/node/serialization/kryo/Kryo.kt b/node/src/main/kotlin/net/corda/node/serialization/kryo/Kryo.kt index ec3046c682..e7b94b9635 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/kryo/Kryo.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/kryo/Kryo.kt @@ -7,6 +7,7 @@ import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer import com.esotericsoftware.kryo.serializers.FieldSerializer import com.esotericsoftware.kryo.util.MapReferenceResolver +import net.corda.core.DeleteForDJVM import net.corda.core.contracts.PrivacySalt import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash @@ -460,6 +461,7 @@ fun Kryo.serializationContext(): SerializeAsTokenContext? = context.get(serializ * unmodifiable collection to [java.lang.Throwable.suppressedExceptions] which will fail some sentinel identity checks * e.g. in [java.lang.Throwable.addSuppressed] */ +@DeleteForDJVM @ThreadSafe class ThrowableSerializer(kryo: Kryo, type: Class) : Serializer(false, true) { diff --git a/serialization-deterministic/build.gradle b/serialization-deterministic/build.gradle new file mode 100644 index 0000000000..ccb1c32558 --- /dev/null +++ b/serialization-deterministic/build.gradle @@ -0,0 +1,186 @@ +description 'Corda serialization (deterministic)' + +apply plugin: 'kotlin' +apply plugin: 'com.jfrog.artifactory' +apply plugin: 'net.corda.plugins.publish-utils' + +evaluationDependsOn(':jdk8u-deterministic') +evaluationDependsOn(":serialization") + +def javaHome = System.getProperty('java.home') +def jarBaseName = "corda-${project.name}".toString() +def jdkTask = project(':jdk8u-deterministic').assemble +def deterministic_jdk_home = project(':jdk8u-deterministic').jdk_home + +configurations { + runtimeLibraries + runtimeArtifacts.extendsFrom runtimeLibraries +} + +dependencies { + compileOnly project(':serialization') + compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8" + + // Configure these by hand. It should be a minimal subset of dependencies, + // and without any obviously non-deterministic ones such as Hibernate. + runtimeLibraries project(path: ':core-deterministic', configuration: 'runtimeArtifacts') + runtimeLibraries "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" + runtimeLibraries "org.apache.qpid:proton-j:$protonj_version" + runtimeLibraries "org.iq80.snappy:snappy:$snappy_version" +} + +tasks.withType(AbstractCompile) { + dependsOn jdkTask +} + +tasks.withType(JavaCompile) { + options.compilerArgs << "-bootclasspath" << "$deterministic_jdk_home/jre/lib/rt.jar".toString() +} + +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { + kotlinOptions.jdkHome = deterministic_jdk_home +} + +jar { + baseName 'DOES-NOT-EXIST' + // Don't build a jar here because it would be the wrong one. + // The jar we really want will be built by the metafix task. + enabled = false +} + +def serializationJarTask = tasks.getByPath(':serialization:jar') +def originalJar = serializationJarTask.outputs.files.singleFile + +task patchSerialization(type: Zip, dependsOn: serializationJarTask) { + destinationDir file("$buildDir/source-libs") + metadataCharset 'UTF-8' + classifier 'transient' + extension 'jar' + + from(compileKotlin) + from(zipTree(originalJar)) { + exclude 'net/corda/serialization/internal/AttachmentsClassLoaderBuilder*' + exclude 'net/corda/serialization/internal/ByteBufferStreams*' + exclude 'net/corda/serialization/internal/DefaultWhitelist*' + exclude 'net/corda/serialization/internal/amqp/AMQPSerializerFactories*' + exclude 'net/corda/serialization/internal/amqp/AMQPStreams*' + } + + reproducibleFileOrder = true + includeEmptyDirs = false +} + +import proguard.gradle.ProGuardTask +task predeterminise(type: ProGuardTask, dependsOn: project(':core-deterministic').assemble) { + injars patchSerialization + outjars "$buildDir/proguard/pre-deterministic-${project.version}.jar" + + libraryjars "$javaHome/lib/rt.jar" + libraryjars "$javaHome/lib/jce.jar" + libraryjars "$javaHome/lib/ext/sunec.jar" + configurations.compileOnly.forEach { + if (originalJar.path != it.path) { + libraryjars it.path, filter: '!META-INF/versions/**' + } + } + + keepattributes '*' + keepdirectories + dontpreverify + dontobfuscate + dontoptimize + printseeds + verbose + + keep '@net.corda.core.KeepForDJVM class * { *; }', includedescriptorclasses:true + keepclassmembers 'class net.corda.serialization.** { public synthetic ; }' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars predeterminise + annotations { + forDelete = [ + "net.corda.core.DeleteForDJVM" + ] + forStub = [ + "net.corda.core.StubOutForDJVM" + ] + forRemove = [ + "co.paralleluniverse.fibers.Suspendable" + ] + } +} + +task determinise(type: ProGuardTask) { + injars jarFilter + outjars "$buildDir/proguard/$jarBaseName-${project.version}.jar" + + libraryjars "$javaHome/lib/rt.jar" + libraryjars "$javaHome/lib/jce.jar" + configurations.runtimeLibraries.forEach { + libraryjars it.path, filter: '!META-INF/versions/**' + } + + // Analyse the JAR for dead code, and remove (some of) it. + optimizations 'code/removal/simple,code/removal/advanced' + printconfiguration + + keepattributes '*' + keepdirectories + dontobfuscate + printseeds + verbose + + keep '@net.corda.core.KeepForDJVM class * { *; }', includedescriptorclasses:true + keepclassmembers 'class net.corda.serialization.** { public synthetic ; }' +} + +import net.corda.gradle.jarfilter.MetaFixerTask +task metafix(type: MetaFixerTask) { + outputDir file("$buildDir/libs") + jars determinise + suffix "" + + // Strip timestamps from the JAR to make it reproducible. + preserveTimestamps = false +} + +task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) { + injars metafix + + libraryjars "$deterministic_jdk_home/jre/lib/rt.jar" + + configurations.runtimeLibraries.forEach { + libraryjars it.path, filter: '!META-INF/versions/**' + } + + keepattributes '*' + dontpreverify + dontobfuscate + dontoptimize + verbose + + keep 'class *' +} + +defaultTasks "determinise" +determinise.finalizedBy metafix +metafix.finalizedBy checkDeterminism +assemble.dependsOn checkDeterminism + +def deterministicJar = metafix.outputs.files.singleFile +artifacts { + runtimeArtifacts file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix + publish file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix +} + +publish { + dependenciesFrom configurations.runtimeArtifacts + publishSources = false + publishJavadoc = false + name jarBaseName +} + +// Must be after publish {} so that the previous install task exists for overwriting. +task install(overwrite: true, dependsOn: 'publishToMavenLocal') diff --git a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderBuilder.kt b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderBuilder.kt new file mode 100644 index 0000000000..01386d8823 --- /dev/null +++ b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderBuilder.kt @@ -0,0 +1,13 @@ +package net.corda.serialization.internal + +import net.corda.core.crypto.SecureHash +import java.lang.ClassLoader + +/** + * Drop-in replacement for [AttachmentsClassLoaderBuilder] in the serialization module. + * This version is not strongly-coupled to [net.corda.core.node.ServiceHub]. + */ +@Suppress("UNUSED", "UNUSED_PARAMETER") +internal class AttachmentsClassLoaderBuilder(private val properties: Map, private val deserializationClassLoader: ClassLoader) { + fun build(attachmentHashes: List): AttachmentsClassLoader? = null +} \ No newline at end of file diff --git a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt new file mode 100644 index 0000000000..4a93bcb5f8 --- /dev/null +++ b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt @@ -0,0 +1,15 @@ +@file:JvmName("ByteBufferStreams") +package net.corda.serialization.internal + +const val DEFAULT_BYTEBUFFER_SIZE = 64 * 1024 + +/** + * Drop-in replacement for [byteArrayOutput] in the serialization module. + * This version does not use a [net.corda.core.internal.LazyPool]. + */ +internal fun byteArrayOutput(task: (ByteBufferOutputStream) -> T): ByteArray { + return ByteBufferOutputStream(DEFAULT_BYTEBUFFER_SIZE).let { underlying -> + task(underlying) + underlying.toByteArray() // Must happen after close, to allow ZIP footer to be written for example. + } +} diff --git a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/DefaultWhitelist.kt b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/DefaultWhitelist.kt new file mode 100644 index 0000000000..b1435f6c2f --- /dev/null +++ b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/DefaultWhitelist.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.internal + +import net.corda.core.serialization.SerializationWhitelist + +/** + * The DJVM does not need whitelisting, by definition. + */ +object DefaultWhitelist : SerializationWhitelist { + override val whitelist: List> get() = emptyList() +} diff --git a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt new file mode 100644 index 0000000000..8e1d0465c1 --- /dev/null +++ b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt @@ -0,0 +1,34 @@ +@file:JvmName("AMQPSerializerFactories") +package net.corda.serialization.internal.amqp + +import net.corda.core.serialization.ClassWhitelist +import net.corda.core.serialization.SerializationContext +import net.corda.serialization.internal.carpenter.ClassCarpenter +import net.corda.serialization.internal.carpenter.Schema + +/** + * Creates a [SerializerFactoryFactory] suitable for the DJVM, + * i.e. one without a [ClassCarpenter] implementation. + */ +@Suppress("UNUSED") +fun createSerializerFactoryFactory(): SerializerFactoryFactory = DeterministicSerializerFactoryFactory() + +private class DeterministicSerializerFactoryFactory : SerializerFactoryFactory { + override fun make(context: SerializationContext) = + SerializerFactory( + whitelist = context.whitelist, + classCarpenter = DummyClassCarpenter(context.whitelist, context.deserializationClassLoader), + serializersByType = mutableMapOf(), + serializersByDescriptor = mutableMapOf(), + customSerializers = ArrayList(), + transformsCache = mutableMapOf() + ) +} + +private class DummyClassCarpenter( + override val whitelist: ClassWhitelist, + override val classloader: ClassLoader +) : ClassCarpenter { + override fun build(schema: Schema): Class<*> + = throw UnsupportedOperationException("ClassCarpentry not supported") +} \ No newline at end of file diff --git a/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPStreams.kt b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPStreams.kt new file mode 100644 index 0000000000..1f4d9af591 --- /dev/null +++ b/serialization-deterministic/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPStreams.kt @@ -0,0 +1,40 @@ +@file:JvmName("AMQPStreams") +package net.corda.serialization.internal.amqp + +import net.corda.serialization.internal.ByteBufferInputStream +import net.corda.serialization.internal.ByteBufferOutputStream +import net.corda.serialization.internal.DEFAULT_BYTEBUFFER_SIZE +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * Drop-in replacement for [InputStream.asByteBuffer] in the serialization module. + * This version does not use a [net.corda.core.internal.LazyPool]. + */ +fun InputStream.asByteBuffer(): ByteBuffer { + return if (this is ByteBufferInputStream) { + byteBuffer // BBIS has no other state, so this is perfectly safe. + } else { + ByteBuffer.wrap(ByteBufferOutputStream(DEFAULT_BYTEBUFFER_SIZE).let { + copyTo(it) + it.toByteArray() + }) + } +} + +/** + * Drop-in replacement for [OutputStream.alsoAsByteBuffer] in the serialization module. + * This version does not use a [net.corda.core.internal.LazyPool]. + */ +fun OutputStream.alsoAsByteBuffer(remaining: Int, task: (ByteBuffer) -> T): T { + return if (this is ByteBufferOutputStream) { + alsoAsByteBuffer(remaining, task) + } else { + ByteBufferOutputStream(DEFAULT_BYTEBUFFER_SIZE).let { + val result = it.alsoAsByteBuffer(remaining, task) + it.copyTo(this) + result + } + } +} diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/AllButBlacklisted.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/AllButBlacklisted.kt index 36516d7966..8493821fb4 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/AllButBlacklisted.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/AllButBlacklisted.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal +import net.corda.core.DeleteForDJVM import net.corda.core.serialization.ClassWhitelist import sun.misc.Unsafe import sun.security.util.Password @@ -34,6 +35,7 @@ import kotlin.collections.LinkedHashSet * in the blacklist - it will still be serialized as specified by custom serializer. * For more details, see [net.corda.serialization.internal.CordaClassResolver.getRegistration] */ +@DeleteForDJVM object AllButBlacklisted : ClassWhitelist { private val blacklistedClasses = hashSetOf( diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoader.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoader.kt index 9a7a749e32..79de2f342b 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoader.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoader.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal +import net.corda.core.KeepForDJVM import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractAttachment import net.corda.core.crypto.SecureHash @@ -22,6 +23,7 @@ import java.util.* * AttachmentsClassLoader is somewhat expensive, as every attachment is scanned to ensure that there are no overlapping * file paths. */ +@KeepForDJVM class AttachmentsClassLoader(attachments: List, parent: ClassLoader = ClassLoader.getSystemClassLoader()) : SecureClassLoader(parent) { private val pathsToAttachments = HashMap() private val idsToAttachments = HashMap() diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt index a37ced49e2..144a0bb047 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt @@ -1,7 +1,9 @@ @file:JvmName("ByteBufferStreams") - +@file:DeleteForDJVM package net.corda.serialization.internal +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.internal.LazyPool import java.io.ByteArrayOutputStream import java.io.IOException @@ -22,6 +24,7 @@ fun byteArrayOutput(task: (ByteBufferOutputStream) -> T): ByteArray { } } +@KeepForDJVM class ByteBufferInputStream(val byteBuffer: ByteBuffer) : InputStream() { @Throws(IOException::class) override fun read(): Int { @@ -43,6 +46,7 @@ class ByteBufferInputStream(val byteBuffer: ByteBuffer) : InputStream() { } } +@KeepForDJVM class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) { companion object { private val ensureCapacity = ByteArrayOutputStream::class.java.getDeclaredMethod("ensureCapacity", Int::class.java).apply { diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/ClassWhitelists.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/ClassWhitelists.kt index 5d647d4f17..a99eba253e 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/ClassWhitelists.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/ClassWhitelists.kt @@ -1,16 +1,20 @@ package net.corda.serialization.internal +import net.corda.core.KeepForDJVM import net.corda.core.serialization.ClassWhitelist import java.util.* +@KeepForDJVM interface MutableClassWhitelist : ClassWhitelist { fun add(entry: Class<*>) } +@KeepForDJVM object AllWhitelist : ClassWhitelist { override fun hasListed(type: Class<*>): Boolean = true } +@KeepForDJVM class BuiltInExceptionsWhitelist : ClassWhitelist { companion object { private val packageName = "^(?:java|kotlin)(?:[.]|$)".toRegex() @@ -40,9 +44,11 @@ sealed class AbstractMutableClassWhitelist(private val whitelist: MutableSet = Collections.synchronizedSet(mutableSetOf()) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/ClientContexts.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/ClientContexts.kt index e8dc6b26af..5df12028a9 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/ClientContexts.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/ClientContexts.kt @@ -1,7 +1,8 @@ +@file:DeleteForDJVM @file:JvmName("ClientContexts") - package net.corda.serialization.internal +import net.corda.core.DeleteForDJVM import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults import net.corda.serialization.internal.amqp.amqpMagic diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/GeneratedAttachment.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/GeneratedAttachment.kt index 2cf38ce721..620544181f 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/GeneratedAttachment.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/GeneratedAttachment.kt @@ -1,8 +1,10 @@ package net.corda.serialization.internal +import net.corda.core.KeepForDJVM import net.corda.core.crypto.sha256 import net.corda.core.internal.AbstractAttachment +@KeepForDJVM class GeneratedAttachment(val bytes: ByteArray) : AbstractAttachment({ bytes }) { override val id = bytes.sha256() } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/OrdinalIO.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/OrdinalIO.kt index a1d4e5e04b..cc8082bb5b 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/OrdinalIO.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/OrdinalIO.kt @@ -1,10 +1,12 @@ package net.corda.serialization.internal +import net.corda.core.KeepForDJVM import java.io.EOFException import java.io.InputStream import java.io.OutputStream import java.nio.ByteBuffer +@KeepForDJVM class OrdinalBits(private val ordinal: Int) { interface OrdinalWriter { val bits: OrdinalBits @@ -19,6 +21,7 @@ class OrdinalBits(private val ordinal: Int) { } } +@KeepForDJVM class OrdinalReader(private val values: Array) { private val enumName = values[0].javaClass.simpleName private val range = 0 until values.size diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationFormat.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationFormat.kt index caf508738c..d275643fc3 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationFormat.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationFormat.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal +import net.corda.core.KeepForDJVM import net.corda.core.serialization.SerializationEncoding import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.OpaqueBytes @@ -12,6 +13,7 @@ import java.nio.ByteBuffer import java.util.zip.DeflaterOutputStream import java.util.zip.InflaterInputStream +@KeepForDJVM class CordaSerializationMagic(bytes: ByteArray) : OpaqueBytes(bytes) { private val bufferView = slice() fun consume(data: ByteSequence): ByteBuffer? { @@ -19,6 +21,7 @@ class CordaSerializationMagic(bytes: ByteArray) : OpaqueBytes(bytes) { } } +@KeepForDJVM enum class SectionId : OrdinalWriter { /** Serialization data follows, and then discard the rest of the stream (if any) as legacy data may have trailing garbage. */ DATA_AND_STOP, @@ -34,6 +37,7 @@ enum class SectionId : OrdinalWriter { override val bits = OrdinalBits(ordinal) } +@KeepForDJVM enum class CordaSerializationEncoding : SerializationEncoding, OrdinalWriter { DEFLATE { override fun wrap(stream: OutputStream) = DeflaterOutputStream(stream) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt index 89739f6988..f37091df49 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt @@ -2,6 +2,8 @@ package net.corda.serialization.internal import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash import net.corda.core.internal.copyBytes @@ -20,6 +22,7 @@ internal object NullEncodingWhitelist : EncodingWhitelist { override fun acceptEncoding(encoding: SerializationEncoding) = false } +@KeepForDJVM data class SerializationContextImpl @JvmOverloads constructor(override val preferredSerializationVersion: SerializationMagic, override val deserializationClassLoader: ClassLoader, override val whitelist: ClassWhitelist, @@ -64,9 +67,10 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe } /* - * This class is internal rather than private so that node-api-deterministic + * This class is internal rather than private so that serialization-deterministic * can replace it with an alternative version. */ +@DeleteForDJVM internal class AttachmentsClassLoaderBuilder(private val properties: Map, private val deserializationClassLoader: ClassLoader) { private val cache: Cache, AttachmentsClassLoader> = Caffeine.newBuilder().weakValues().maximumSize(1024).build() @@ -90,10 +94,12 @@ internal class AttachmentsClassLoaderBuilder(private val properties: Map, SerializationScheme> ) : SerializationFactory() { + @DeleteForDJVM constructor() : this(ConcurrentHashMap()) companion object { @@ -155,6 +161,7 @@ open class SerializationFactoryImpl( } +@KeepForDJVM interface SerializationScheme { fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean @Throws(NotSerializableException::class) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializeAsTokenContextImpl.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializeAsTokenContextImpl.kt index 1cffdbb9a1..d028e91168 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializeAsTokenContextImpl.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializeAsTokenContextImpl.kt @@ -1,5 +1,7 @@ +@file:DeleteForDJVM package net.corda.serialization.internal +import net.corda.core.DeleteForDJVM import net.corda.core.node.ServiceHub import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationFactory @@ -19,6 +21,7 @@ fun SerializationContext.withTokenContext(serializationContext: SerializeAsToken * Then it is a case of using the companion object methods on [SerializeAsTokenSerializer] to set and clear context as necessary * when serializing to enable/disable tokenization. */ +@DeleteForDJVM class SerializeAsTokenContextImpl(override val serviceHub: ServiceHub, init: SerializeAsTokenContext.() -> Unit) : SerializeAsTokenContext { constructor(toBeTokenized: Any, serializationFactory: SerializationFactory, context: SerializationContext, serviceHub: ServiceHub) : this(serviceHub, { serializationFactory.serialize(toBeTokenized, context.withTokenContext(this)) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/ServerContexts.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/ServerContexts.kt index 4070d70e10..d19a177d2a 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/ServerContexts.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/ServerContexts.kt @@ -1,7 +1,8 @@ @file:JvmName("ServerContexts") - +@file:DeleteForDJVM package net.corda.serialization.internal +import net.corda.core.DeleteForDJVM import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults import net.corda.serialization.internal.amqp.amqpMagic diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/SharedContexts.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/SharedContexts.kt index 5f0143e002..c2d12e8b55 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/SharedContexts.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/SharedContexts.kt @@ -1,5 +1,9 @@ +@file:JvmName("SharedContexts") +@file:DeleteForDJVM package net.corda.serialization.internal +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.serialization.* import net.corda.serialization.internal.amqp.amqpMagic @@ -13,6 +17,7 @@ val AMQP_P2P_CONTEXT = SerializationContextImpl( null ) +@KeepForDJVM object AlwaysAcceptEncodingWhitelist : EncodingWhitelist { override fun acceptEncoding(encoding: SerializationEncoding) = true } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/UseCaseAwareness.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/UseCaseAwareness.kt index 5136ebe23f..ebe030b81d 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/UseCaseAwareness.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/UseCaseAwareness.kt @@ -1,5 +1,7 @@ +@file:KeepForDJVM package net.corda.serialization.internal +import net.corda.core.KeepForDJVM import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationFactory import java.util.* diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt index e5065ea06d..6a38e9c0f1 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt @@ -3,6 +3,9 @@ package net.corda.serialization.internal.amqp import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM +import net.corda.core.StubOutForDJVM import net.corda.core.cordapp.Cordapp import net.corda.core.internal.objectOrNewInstance import net.corda.core.internal.uncheckedCast @@ -31,11 +34,13 @@ interface SerializerFactoryFactory { fun make(context: SerializationContext): SerializerFactory } +@KeepForDJVM abstract class AbstractAMQPSerializationScheme( private val cordappCustomSerializers: Set>, private val serializerFactoriesForContexts: MutableMap, SerializerFactory>, val sff: SerializerFactoryFactory = createSerializerFactoryFactory() ) : SerializationScheme { + @DeleteForDJVM constructor(cordapps: List) : this(cordapps.customSerializers, ConcurrentHashMap()) // TODO: This method of initialisation for the Whitelist and plugin serializers will have to change @@ -63,6 +68,7 @@ abstract class AbstractAMQPSerializationScheme( } } + @DeleteForDJVM val List.customSerializers get() = flatMap { it.serializationCustomSerializers }.toSet() } @@ -126,6 +132,7 @@ abstract class AbstractAMQPSerializationScheme( /* * Register the serializers which will be excluded from the DJVM. */ + @StubOutForDJVM private fun registerNonDeterministicSerializers(factory: SerializerFactory) { with(factory) { register(net.corda.serialization.internal.amqp.custom.SimpleStringSerializer) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializer.kt index 283b997a84..b4b0b2e58e 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializer.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data @@ -8,6 +9,7 @@ import java.lang.reflect.Type /** * Implemented to serialize and deserialize different types of objects to/from AMQP. */ +@KeepForDJVM interface AMQPSerializer { /** * The JVM type this can serialize and deserialize. diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPStreams.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPStreams.kt index c861e22f42..c249a98aed 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPStreams.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPStreams.kt @@ -1,7 +1,8 @@ @file:JvmName("AMQPStreams") - +@file:DeleteForDJVM package net.corda.serialization.internal.amqp +import net.corda.core.DeleteForDJVM import net.corda.serialization.internal.ByteBufferInputStream import net.corda.serialization.internal.ByteBufferOutputStream import net.corda.serialization.internal.serializeOutputStreamPool diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt index 3caac111d9..061f8e7d4f 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.serialization.SerializationContext import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug @@ -13,6 +14,7 @@ import java.lang.reflect.Type /** * Serialization / deserialization of arrays. */ +@KeepForDJVM open class ArraySerializer(override val type: Type, factory: SerializerFactory) : AMQPSerializer { companion object { fun make(type: Type, factory: SerializerFactory) : AMQPSerializer { diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CollectionSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CollectionSerializer.kt index 9318da28f4..1a5c5b48b3 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CollectionSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CollectionSerializer.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.SerializationContext import net.corda.core.utilities.NonEmptySet @@ -14,6 +15,7 @@ import kotlin.collections.LinkedHashSet /** * Serialization / deserialization of predefined set of supported [Collection] types covering mostly [List]s and [Set]s. */ +@KeepForDJVM class CollectionSerializer(private val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer { override val type: Type = declaredType as? DeserializedParameterizedType ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType)) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt index 822bb3d855..6edf3e3e00 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.internal.VisibleForTesting import net.corda.core.serialization.EncodingWhitelist import net.corda.core.serialization.SerializationContext @@ -27,6 +28,7 @@ data class ObjectAndEnvelope(val obj: T, val envelope: Envelope) * @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple * instances and threads. */ +@KeepForDJVM class DeserializationInput @JvmOverloads constructor(private val serializerFactory: SerializerFactory, private val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) { private val objectHistory: MutableList = mutableListOf() diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializedParameterizedType.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializedParameterizedType.kt index 74531364a6..ada0e32823 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializedParameterizedType.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializedParameterizedType.kt @@ -1,6 +1,7 @@ package net.corda.serialization.internal.amqp import com.google.common.primitives.Primitives +import net.corda.core.KeepForDJVM import java.io.NotSerializableException import java.lang.reflect.ParameterizedType import java.lang.reflect.Type @@ -11,6 +12,7 @@ import java.util.* * Implementation of [ParameterizedType] that we can actually construct, and a parser from the string representation * of the JDK implementation which we use as the textual format in the AMQP schema. */ +@KeepForDJVM class DeserializedParameterizedType(private val rawType: Class<*>, private val params: Array, private val ownerType: Type? = null) : ParameterizedType { init { if (params.isEmpty()) { diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Envelope.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Envelope.kt index a74bd82675..37be145720 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Envelope.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Envelope.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import org.apache.qpid.proton.amqp.DescribedType import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.DescribedTypeConstructor @@ -13,6 +14,7 @@ import java.io.NotSerializableException */ // TODO: make the schema parsing lazy since mostly schemas will have been seen before and we only need it if we // TODO: don't recognise a type descriptor. +@KeepForDJVM data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: TransformsSchema) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = AMQPDescriptorRegistry.ENVELOPE.amqpDescriptor diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt index d5a71f5ac4..79e576c0bb 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.internal.isConcreteClass import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.SerializationContext @@ -43,6 +44,7 @@ abstract class EvolutionSerializer( * should be placed * @param property object to read the actual property value */ + @KeepForDJVM data class OldParam(var resultsIndex: Int, val property: PropertySerializer) { fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput, new: Array, context: SerializationContext @@ -259,6 +261,7 @@ abstract class EvolutionSerializerGetterBase { * The normal use case for generating an [EvolutionSerializer]'s based on the differences * between the received schema and the class as it exists now on the class path, */ +@KeepForDJVM class EvolutionSerializerGetter : EvolutionSerializerGetterBase() { override fun getEvolutionSerializer(factory: SerializerFactory, typeNotation: TypeNotation, diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt index 188ce947a2..39fce872fb 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt @@ -2,6 +2,7 @@ package net.corda.serialization.internal.amqp import com.google.common.hash.Hasher import com.google.common.hash.Hashing +import net.corda.core.KeepForDJVM import net.corda.core.internal.kotlinObjectInstance import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toBase64 @@ -12,6 +13,7 @@ import java.util.* /** * Should be implemented by classes which wish to provide plugable fingerprinting og types for a [SerializerFactory] */ +@KeepForDJVM interface FingerPrinter { /** * Return a unique identifier for a type, usually this will take into account the constituent elements @@ -28,6 +30,7 @@ interface FingerPrinter { /** * Implementation of the finger printing mechanism used by default */ +@KeepForDJVM class SerializerFingerPrinter : FingerPrinter { private var factory: SerializerFactory? = null diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/MapSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/MapSerializer.kt index 2cecfa492e..26e9e7f090 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/MapSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/MapSerializer.kt @@ -1,5 +1,7 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM +import net.corda.core.StubOutForDJVM import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Symbol @@ -15,6 +17,7 @@ private typealias MapCreationFunction = (Map<*, *>) -> Map<*, *> /** * Serialization / deserialization of certain supported [Map] types. */ +@KeepForDJVM class MapSerializer(private val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer { override val type: Type = (declaredType as? DeserializedParameterizedType) ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType), factory.classloader) @@ -130,6 +133,7 @@ private fun Class<*>.checkHashMap() { * The [WeakHashMap] class does not exist within the DJVM, and so we need * to isolate this reference. */ +@StubOutForDJVM private fun Class<*>.checkWeakHashMap() { if (WeakHashMap::class.java.isAssignableFrom(this)) { throw IllegalArgumentException("Weak references with map types not supported. Suggested fix: " diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializer.kt index fe052c5b3d..e83c5c4119 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializer.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.codec.Data @@ -60,6 +61,7 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe /** * A property serializer for a complex type (another object). */ + @KeepForDJVM class DescribedTypePropertySerializer( name: String, readMethod: PropertyReader, diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt index a89b1d0880..4504eca85c 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.utilities.loggerFor import java.io.NotSerializableException import java.lang.reflect.Field @@ -17,6 +18,7 @@ abstract class PropertyReader { /** * Accessor for those properties of a class that have defined getter functions. */ +@KeepForDJVM class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() { init { readMethod?.isAccessible = true @@ -55,6 +57,7 @@ class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() { * Accessor for those properties of a class that do not have defined getter functions. In which case * we used reflection to remove the unreadable status from that property whilst it's accessed. */ +@KeepForDJVM class PrivatePropertyReader(val field: Field, parentType: Type) : PropertyReader() { init { loggerFor().warn("Create property Serializer for private property '${field.name}' not " diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt index 1c6156f818..8409c99a8c 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.internal.uncheckedCast import net.corda.serialization.internal.CordaSerializationMagic import org.apache.qpid.proton.amqp.DescribedType @@ -18,6 +19,7 @@ val amqpMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(1, 0 * This and the classes below are OO representations of the AMQP XML schema described in the specification. Their * [toString] representations generate the associated XML form. */ +@KeepForDJVM data class Schema(val types: List) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = AMQPDescriptorRegistry.SCHEMA.amqpDescriptor @@ -46,6 +48,7 @@ data class Schema(val types: List) : DescribedType { override fun toString(): String = types.joinToString("\n") } +@KeepForDJVM data class Descriptor(val name: Symbol?, val code: UnsignedLong? = null) : DescribedType { constructor(name: String?) : this(Symbol.valueOf(name)) @@ -86,6 +89,7 @@ data class Descriptor(val name: Symbol?, val code: UnsignedLong? = null) : Descr } } +@KeepForDJVM data class Field( val name: String, val type: String, @@ -153,6 +157,7 @@ sealed class TypeNotation : DescribedType { abstract val descriptor: Descriptor } +@KeepForDJVM data class CompositeType( override val name: String, override val label: String?, @@ -203,6 +208,7 @@ data class CompositeType( } } +@KeepForDJVM data class RestrictedType(override val name: String, override val label: String?, override val provides: List, @@ -253,6 +259,7 @@ data class RestrictedType(override val name: String, } } +@KeepForDJVM data class Choice(val name: String, val value: String) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = AMQPDescriptorRegistry.CHOICE.amqpDescriptor @@ -282,6 +289,7 @@ data class Choice(val name: String, val value: String) : DescribedType { } } +@KeepForDJVM data class ReferencedObject(private val refCounter: Int) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = AMQPDescriptorRegistry.REFERENCED_OBJECT.amqpDescriptor diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt index 36bc35d147..65b07d2638 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt @@ -2,6 +2,7 @@ package net.corda.serialization.internal.amqp import com.google.common.primitives.Primitives import com.google.common.reflect.TypeToken +import net.corda.core.KeepForDJVM import net.corda.core.internal.isConcreteClass import net.corda.core.internal.isPublic import net.corda.core.serialization.ClassWhitelist @@ -87,6 +88,7 @@ fun propertiesForSerialization( * @property getter the method of a class that returns a fields value. Determined by * locating a function named getXyz for the property named in field as xyz. */ +@KeepForDJVM data class PropertyDescriptor(var field: Field?, var setter: Method?, var getter: Method?, var iser: Method?) { override fun toString() = StringBuilder("").apply { appendln("Property - ${field?.name ?: "null field"}\n") diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationOutput.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationOutput.kt index 531c025a3b..74e9d6b1d0 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationOutput.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationOutput.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationEncoding import net.corda.core.serialization.SerializedBytes @@ -13,6 +14,7 @@ import java.lang.reflect.Type import java.util.* import kotlin.collections.LinkedHashSet +@KeepForDJVM data class BytesAndSchemas( val obj: SerializedBytes, val schema: Schema, @@ -24,6 +26,7 @@ data class BytesAndSchemas( * @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple * instances and threads. */ +@KeepForDJVM open class SerializationOutput @JvmOverloads constructor( internal val serializerFactory: SerializerFactory, private val encoding: SerializationEncoding? = null diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt index 560ddb998d..32c958d0d2 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt @@ -2,6 +2,9 @@ package net.corda.serialization.internal.amqp import com.google.common.primitives.Primitives import com.google.common.reflect.TypeResolver +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM +import net.corda.core.StubOutForDJVM import net.corda.core.internal.kotlinObjectInstance import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.ClassWhitelist @@ -17,7 +20,9 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList import javax.annotation.concurrent.ThreadSafe +@KeepForDJVM data class SerializationSchemas(val schema: Schema, val transforms: TransformsSchema) +@KeepForDJVM data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typeDescriptor: Any) /** @@ -38,6 +43,7 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ // TODO: generic types should define restricted type alias with source of the wildcarded version, I think, if we're to generate classes from schema // TODO: need to rethink matching of constructor to properties in relation to implementing interfaces and needing those properties etc. // TODO: need to support super classes as well as interfaces with our current code base... what's involved? If we continue to ban, what is the impact? +@KeepForDJVM @ThreadSafe open class SerializerFactory( val whitelist: ClassWhitelist, @@ -48,6 +54,7 @@ open class SerializerFactory( val serializersByDescriptor: MutableMap>, private val customSerializers: MutableList, val transformsCache: MutableMap>>) { + @DeleteForDJVM constructor(whitelist: ClassWhitelist, classCarpenter: ClassCarpenter, evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), @@ -58,6 +65,7 @@ open class SerializerFactory( customSerializers = CopyOnWriteArrayList(), transformsCache = ConcurrentHashMap()) + @DeleteForDJVM constructor(whitelist: ClassWhitelist, classLoader: ClassLoader, evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), @@ -274,6 +282,7 @@ open class SerializerFactory( } } + @StubOutForDJVM private fun runCarpentry(schemaAndDescriptor: FactorySchemaAndDescriptor, metaSchema: CarpenterMetaSchema) { val mc = MetaCarpenter(metaSchema, classCarpenter) try { diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SupportedTransforms.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SupportedTransforms.kt index e93a519133..8c2bd34086 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SupportedTransforms.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SupportedTransforms.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.CordaSerializationTransformEnumDefaults import net.corda.core.serialization.CordaSerializationTransformRename @@ -15,6 +16,7 @@ import net.corda.core.serialization.CordaSerializationTransformRenames * that reference the transform. Notionally this allows the code that extracts transforms to work on single instances * of a transform or a meta list of them. */ +@KeepForDJVM data class SupportedTransform( val type: Class, val enum: TransformTypes, diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformTypes.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformTypes.kt index 8714d49206..a36c56c374 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformTypes.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformTypes.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.CordaSerializationTransformEnumDefaults @@ -20,6 +21,7 @@ import java.io.NotSerializableException */ // TODO: it would be awesome to auto build this list by scanning for transform annotations themselves // TODO: annotated with some annotation +@KeepForDJVM enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType { /** * Placeholder entry for future transforms where a node receives a transform we've subsequently diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformsSchema.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformsSchema.kt index e922136ee1..100fe70fb0 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformsSchema.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformsSchema.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp +import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.CordaSerializationTransformRename import org.apache.qpid.proton.amqp.DescribedType @@ -303,6 +304,7 @@ data class TransformsSchema(val types: Map = Class.forName(proxy.className, true, factory.classloader) + @KeepForDJVM data class ClassProxy(val className: String) } \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ContractAttachmentSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ContractAttachmentSerializer.kt index ece4cf29ac..05fe559ac7 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ContractAttachmentSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ContractAttachmentSerializer.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp.custom +import net.corda.core.KeepForDJVM import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractClassName @@ -29,5 +30,6 @@ class ContractAttachmentSerializer(factory: SerializerFactory) : CustomSerialize return ContractAttachment(proxy.attachment, proxy.contract, proxy.contracts, proxy.uploader) } + @KeepForDJVM data class ContractAttachmentProxy(val attachment: Attachment, val contract: ContractClassName, val contracts: Set, val uploader: String?) } \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/DurationSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/DurationSerializer.kt index e7c9d76147..79be3f97e7 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/DurationSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/DurationSerializer.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp.custom +import net.corda.core.KeepForDJVM import net.corda.serialization.internal.amqp.CustomSerializer import net.corda.serialization.internal.amqp.SerializerFactory import java.time.Duration @@ -12,5 +13,6 @@ class DurationSerializer(factory: SerializerFactory) : CustomSerializer.Proxy, val elements: List) } \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InstantSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InstantSerializer.kt index 236c3d01be..6a3ae66b81 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InstantSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InstantSerializer.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp.custom +import net.corda.core.KeepForDJVM import net.corda.serialization.internal.amqp.CustomSerializer import net.corda.serialization.internal.amqp.SerializerFactory import java.time.Instant @@ -12,5 +13,6 @@ class InstantSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(SimpleString::class.java) \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ThrowableSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ThrowableSerializer.kt index 4a71edf700..135e93710c 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ThrowableSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ThrowableSerializer.kt @@ -2,11 +2,13 @@ package net.corda.serialization.internal.amqp.custom import net.corda.core.CordaRuntimeException import net.corda.core.CordaThrowable +import net.corda.core.KeepForDJVM import net.corda.core.serialization.SerializationFactory import net.corda.core.utilities.contextLogger import net.corda.serialization.internal.amqp.* import java.io.NotSerializableException +@KeepForDJVM class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(Throwable::class.java, ThrowableProxy::class.java, factory) { companion object { @@ -88,5 +90,6 @@ class StackTraceElementSerializer(factory: SerializerFactory) : CustomSerializer override fun fromProxy(proxy: StackTraceElementProxy): StackTraceElement = StackTraceElement(proxy.declaringClass, proxy.methodName, proxy.fileName, proxy.lineNumber) + @KeepForDJVM data class StackTraceElementProxy(val declaringClass: String, val methodName: String, val fileName: String?, val lineNumber: Int) } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/YearMonthSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/YearMonthSerializer.kt index 4fbc1a7ddf..f2c16d009d 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/YearMonthSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/YearMonthSerializer.kt @@ -1,5 +1,6 @@ package net.corda.serialization.internal.amqp.custom +import net.corda.core.KeepForDJVM import net.corda.serialization.internal.amqp.CustomSerializer import net.corda.serialization.internal.amqp.SerializerFactory import java.time.YearMonth @@ -12,5 +13,6 @@ class YearMonthSerializer(factory: SerializerFactory) : CustomSerializer.Proxy = mutableMapOf() diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt index 7f25bef8f5..7545d97c47 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt @@ -1,6 +1,9 @@ +@file:DeleteForDJVM package net.corda.serialization.internal.carpenter import com.google.common.base.MoreObjects +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.CordaSerializable import org.objectweb.asm.ClassWriter @@ -22,6 +25,7 @@ interface SimpleFieldAccess { operator fun get(name: String): Any? } +@DeleteForDJVM class CarpenterClassLoader(parentClassLoader: ClassLoader = Thread.currentThread().contextClassLoader) : ClassLoader(parentClassLoader) { fun load(name: String, bytes: ByteArray): Class<*> = defineClass(name, bytes, 0, bytes.size) @@ -46,6 +50,7 @@ private val moreObjects: String = Type.getInternalName(MoreObjects::class.java) private val toStringHelper: String = Type.getInternalName(MoreObjects.ToStringHelper::class.java) // Allow us to create alternative ClassCarpenters. +@KeepForDJVM interface ClassCarpenter { val whitelist: ClassWhitelist val classloader: ClassLoader @@ -96,6 +101,7 @@ interface ClassCarpenter { * * Equals/hashCode methods are not yet supported. */ +@DeleteForDJVM class ClassCarpenterImpl(cl: ClassLoader, override val whitelist: ClassWhitelist) : ClassCarpenter { constructor(whitelist: ClassWhitelist) : this(Thread.currentThread().contextClassLoader, whitelist) diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/Exceptions.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/Exceptions.kt index 4d5ccfb864..ac1346b053 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/Exceptions.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/Exceptions.kt @@ -1,6 +1,7 @@ package net.corda.serialization.internal.carpenter import net.corda.core.CordaRuntimeException +import net.corda.core.DeleteForDJVM import org.objectweb.asm.Type /** @@ -16,6 +17,7 @@ abstract class InterfaceMismatchException(msg: String) : ClassCarpenterException class DuplicateNameException(val name: String) : ClassCarpenterException( "An attempt was made to register two classes with the name '$name' within the same ClassCarpenter namespace.") +@DeleteForDJVM class NullablePrimitiveException(val name: String, val field: Class) : ClassCarpenterException( "Field $name is primitive type ${Type.getDescriptor(field)} and thus cannot be nullable") diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt index e15e69e2a0..c85bb6ebd6 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt @@ -1,5 +1,8 @@ package net.corda.serialization.internal.carpenter +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM +import net.corda.core.StubOutForDJVM import net.corda.serialization.internal.amqp.CompositeType import net.corda.serialization.internal.amqp.RestrictedType import net.corda.serialization.internal.amqp.TypeNotation @@ -23,6 +26,7 @@ import net.corda.serialization.internal.amqp.TypeNotation * in turn look up all of those classes in the [dependsOn] list, remove their dependency on the newly created class, * and if that list is reduced to zero know we can now generate a [Schema] for them and carpent them up */ +@KeepForDJVM data class CarpenterMetaSchema( val carpenterSchemas: MutableList, val dependencies: MutableMap>>, @@ -47,7 +51,8 @@ data class CarpenterMetaSchema( // We could make this an abstract method on TypeNotation but that // would mean the amqp package being "more" infected with carpenter // specific bits. - fun buildFor(target: TypeNotation, cl: ClassLoader) = when (target) { + @StubOutForDJVM + fun buildFor(target: TypeNotation, cl: ClassLoader): Unit = when (target) { is RestrictedType -> target.carpenterSchema(this) is CompositeType -> target.carpenterSchema(cl, this, false) } @@ -62,6 +67,7 @@ data class CarpenterMetaSchema( * @property cc a reference to the actual class carpenter we're using to constuct classes * @property objects a list of carpented classes loaded into the carpenters class loader */ +@DeleteForDJVM abstract class MetaCarpenterBase(val schemas: CarpenterMetaSchema, val cc: ClassCarpenter) { val objects = mutableMapOf>() @@ -91,6 +97,7 @@ abstract class MetaCarpenterBase(val schemas: CarpenterMetaSchema, val cc: Class get() = cc.classloader } +@DeleteForDJVM class MetaCarpenter(schemas: CarpenterMetaSchema, cc: ClassCarpenter) : MetaCarpenterBase(schemas, cc) { override fun build() { while (schemas.carpenterSchemas.isNotEmpty()) { @@ -104,6 +111,7 @@ class MetaCarpenter(schemas: CarpenterMetaSchema, cc: ClassCarpenter) : MetaCarp } } +@DeleteForDJVM class TestMetaCarpenter(schemas: CarpenterMetaSchema, cc: ClassCarpenter) : MetaCarpenterBase(schemas, cc) { override fun build() { if (schemas.carpenterSchemas.isEmpty()) return diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/Schema.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/Schema.kt index 3685cacf8c..8690668367 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/Schema.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/Schema.kt @@ -1,9 +1,13 @@ +@file:DeleteForDJVM package net.corda.serialization.internal.carpenter +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import org.objectweb.asm.ClassWriter import org.objectweb.asm.Opcodes.* import java.util.* +@KeepForDJVM enum class SchemaFlags { SimpleFieldAccess, CordaSerializable } @@ -16,6 +20,7 @@ enum class SchemaFlags { * - [InterfaceSchema] * - [EnumSchema] */ +@KeepForDJVM abstract class Schema( val name: String, var fields: Map, @@ -40,6 +45,7 @@ abstract class Schema( fun descriptorsIncludingSuperclasses(): Map = (superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + fields.descriptors() + @DeleteForDJVM abstract fun generateFields(cw: ClassWriter) val jvmName: String @@ -64,6 +70,7 @@ fun EnumMap.simpleFieldAccess(): Boolean { /** * Represents a concrete object. */ +@DeleteForDJVM class ClassSchema( name: String, fields: Map, @@ -79,6 +86,7 @@ class ClassSchema( * Represents an interface. Carpented interfaces can be used within [ClassSchema]s * if that class should be implementing that interface. */ +@DeleteForDJVM class InterfaceSchema( name: String, fields: Map, @@ -93,6 +101,7 @@ class InterfaceSchema( /** * Represents an enumerated type. */ +@DeleteForDJVM class EnumSchema( name: String, fields: Map @@ -114,6 +123,7 @@ class EnumSchema( * Factory object used by the serializer when building [Schema]s based * on an AMQP schema. */ +@DeleteForDJVM object CarpenterSchemaFactory { fun newInstance( name: String, diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/SchemaFields.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/SchemaFields.kt index 2cd35385a3..c9babf9348 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/SchemaFields.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/SchemaFields.kt @@ -1,6 +1,7 @@ package net.corda.serialization.internal.carpenter import jdk.internal.org.objectweb.asm.Opcodes.* +import net.corda.core.DeleteForDJVM import org.objectweb.asm.ClassWriter import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Type @@ -15,7 +16,9 @@ abstract class Field(val field: Class) { var name: String = unsetName abstract val type: String + @DeleteForDJVM abstract fun generateField(cw: ClassWriter) + @DeleteForDJVM abstract fun visitParameter(mv: MethodVisitor, idx: Int) } @@ -26,6 +29,7 @@ abstract class Field(val field: Class) { * - [NullableField] * - [NonNullableField] */ +@DeleteForDJVM abstract class ClassField(field: Class) : Field(field) { abstract val nullabilityAnnotation: String abstract fun nullTest(mv: MethodVisitor, slot: Int) @@ -59,6 +63,7 @@ abstract class ClassField(field: Class) : Field(field) { * * maps to AMQP mandatory = true fields */ +@DeleteForDJVM open class NonNullableField(field: Class) : ClassField(field) { override val nullabilityAnnotation = "Ljavax/annotation/Nonnull;" @@ -89,6 +94,7 @@ open class NonNullableField(field: Class) : ClassField(field) { * * maps to AMQP mandatory = false fields */ +@DeleteForDJVM class NullableField(field: Class) : ClassField(field) { override val nullabilityAnnotation = "Ljavax/annotation/Nullable;" @@ -110,6 +116,7 @@ class NullableField(field: Class) : ClassField(field) { /** * Represents enum constants within an enum */ +@DeleteForDJVM class EnumField : Field(Enum::class.java) { override var descriptor: String? = null @@ -130,6 +137,7 @@ class EnumField : Field(Enum::class.java) { * Constructs a Field Schema object of the correct type depending weather * the AMQP schema indicates it's mandatory (non nullable) or not (nullable) */ +@DeleteForDJVM object FieldFactory { fun newInstance(mandatory: Boolean, name: String, field: Class) = if (mandatory) NonNullableField(name, field) else NullableField(name, field) diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/GenericsTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/GenericsTests.kt index e7c24304c6..a99fefd455 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/GenericsTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/GenericsTests.kt @@ -12,6 +12,7 @@ import net.corda.serialization.internal.AllWhitelist import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.core.TestIdentity import org.junit.Test +import java.net.URI import java.util.* import kotlin.test.assertEquals @@ -28,7 +29,7 @@ class GenericsTests { const val VERBOSE = true @Suppress("UNUSED") - var localPath = projectRootDir.toUri().resolve( + var localPath: URI = projectRootDir.toUri().resolve( "serialization/src/test/resources/net/corda/serialization/internal/amqp") val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) diff --git a/settings.gradle b/settings.gradle index 0e31eeb1fb..e2107da0bc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,10 @@ include 'confidential-identities' include 'finance' include 'finance:isolated' include 'core' +include 'core-deterministic' +include 'core-deterministic:testing' +include 'core-deterministic:testing:common' +include 'core-deterministic:testing:data' include 'docs' include 'node-api' include 'node' @@ -22,6 +26,7 @@ include 'experimental:sandbox' include 'experimental:quasar-hook' include 'experimental:kryo-hook' include 'experimental:corda-utils' +include 'jdk8u-deterministic' include 'test-common' include 'test-utils' include 'smoke-test-utils' @@ -53,4 +58,4 @@ include 'samples:notary-demo' include 'samples:bank-of-corda-demo' include 'samples:cordapp-configuration' include 'serialization' - +include 'serialization-deterministic'