ENT-1463, ENT-1903: Create core-deterministic and serialization-deterministic artifacts. ()

* Create core-deterministic and serialization-deterministic artifacts:

commit 6f77838fe53d7c9565283c20bbf20f27954b27f6
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue May 29 23:14:23 2018 +0100

    Tidy up Gradle files.

commit 0aa958d31c6342e92ad4d6ab396db6e4a39d4fed
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue May 29 18:11:17 2018 +0100

    Fix EnclaveletTest with deterministic core and serialisation.

commit 732fcf37ee2219dfad373200676241d2fd90aeb3
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun May 27 00:21:42 2018 +0100

    Extend JarFilter to delete typealias declarations.

commit 25dbf30ed62c0c059df07782306b7f760f4cdf73
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu May 24 17:20:41 2018 +0100

    Test deserialising a contract and verifying it using core-deterministic.

commit f7753bf2ab588e084cb8bfaa5fd04f1a18d3aaef
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu May 24 11:25:49 2018 +0100

    Do not remove constructors from Kotlin annotation metadata.

commit 4ddf357b71b29775aa921aca33b4505a402a20e8
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed May 23 16:30:18 2018 +0100

    Add Gradle modules for deterministic rt.jar artifacts.

commit e81f462eefad2369706fd1b8447d426a71a25a03
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed May 23 16:22:04 2018 +0100

    Isolate reference to WeakHashMap - for deletion!

commit eea2458fbec06b28344547fdf9c191a9445fe1e7
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu May 17 18:01:20 2018 +0100

    Extract Kotlin metadata classes from kotlin-compiler-embeddable.
    This fixes a classpath issue that was crashing Gradle.

commit 87fdb938d83f3de6589730343c860fbbc406942e
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue May 15 15:40:31 2018 +0100

    Remove instances of ConcurrentHashMap from AMQP serialization scheme.

commit 9e7773ec32542af4df62269aea3d08e2bd3794f9
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon May 14 23:10:09 2018 +0100

    Fix the checkDeterminism targets to validate JAR.

commit 6066ba89cb0077b17a7bdda79195763e86d100f9
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon May 14 09:16:14 2018 +0100

    Remove private Blob object.

commit 73180723ad437b07ba4ccfd935620c0fa97039ea
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun May 13 23:48:48 2018 +0100

    Ignore unit tests if important system property is not set.

commit abfa0a85aff72007342142a9c66fea3b48f62cc7
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sat May 12 22:18:28 2018 +0100

    Make deterministic tests involving keystores optional.

commit 5866f8f08910cbfa90c006e88482acec467042a5
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri May 11 17:19:57 2018 +0100

    Prevent checked exceptions escaping from JarFilter tasks.

commit e2a41913e00aff2bb9b59b43f0a721c5547a8683
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu May 10 17:18:30 2018 +0100

    Create node-api-deterministic artifact.

commit 804feb4e69be4899f29c0cb1c5be95f58d2c47c9
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu May 10 16:54:46 2018 +0100

    Upgrade to ProGuard 6.0.3

commit ec12b0ed213c1336202012ccf864a49bb8adf727
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed May 9 12:56:15 2018 +0100

    Extend JarFilter to identify extension properties correctly.

commit f0e119e2e3d90db80efb38a316f48b34082c5f49
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sat May 5 13:47:51 2018 +0100

    Construct a better test jar for tasks to unzip/rezip.

commit a13380c0ee29dbdd93419f29c01a904c4a69db15
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri May 4 09:47:32 2018 +0100

    Update JarFilter to Kotlin 1.2.41.

commit b774a1e359fb08077a57e8c3b4f1b314653deec0
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu May 3 16:27:43 2018 +0100

    Convert some JarFilter functions into val properties.

commit b38f9a8f53a3e68e62580e0b8af625b37463cd41
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed May 2 23:05:24 2018 +0100

    Tidy up Gradle test projects.

commit 421c2e6c93c0c7317e7977fd7bf134902920760e
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed May 2 22:12:18 2018 +0100

    Published core-deterministic artifact is actually built by metafix task.

commit 6d7b20a6826e4c04cd252a4ff4d30ceeb9193eb4
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed May 2 16:55:01 2018 +0100

    Always recompute compressed sizes for ZipEntry.

commit 05587234c4f87aeab925b73f7b7fdc22a2d77159
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed May 2 15:14:12 2018 +0100

    Test whether MetaFix task can delete file timestamps from the jar.

commit 9d6bd0d5cf9f05f088d98eaf7399db4cafc64c61
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue May 1 22:15:02 2018 +0100

    WIP - erase timestamps for all jar entries.

commit 4cb4d6213916d752a654d4fa8d22db6fe6e7e9c6
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue May 1 17:39:56 2018 +0100

    Annotate more core elements as @Deterministic/@NonDeterministic.

commit e847c6c1f03665bd0eff228ce242958512155860
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue May 1 17:17:24 2018 +0100

    Update JarFilter so that void methods are stubbed with empty bodies.

commit f53d7b48676f2b3d2b2062bc12591f9966a8db83
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue May 1 16:07:48 2018 +0100

    Rename @DeterministicStub to @NonDeterministicStub.

commit 0c2e7e76587805b72f0270cdbbc067a909abae82
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue May 1 15:57:37 2018 +0100

    Consistency fix for JarFilter log messages about methods.

commit 43a5c342c508fcc690a02b94926cf4153b5eb297
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue May 1 13:25:15 2018 +0100

    Reorganise determinism unit test.

commit 6079d319d20a6c0cb7386bfcf98b675a73bff040
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon Apr 30 23:10:23 2018 +0100

    Allow file timestamps to be thrown away for reproducible builds.

commit 7068f2fcd46d3f600710ccd9312b9d8dc46f1f38
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon Apr 30 21:55:26 2018 +0100

    Declare PlatformSecureRandom as non-deterministic.

commit 3a5b8eff11a7200f48310408442880967260d80e
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon Apr 30 17:48:20 2018 +0100

    Test deleting property initialisers from <clinit> block.

commit a91f75cf8eb813305adcfd962d8931a1b9322915
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun Apr 29 23:34:26 2018 +0100

    Suppress lots of "UNUSED" warnings for test classes.

commit fb09396f14cb6b2b80e80209091afe370cef15ab
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun Apr 29 23:10:13 2018 +0100

    Add crude test for fixing package metadata.

commit 80a9c794bcbc6cbfb7010285c9e94faa9c17310a
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun Apr 29 21:45:28 2018 +0100

    Refactor GradleRunner code into a JUnit Rule.

commit 5615dd6624991af49ae283beb3dfe1223d0f26f3
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun Apr 29 00:55:10 2018 +0100

    Add JaCoCo support to JarFilter plugin.

commit df55b962aa77f170d4183865743a263d11f061b3
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sat Apr 28 16:48:58 2018 +0100

    Allow the executor to iterate the Visitor more than twice.

commit d906e3996b7724528e69fc4abe79c2b59b2f03cb
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sat Apr 28 16:19:52 2018 +0100

    Add tests for deleting static properties.

commit f87120efeeb9b6edd129ca71852d1c1bc3fe7e57
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Apr 27 17:34:39 2018 +0100

    Ensure that SgxSupport.isInsideEnclave is always true for core-deterministic.

commit 85ff9cb17492ae93f0e4f5bbaa2d935e4d776b13
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Apr 27 14:26:14 2018 +0100

    Test deleting field references from constructor byte-code.

commit ac45aa04c60dab71553ddf0ddfc97ecaed6c84af
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Apr 26 22:49:39 2018 +0100

    Add tweak to ClassVisitor code.

commit 70bc232592e8739546e3f0cdb90add29b5953cf8
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Apr 26 18:37:55 2018 +0100

    Declare more Crypto functionality as Deterministic/NonDeterministic.

commit 6ceb49af6b75e90ce8e6d739ca6b012627ed6128
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Apr 26 15:39:41 2018 +0100

    Rewrite constructors not to reference deleted fields.

commit ed1a0e76e68d49531026e130d3c4d4ca56b3e06d
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Apr 26 09:48:38 2018 +0100

    Configure ASM to compute max stack and locals automatically.

commit 0c1a789bf0824b8a18c57a04f4428c678864b76d
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Apr 25 17:30:52 2018 +0100

    Add isConstructor property to MethodElement.

commit acd640db52b2b1051c67067c30414d2035c9d064
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Apr 25 14:59:33 2018 +0100

    Add test cases for deleting singleton objects.

commit 1a1b79ee13f993dd9cfc9ab8f570e96a5f2e3530
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Apr 25 12:18:49 2018 +0100

    Extend JarFilter to delete lazy properties.

commit acea7942ad85107e0deec6bef1a0c9d88329b9c4
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue Apr 24 16:04:48 2018 +0100

    Remove functions for measuring elapsed time.

commit 03cc5c53b5b220ceccf43b0a3a218e84055a2f17
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon Apr 23 18:29:47 2018 +0100

    Modify MetaFixer task to remove deleted nested classes.

commit 281c5c06b69fe4bbc28d41aa46c3cf4b6c625877
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon Apr 23 15:02:54 2018 +0100

    Removing dangling references to deleted nested classes.

commit 8bd44ab76dca21b1198db37a1e574538f99c2555
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun Apr 22 20:15:00 2018 +0100

    Refactor StubbingMethodAdapter to rewrite remaining annotations.

commit 59f7392155fe79c9017af563c4705ef5f486dd6b
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun Apr 22 01:14:59 2018 +0100

    Small tidy-up of property tests.

commit 7696708ddf3370b13ff5ea2727b2e03380792098
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun Apr 22 00:53:28 2018 +0100

    Test backing field when deleting a property.

commit 083d7678ea73fde03be62d1b845654b9ec9c0c9a
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun Apr 22 00:40:45 2018 +0100

    Refactor isFunction() and isConstructor() for Kotlin reflection.

commit cbb5bc30d9fb991d12a8c3775e715b49a2c13abd
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sun Apr 22 00:33:11 2018 +0100

    Ensure that stubbed out functions keep their annotations.

commit 14947aa105cb7c336b6a7cffa875b6add8000c5d
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sat Apr 21 16:11:33 2018 +0100

    Test JarFilter interactions between deleting and stubbing out.

commit 2d2a944f56268a697d110923a73589bf71145011
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Apr 20 16:59:40 2018 +0100

    Annotate more core classes as @Deterministic.

commit a23382ff1930747fa55497fcd8c18e00bf980d4f
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Apr 20 15:48:49 2018 +0100

    Extend JarFilter unit test coverage.

commit af0d3b32c85e23fb7a6c6e9a0639cc0d22a7213f
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Apr 20 14:49:56 2018 +0100

    Enhance JarFilter - also delete methods that use deleted methods/fields.

commit e6cd87e73b5509656faa6879ab8057690c8450ad
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Apr 19 18:40:30 2018 +0100

    Upgrade AssertJ to 3.9.1

commit ab217563de2cb60a690221d1d497247d04486060
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Apr 19 18:20:54 2018 +0100

    Add unit tests for the metadata-fixing task.

commit ddff9a10e8aa6dae81b597ff757edee0125d663f
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Apr 19 09:01:25 2018 +0100

    Refactor Hamcrest matchers into a separate package.

commit afa3f5a825f8127bec262ff0a7ece5af1e0c6dfb
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Apr 18 23:15:05 2018 +0100

    Add unit tests for MethodElement and FieldElement.

commit dd412756bc99ff46083558e6863498ae1493a4b7
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Apr 18 17:06:42 2018 +0100

    Declare exception and MerkleTree classes as deterministic.

commit ce732d2cfb17a8f70a4bc71ccad4d75e68e240c7
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Apr 18 16:04:33 2018 +0100

    Never remove @JvmField properties from companion objects when fixing metadata.

commit c2a5b35b351480c637dc023c07043243b7f16ee5
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Apr 18 10:37:28 2018 +0100

    Rename MetaFix* classes to MetaFixer*.

commit 358916bef7eb9955f3fc7cea9ab08286ab153564
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue Apr 17 16:21:10 2018 +0100

    Extend JarFilter to remove getters/setters along with properties.

commit 0c96a154b89244cdc93c53563aacd40b019182d4
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue Apr 17 13:11:29 2018 +0100

    Extend JarFilter tests for deleting properties.

commit bb63fbacbd46e93eb2dbecca21161968d11fc59e
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue Apr 17 12:28:37 2018 +0100

    Fix determination of CordaException.

commit cb92d47643e1a9c41267e548fc79d077da941b28
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue Apr 17 12:25:06 2018 +0100

    Refactor JarFilter - support deleting @JvmField properties.

commit 349b1a7fe9bec140e1f988e104ec44a8e65745c6
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Apr 13 17:32:07 2018 +0100

    Preliminary Gradle task to remove missing elements from @Metadata.

commit f4564e6661458a317f2ebf0e8ce0fbdeae5e1c30
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue Apr 3 20:46:41 2018 +0100

    Upgrade to ProGuard 6.0.2

commit c937109398c242bb09d0157cec8debded6012a1b
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 29 12:04:52 2018 +0100

    Refactor internal Kotlin dependencies into MetadataTransformer.

commit 899a315a2684986249c88f647784f88235205530
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 29 09:48:05 2018 +0100

    Upgrade to ASM 6.1.1.

commit 592e1ced7d36f0838c634cb413af9d0b4b8b516b
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Sat Mar 24 13:37:17 2018 +0000

    Remove unwanted Kotlin artifacts from the JarFilter's classpath.

commit 4591d54c247fc9937f202306e2a5ec872fb2dbea
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 23 10:04:49 2018 +0000

    Tidy up output from Kotlin reflection matchers.

commit fb78d898ef1428210bbb030f43b9a2024f1fdeb1
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 23 09:42:38 2018 +0000

    Remove lateinit field from ClassTransformer.

commit c08ecb2139550ea1bc6ab6cebb3ab180e037c40a
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 22 18:25:54 2018 +0000

    Remove non-deterministic DelegatingSecureRandom* classes from core-deterministic.

commit 7c3e8e794ec868ff4385661ff68081f2bc5ba09c
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 22 18:24:48 2018 +0000

    Stop removing @kotlin.Metadata annotations from core-deterministic.

commit 16ce8ceee91793efb8a100e29d1770f23cf02643
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 22 15:42:58 2018 +0000

    Add (C) headers for new files.

commit 6146b0b47d9e9f46873506711cbef60477aea655
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 22 12:43:43 2018 +0000

    Log synthetic classes, but do not adjust @Metadata.

commit 016b2be942533790413e28d50d6dc8b104a4de5c
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 22 12:08:36 2018 +0000

    Add @Metadata support for Kotlin multi-file classes.

commit 9eeed582a083c34a0580f1049cad42d7dc8812a1
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 22 10:38:09 2018 +0000

    Add JarFilter unit tests for @kotlin.Metadata updates.

commit eb71cb3d76a45fa15eedf478e6172e33a8127305
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Mar 21 15:29:01 2018 +0000

    Update JarFilter plugin to remove references to deleted constructors, functions and fields from @kotlin.Metadata.

commit c28c099546dd24ab6f158b633e494948fabb6b5e
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 15 18:27:06 2018 +0000

    Tidy up Enclavelet tests slightly.

commit 895dfe659b9ffa6e39b407606876facc153e3128
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 15 18:25:14 2018 +0000

    Annotate more Attachment / Transaction classes as @Deterministic.

commit f5ab283d09a803b9e2e0f465841cd072e9a7040f
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 15 14:21:51 2018 +0000

    Upgrade to ProGuard 6.0.1

commit c7717cc0106f39fec822bce8fbbcf18a75a25c2d
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 15 11:11:34 2018 +0000

    Adjust LedgerTransaction to remove ClassLoader references when deterministic.

commit 5b37fe9f3f716944f2eb3952870d2e9548dc144d
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Mar 14 16:28:41 2018 +0000

    Extra testing for deterministic SecureHash.

commit 01be61676edddf28d4b16a75cff1dd5fe2079c03
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon Mar 12 16:32:05 2018 +0000

    Keep synthetic methods for Kotlin classes.

commit cb01f28089c94457c0498802741dcc742a52eaac
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon Mar 12 14:35:17 2018 +0000

    Add libraries to core-deterministic's runtimeArtifacts configuration.

commit c23ad307596c07a608d6ce3e600fe1b0aee94ef1
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon Mar 12 11:20:35 2018 +0000

    Check that JarFilter's different annotations are all distinct.

commit 4b84451f9d124cba75bb4a1984b9a9d9f60efd17
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 9 17:01:15 2018 +0000

    Update the JarFilter plugin to remove some annotations.
    This is for stripping @kotlin.Metadata from deterministic classes.

commit 72c4740ffdd5fcb9a7828a1324f6632747fe3115
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 9 14:11:16 2018 +0000

    Configure ProGuard to preverify the deterministic JAR.

commit 9fce4724ac3e1cb80f89d38f63a28b39585dfbf9
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 9 14:09:33 2018 +0000

    Update to corda-gradle-plugins 4.0.7-SNAPSHOT.

commit fc46624ea2f1c862c9b2a2064a9007ffdc1b94d8
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 8 18:08:20 2018 +0000

    Allow core-deterministic artifact to be tested and published.

commit 238814ad2d94dd74fd7cbae7dc3b4d1016697850
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Mar 8 14:54:27 2018 +0000

    Since Kotlin 1.2.x, Kotlin artifact dependencies match Kotlin plugin version by default.

commit f81b3772b598995d0df0519512ae1c6b1d4d238b
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Mar 7 13:46:41 2018 +0000

    Update KDoc for @Deterministic annotation.

commit 7a1b0fbe6540958bbc743981a3ba724f0f22ef80
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Mar 7 12:27:22 2018 +0000

    Add (C) headers for JarFilter and deterministic core.

commit 0add901e55a23c898da7c6a3ec0c4273d7555441
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Mar 7 09:30:38 2018 +0000

    Refactor function name for compatibility with corda-gradle-plugins.

commit f37a73dea8969a82ceda48072cb7d393c05a44c7
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue Mar 6 13:57:58 2018 +0000

    Include more contract states in core-deterministic.

commit b2eeb08be90fa1a0739854d0c393a23b8c49aed0
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue Mar 6 11:18:53 2018 +0000

    Remove synchronized section from deterministic ToggleField.

commit 353257e6a04de1447c674f43989e2fc8aecc807a
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 2 15:24:46 2018 +0000

    Extend @NonDeterministic also to target JVM fields.

commit 9dc940c4f9ae8e29e043cdf93634d072373eb030
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 2 15:21:03 2018 +0000

    Add tests for deleting field.

commit 2bf43957ed656c419cbf1a0a0ba48b755b8e8ac9
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 2 14:16:33 2018 +0000

    Tidy up Kotlin lambdas.

commit 45dc150cfc0b7090816036a4f4f3ce7ae5cde79b
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 2 10:27:57 2018 +0000

    Set 'gradle.user.home' for test-kit's GradleRunner. This allows
    it to share the project's module cache, which means that it doesn't
    need to download its own copy of Kotlin over the Internet.

commit d79ffd0b44cc890dc8e0f513e5d5baaeaddb5d50
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 2 00:41:00 2018 +0000

    Remove settings.gradle from tests - it was not the solution.

commit b30fdcd4c2b44370294ae78699b1424e817b13de
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Mar 2 00:28:42 2018 +0000

    Create plugin descriptor using java-gradle-plugin.

commit a9e7cbe51e5d3f0d8efea0501ef4858fd3511cd0
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Feb 28 16:55:53 2018 +0000

    Resolve simple compiler warning.

commit d247524090539a0d708d383f25e9539a6e6ee809
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Feb 28 16:03:19 2018 +0000

    Add local settings.gradle for all unit tests.

commit 031411c71fda98511f9fba6c763cb6d3f74d95eb
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Feb 28 13:58:06 2018 +0000

    Add test filtering interface functions.

commit dcc6055ae01fb9e98bea73befe7a5cf473e27590
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Feb 28 13:45:25 2018 +0000

    Add test for filtering abstract functions.

commit 0c084f96aa4cbf7173f633dd1d4fa6e633cea6a7
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Feb 28 11:26:27 2018 +0000

    Add tests for stubbing static functions out.

commit 3412e3479f09f36e34a33bbd7564bd95b4bbd017
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Feb 28 11:13:35 2018 +0000

    Add tests for deleting static functions.

commit 5d8ce9ce1edbee0020595af99c20268de8c38c5f
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Feb 28 10:50:03 2018 +0000

    Add test for stubbing out a var property.

commit dea60c8252b0bc849845fdeecc28f67817ef77d8
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Feb 28 10:41:13 2018 +0000

    Add test for stubbing a val property out.

commit c69de1b904b496fe146e91eb7e6d138171528b1a
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Feb 28 10:28:13 2018 +0000

    Add tests for stubbing constructors out.

commit 1f791cf6013700689e38b129460eba1d20dc5efa
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Wed Feb 28 00:35:23 2018 +0000

    Add tests for deleting constructors.

commit 55790a8abb3dba50b4a136760c9a21dc1bd214ca
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue Feb 27 18:37:51 2018 +0000

    Add (and fix) test for stubbing a function out.

commit 1f03202197a9e1fe9023848869e0273a05eef3dc
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Tue Feb 27 13:09:55 2018 +0000

    Refactor buildSrc into a multi-module project.

commit 4c937580f40753408b6f29cfc72741b412e4ed3e
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Mon Feb 26 17:15:50 2018 +0000

    Initial unit testing framework for JarFilter plugin.

commit 45afcaa082cb3f7223d42458a28af14c7c02d611
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Fri Feb 23 12:32:04 2018 +0000

    Allow some methods to be stubbed out instead of deleted.

commit c5911ec643739369e138a5b451cafa7c067c4134
Author: Chris Rankin <chris.rankin@r3.com>
Date:   Thu Feb 22 10:31:18 2018 +0000

    ENT-1468: Initial version of the JarFilterTask.

* Refactor deterministic test data to work on Windows.
* Fix JarFilter unit tests on Windows.
* Upgrade JarFilter to ASM 6.2
* Allow core-deterministic, serialization-deterministic to be published to Artifactory.
* Share repository configuration between all JarFilter unit tests.
* Small fixes after review.
* Ensure core-deterministic and serialization-deterministic are published with their dependencies.
* Fix logic for number of JarFilter passes.
* Add README for JarFilter plugin.
* Move JarFilter plugin into the 'net.corda.plugins' namespace.
* Modify JarFilter to update sealed subclasses in @kotlin.Metadata.
* Add Gradle fixes from @Clintonio.
* Annotate TransientClassWhitelist as deterministic.
* Add literalinclude blocks to the deterministic-module docs.
* Fix Kotlin Metadata properly when all nested classes are deleted.
* Small tidy-up for Gradle files.
* Add some KDoc for the JarFilter and deterministic classes.
* Update JarFilter to handle properties with generic return types.
* Remove some uses of Java Reflection from DJVM.
* Rename determinism annotations to @KeepForDJVM, @DeleteForDJVM, @StubOutForDJVM.
This commit is contained in:
Chris Rankin 2018-06-11 20:34:59 +01:00 committed by GitHub
parent 4951ad75d5
commit 5d42f48966
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
349 changed files with 11114 additions and 163 deletions
build.gradle
buildSrc
build.gradle
jarfilter
README.mdbuild.gradle
kotlin-metadata
src
main/kotlin/net/corda/gradle/jarfilter
test
kotlin/net/corda/gradle/jarfilter
resources
abstract-function
build.gradle
kotlin/net/corda/gradle
annotations/kotlin/net/corda/gradle/jarfilter
delete-and-stub
delete-constructor
delete-extension-val
build.gradle
kotlin/net/corda/gradle
delete-field
build.gradle
kotlin/net/corda/gradle
delete-file-typealias
build.gradle
kotlin/net/corda/gradle
delete-function
delete-lazy
build.gradle
kotlin/net/corda/gradle
delete-multifile
delete-nested-class
build.gradle
kotlin/net/corda/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'

@ -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 {

@ -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<String> = []
val d2: Array<String> = []
// ...
}
```
This is an internal feature of Kotlin which is read by Kotlin Reflection. There is no public API
for writing this information, and the content format of arrays `d1` and `d2` depends upon the
"class kind" `k`. For the kinds that we are interested in, `d1` contains a buffer of ProtoBuf
data and `d2` contains an array of `String` identifiers which the ProtoBuf data refers to by index.
Although ProtoBuf generates functions for both reading and writing the data buffer, the
Kotlin Reflection artifact only contains the functions for reading. This is almost certainly
because the writing functionality has been removed from the `kotlin-reflect` JAR using
ProGuard. However, the complete set of generated ProtoBuf classes is still available in the
`kotlin-compiler-embeddable` JAR. The `jarfilter:kotlin-metadata` module uses ProGuard to
extracts these classes into a new `kotlin-metdata` JAR, discarding any classes that the
ProtoBuf ones do not need and obfuscating any other ones that they do.
The custom `kotlin-metadata` object was originally created as a workaround for
[KT-18621](https://youtrack.jetbrains.com/issue/KT-18621). However, reducing the number of unwanted
classes on the classpath anyway can only be a Good Thing<sup>(TM)</sup>.
At runtime, `JarFilter` decompiles the ProtoBuf buffer into POJOs, deletes the elements that
no longer exist in the byte-code and then recompiles the POJOs into a new ProtoBuf buffer. The
`@Metadata` annotation is then rewritten using this new buffer for `d1` and the _original_ `String`
identifiers for `d2`. While some of these identifiers are very likely no longer used after this,
removing them would also require re-indexing the ProtoBuf data. It is therefore simpler just to
leave them as harmless cruft in the byte-code's constant pool.
The majority of `JarFilter`'s unit tests use Kotlin and Java reflection and so should not be
brittle as Kotlin evolves because `kotlin-reflect` is public API. Also, Kotlin's requirement that
it remain backwards-compatible with itself should imply that the ProtoBuf logic shouldn't change
(much). However, the ProtoBuf classes are still internal to Kotlin and so it _is_ possible that they
will occasionally move between packages. This has already happened for Kotlin 1.2.3x -> 1.2.4x, but
I am hoping this means that they will not move again for a while.
### JARs vs ZIPs
The `JarFilter` and `MetaFixer` tasks _deliberately_ use `ZipFile` and `ZipOutputStream` rather
than `JarInputStream` and `JarOutputStream` when reading and writing their JAR files. This is to
ensure that the original `META-INF/MANIFEST.MF` files are passed through unaltered. Note also that
there is no `ZipInputStream.getComment()` method, and so we need to use `ZipFile` in order to
preserve any JAR comments.
Neither `JarFilter` nor `MetaFixer` should change the order of the entries inside the JAR files.

@ -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"
}

@ -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

@ -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<String, List<String>>,
private val removeAnnotations: Set<String>,
private val deleteAnnotations: Set<String>,
private val stubAnnotations: Set<String>,
private val unwantedClasses: MutableSet<String>,
private val unwantedFields: MutableSet<FieldElement>,
private val deletedMethods: MutableSet<MethodElement>,
private val stubbedMethods: MutableSet<MethodElement>
) : KotlinAwareVisitor(ASM6, visitor, logger, kotlinMetadata), Repeatable<ClassTransformer> {
constructor(
visitor: ClassVisitor,
logger: Logger,
removeAnnotations: Set<String>,
deleteAnnotations: Set<String>,
stubAnnotations: Set<String>,
unwantedClasses: MutableSet<String>
) : 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<String>?) {
_className = clsName
logger.info("Class {}", clsName)
super.visit(version, access, clsName, signature, superName, interfaces)
}
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
if (removeAnnotations.contains(descriptor)) {
logger.info("- Removing annotation {}", descriptor)
return null
} else if (deleteAnnotations.contains(descriptor)) {
if (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<String>?): 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<String>, d2: List<String>): List<String> {
val partitioned = deletedMethods.groupBy(MethodElement::isConstructor)
val prefix = "$className$"
return ClassMetadataTransformer(
logger = logger,
deletedFields = unwantedFields,
deletedFunctions = partitioned[false] ?: emptyList(),
deletedConstructors = partitioned[true] ?: emptyList(),
deletedNestedClasses = 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<String>, d2: List<String>): List<String> {
return PackageMetadataTransformer(
logger = logger,
deletedFields = unwantedFields,
deletedFunctions = deletedMethods,
handleExtraMethod = ::delete,
d1 = d1,
d2 = d2)
.transform()
}
/**
* Callback function to mark extra methods for deletion.
* This will override a request for stubbing.
*/
private fun delete(method: MethodElement) {
if (deletedMethods.add(method) && stubbedMethods.remove(method)) {
logger.warn("-- method {}{} will be deleted instead of stubbed out",
method.name, method.descriptor)
}
}
/**
* Analyses the field to decide whether it should be deleted.
*/
private inner class UnwantedFieldAdapter(fv: FieldVisitor, private val field: FieldElement) : FieldVisitor(api, fv) {
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
if (removeAnnotations.contains(descriptor)) {
logger.info("- Removing annotation {} from field {},{}", descriptor, field.name, field.descriptor)
return null
} else if (deleteAnnotations.contains(descriptor)) {
if (unwantedFields.add(field)) {
logger.info("- Identified field {},{} as unwanted", field.name, field.descriptor)
}
}
return super.visitAnnotation(descriptor, visible)
}
}
/**
* Analyses the method to decide whether it should be deleted.
*/
private inner class UnwantedMethodAdapter(mv: MethodVisitor, private val method: MethodElement) : MethodVisitor(api, mv) {
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
if (removeAnnotations.contains(descriptor)) {
logger.info("- Removing annotation {} from method {}{}", descriptor, method.name, method.descriptor)
return null
} else if (deleteAnnotations.contains(descriptor)) {
if (deletedMethods.add(method)) {
logger.info("- Identified method {}{} for deletion", method.name, method.descriptor)
}
if (method.isKotlinSynthetic("annotations")) {
val extensionType = method.descriptor.extensionType
if (unwantedFields.add(FieldElement(name = method.visibleName, extension = extensionType))) {
logger.info("-- also identified property or typealias {},{} for deletion", method.visibleName, extensionType)
}
}
} else if (stubAnnotations.contains(descriptor) && (method.access and (ACC_ABSTRACT 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", "<init>", "(Ljava/lang/String;)V", false)
visitInsn(ATHROW)
}
}
}
/**
* Write an empty method. Can only be applied to methods that return void.
*/
private inner class VoidStubMethodAdapter(mv: MethodVisitor) : StubbingMethodAdapter(mv) {
override fun writeStubCode() {
mv.visitInsn(RETURN)
}
}
}

@ -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 == "<clinit>"
val isObjectConstructor: Boolean get() = name == "<init>"
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)
}

@ -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<Project> {
override fun apply(project: Project) {
project.logger.info("Applying JarFilter plugin")
}
}

@ -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<String> = emptySet()
@get:Input
protected var forStub: Set<String> = emptySet()
@get:Input
protected var forRemove: Set<String> = emptySet()
fun annotations(assign: Closure<List<String>>) {
assign.call()
}
@get:Console
var verbose: Boolean = false
@get:Input
var maxPasses: Int = DEFAULT_MAX_PASSES
set(value) {
field = max(value, 1)
}
@get:Input
var preserveTimestamps: Boolean = true
private var _outputDir = project.buildDir.resolve("filtered-libs")
@get:Internal
val outputDir: File get() = _outputDir
fun setOutputDir(d: File?) {
val dir = d ?: return
_outputDir = dir
}
fun outputDir(dir: File?) = setOutputDir(dir)
@get:OutputFiles
val filtered: FileCollection get() = project.files(jars.files.map(this::toFiltered))
private fun toFiltered(source: File) = File(outputDir, source.name.replace(JAR_PATTERN, "-filtered\$1"))
@TaskAction
fun filterJars() {
logger.info("JarFiltering:")
if (forDelete.isNotEmpty()) {
logger.info("- Elements annotated with one of '{}' will be deleted", forDelete.joinToString())
}
if (forStub.isNotEmpty()) {
logger.info("- Methods annotated with one of '{}' will be stubbed out", forStub.joinToString())
}
if (forRemove.isNotEmpty()) {
logger.info("- Annotations '{}' will be removed entirely", forRemove.joinToString())
}
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<String> = 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()
}
}
}
}
}

@ -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<String, List<String>>
) : ClassVisitor(api, visitor) {
private companion object {
/** See [org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind]. */
private const val KOTLIN_CLASS = 1
private const val KOTLIN_FILE = 2
private const val KOTLIN_SYNTHETIC = 3
private const val KOTLIN_MULTIFILE_PART = 5
}
private var classKind: Int = 0
open val hasUnwantedElements: Boolean get() = kotlinMetadata.isNotEmpty()
protected abstract fun transformClassMetadata(d1: List<String>, d2: List<String>): List<String>
protected abstract fun transformPackageMetadata(d1: List<String>, d2: List<String>): List<String>
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<String>, d2: List<String>): List<String> {
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<String> = mutableListOf()
override fun visit(name: String?, value: Any?) {
super.visit(name, value)
data.add(value as String)
}
override fun visitEnd() {
super.visitEnd()
kotlinMetadata[name] = data
logger.debug("-- read @Metadata.{}[{}]", name, data.size)
}
}
}
}

@ -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<ZipEntry>.namesEndingWith(suffix: String): Set<String> {
return filter { it.name.endsWith(suffix) }.map { it.name.dropLast(suffix.length) }.toSet()
}
}
fun ByteArray.fixMetadata(logger: Logger, classNames: Set<String>): ByteArray
= execute({ writer -> MetaFixerVisitor(writer, logger, classNames) })

@ -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<out T : MessageLite>(
private val logger: Logger,
private val actualFields: Collection<FieldElement>,
private val actualMethods: Collection<String>,
private val actualNestedClasses: Collection<String>,
private val actualClasses: Collection<String>,
d1: List<String>,
d2: List<String>,
parser: (InputStream, ExtensionRegistryLite) -> T
) {
private val stringTableTypes: StringTableTypes
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<ProtoBuf.Property>
protected abstract val functions: MutableList<ProtoBuf.Function>
protected abstract val constructors: MutableList<ProtoBuf.Constructor>
protected open val nestedClassNames: MutableList<Int> get() = throw UnsupportedOperationException("No nestedClassNames")
protected open val sealedSubclassNames: MutableList<Int> get() = throw UnsupportedOperationException("No sealedSubclassNames")
init {
val input = ByteArrayInputStream(BitEncoding.decodeBytes(d1.toTypedArray()))
stringTableTypes = StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY)
nameResolver = JvmNameResolver(stringTableTypes, d2.toTypedArray())
message = parser(input, EXTENSION_REGISTRY)
}
abstract fun rebuild(): T
private fun filterNestedClasses(): Int {
if (classKind == null) return 0
var count = 0
var idx = 0
while (idx < nestedClassNames.size) {
val nestedClassName = nameResolver.getString(nestedClassNames[idx])
if (actualNestedClasses.contains(nestedClassName)) {
++idx
} else {
logger.info("-- removing nested class: {}", nestedClassName)
nestedClassNames.removeAt(idx)
++count
}
}
return count
}
private fun filterSealedSubclassNames(): Int {
if (classKind == null) return 0
var count = 0
var idx = 0
while (idx < sealedSubclassNames.size) {
val sealedSubclassName = nameResolver.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<String> {
var count = filterProperties() + filterFunctions() + filterNestedClasses() + filterSealedSubclassNames()
if (classKind != ANNOTATION_CLASS) {
count += filterConstructors()
}
if (count == 0) {
return emptyList()
}
val bytes = ByteArrayOutputStream()
stringTableTypes.writeDelimitedTo(bytes)
rebuild().writeTo(bytes)
return BitEncoding.encodeBytes(bytes.toByteArray()).toList()
}
}
/**
* Aligns a [kotlin.Metadata] annotation containing a [ProtoBuf.Class] object
* in its [d1][kotlin.Metadata.d1] field with the byte-code of its host class.
*/
internal class ClassMetaFixerTransformer(
logger: Logger,
actualFields: Collection<FieldElement>,
actualMethods: Collection<String>,
actualNestedClasses: Collection<String>,
actualClasses: Collection<String>,
d1: List<String>,
d2: List<String>
) : MetaFixerTransformer<ProtoBuf.Class>(
logger,
actualFields,
actualMethods,
actualNestedClasses,
actualClasses,
d1,
d2,
ProtoBuf.Class::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val 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<FieldElement>,
actualMethods: Collection<String>,
d1: List<String>,
d2: List<String>
) : MetaFixerTransformer<ProtoBuf.Package>(
logger,
actualFields,
actualMethods,
emptyList(),
emptyList(),
d1,
d2,
ProtoBuf.Package::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val properties = mutableList(message.propertyList)
override val functions = mutableList(message.functionList)
override val constructors = mutableListOf<ProtoBuf.Constructor>()
override fun rebuild(): ProtoBuf.Package = message.toBuilder().apply {
if (functions.size != functionCount) {
clearFunction().addAllFunction(functions)
}
if (properties.size != propertyCount) {
clearProperty().addAllProperty(properties)
}
}.build()
}

@ -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<String, List<String>>,
private val classNames: Set<String>,
private val fields: MutableSet<FieldElement>,
private val methods: MutableSet<String>,
private val nestedClasses: MutableSet<String>
) : KotlinAwareVisitor(ASM6, visitor, logger, kotlinMetadata), Repeatable<MetaFixerVisitor> {
constructor(visitor: ClassVisitor, logger: Logger, classNames: Set<String>)
: this(visitor, logger, mutableMapOf(), classNames, mutableSetOf(), mutableSetOf(), mutableSetOf())
override fun recreate(visitor: ClassVisitor) = MetaFixerVisitor(visitor, logger, kotlinMetadata, classNames, fields, methods, nestedClasses)
private var className: String = "(unknown)"
override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array<String>?) {
className = clsName
logger.info("Class {}", clsName)
super.visit(version, access, clsName, signature, superName, interfaces)
}
override fun visitField(access: Int, fieldName: String, descriptor: String, signature: String?, value: Any?): FieldVisitor? {
if (fields.add(FieldElement(fieldName, descriptor))) {
logger.info("- field {},{}", fieldName, descriptor)
}
return super.visitField(access, fieldName, descriptor, signature, value)
}
override fun visitMethod(access: Int, methodName: String, descriptor: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
if (methods.add(methodName + descriptor)) {
logger.info("- method {}{}", methodName, descriptor)
}
return super.visitMethod(access, methodName, descriptor, signature, exceptions)
}
override fun visitInnerClass(clsName: String, outerName: String?, innerName: String?, access: Int) {
if (outerName == className && innerName != null && nestedClasses.add(innerName)) {
logger.info("- inner class {}", clsName)
}
return super.visitInnerClass(clsName, outerName, innerName, access)
}
override fun transformClassMetadata(d1: List<String>, d2: List<String>): List<String> {
return ClassMetaFixerTransformer(
logger = logger,
actualFields = fields,
actualMethods = methods,
actualNestedClasses = nestedClasses,
actualClasses = classNames,
d1 = d1,
d2 = d2)
.transform()
}
override fun transformPackageMetadata(d1: List<String>, d2: List<String>): List<String> {
return PackageMetaFixerTransformer(
logger = logger,
actualFields = fields,
actualMethods = methods,
d1 = d1,
d2 = d2)
.transform()
}
}

@ -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<out T : MessageLite>(
private val logger: Logger,
private val deletedFields: Collection<FieldElement>,
private val deletedFunctions: Collection<MethodElement>,
private val deletedConstructors: Collection<MethodElement>,
private val deletedNestedClasses: Collection<String>,
private val deletedClasses: Collection<String>,
private val handleExtraMethod: (MethodElement) -> Unit,
d1: List<String>,
d2: List<String>,
parser: (InputStream, ExtensionRegistryLite) -> T
) {
private val stringTableTypes: StringTableTypes
protected val nameResolver: NameResolver
protected val message: T
protected abstract val typeTable: TypeTable
protected open val className: String get() = throw UnsupportedOperationException("No className")
protected open val nestedClassNames: MutableList<Int> get() = throw UnsupportedOperationException("No nestedClassNames")
protected open val sealedSubclassNames: MutableList<Int> get() = throw UnsupportedOperationException("No sealedSubclassNames")
protected abstract val properties: MutableList<ProtoBuf.Property>
protected abstract val functions: MutableList<ProtoBuf.Function>
protected open val constructors: MutableList<ProtoBuf.Constructor> get() = throw UnsupportedOperationException("No constructors")
protected abstract val typeAliases: MutableList<ProtoBuf.TypeAlias>
init {
val input = ByteArrayInputStream(BitEncoding.decodeBytes(d1.toTypedArray()))
stringTableTypes = StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY)
nameResolver = JvmNameResolver(stringTableTypes, d2.toTypedArray())
message = parser(input, EXTENSION_REGISTRY)
}
abstract fun rebuild(): T
fun transform(): List<String> {
val count = (
filterProperties()
+ filterFunctions()
+ filterConstructors()
+ filterNestedClasses()
+ filterTypeAliases()
+ filterSealedSubclasses()
)
if (count == 0) {
return emptyList()
}
val bytes = ByteArrayOutputStream()
stringTableTypes.writeDelimitedTo(bytes)
rebuild().writeTo(bytes)
return BitEncoding.encodeBytes(bytes.toByteArray()).toList()
}
private fun filterNestedClasses(): Int {
if (deletedNestedClasses.isEmpty()) return 0
var count = 0
var idx = 0
while (idx < nestedClassNames.size) {
val nestedClassName = nameResolver.getString(nestedClassNames[idx])
if (deletedNestedClasses.contains(nestedClassName)) {
logger.info("-- removing nested class: {}", nestedClassName)
nestedClassNames.removeAt(idx)
++count
} else {
++idx
}
}
return count
}
private fun filterConstructors(): Int = deletedConstructors.count(::filterConstructor)
private fun filterConstructor(deleted: MethodElement): Boolean {
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<FieldElement>,
deletedFunctions: Collection<MethodElement>,
deletedConstructors: Collection<MethodElement>,
deletedNestedClasses: Collection<String>,
deletedClasses: Collection<String>,
handleExtraMethod: (MethodElement) -> Unit,
d1: List<String>,
d2: List<String>
) : MetadataTransformer<ProtoBuf.Class>(
logger,
deletedFields,
deletedFunctions,
deletedConstructors,
deletedNestedClasses,
deletedClasses,
handleExtraMethod,
d1,
d2,
ProtoBuf.Class::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val className = nameResolver.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<FieldElement>,
deletedFunctions: Collection<MethodElement>,
handleExtraMethod: (MethodElement) -> Unit,
d1: List<String>,
d2: List<String>
) : MetadataTransformer<ProtoBuf.Package>(
logger,
deletedFields,
deletedFunctions,
emptyList(),
emptyList(),
emptyList(),
handleExtraMethod,
d1,
d2,
ProtoBuf.Package::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val properties = mutableList(message.propertyList)
override val functions = mutableList(message.functionList)
override val typeAliases = mutableList(message.typeAliasList)
override fun rebuild(): ProtoBuf.Package = message.toBuilder().apply {
if (functions.size != functionCount) {
clearFunction().addAllFunction(functions)
}
if (properties.size != propertyCount) {
clearProperty().addAllProperty(properties)
}
if (typeAliases.size != typeAliasCount) {
clearTypeAlias().addAllTypeAlias(typeAliases)
}
}.build()
}

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

@ -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 <T : Any> mutableList(c: Collection<T>): MutableList<T> = ArrayList(c)
/**
* Converts Java class names to Java descriptors.
*/
internal fun toDescriptors(classNames: Iterable<String>): Set<String> {
return classNames.map(String::descriptor).toSet()
}
internal val String.toPathFormat: String get() = replace('.', '/')
internal val String.descriptor: String get() = "L$toPathFormat;"
/**
* Performs the given number of passes of the repeatable visitor over the byte-code.
* Used by [MetaFixerVisitor], but also by some of the test visitors.
*/
internal fun <T> ByteArray.execute(visitor: (ClassVisitor) -> T, flags: Int = 0, passes: Int = 2): ByteArray
where T : ClassVisitor,
T : Repeatable<T> {
var 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()
}

@ -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<Any>(FUNCTION_CLASS).apply {
getMethod("toDelete", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
assertThat("toDelete(J) not found", kotlin.declaredFunctions, hasItem(longFunction))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod("toDelete", Long::class.java) }
assertThat("toDelete(J) still exists", kotlin.declaredFunctions, not(hasItem(longFunction)))
}
}
}
@Test
fun cannotStubAbstractFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("toStubOut", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("toStubOut", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
}
}
}
}

@ -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<HasStringVal>(VAL_PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.stringVal)
}
assertThat("stringVal not found", kotlin.declaredMemberProperties, hasItem(stringVal))
assertThat("getStringVal() not found", kotlin.javaDeclaredMethods, hasItem(getStringVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasStringVal>(VAL_PROPERTY_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
assertThat("stringVal still exists", kotlin.declaredMemberProperties, not(hasItem(stringVal)))
assertThat("getStringVal() still exists", kotlin.javaDeclaredMethods, not(hasItem(getStringVal)))
}
}
}
@Test
fun deleteVarProperty() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasLongVar>(VAR_PROPERTY_CLASS).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { obj ->
assertEquals(BIG_NUMBER, obj.longVar)
}
assertThat("longVar not found", kotlin.declaredMemberProperties, hasItem(longVar))
assertThat("getLongVar() not found", kotlin.javaDeclaredMethods, hasItem(getLongVar))
assertThat("setLongVar() not found", kotlin.javaDeclaredMethods, hasItem(setLongVar))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLongVar>(VAR_PROPERTY_CLASS).apply {
assertNotNull(getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER))
assertThat("longVar still exists", kotlin.declaredMemberProperties, not(hasItem(longVar)))
assertThat("getLongVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(getLongVar)))
assertThat("setLongVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(setLongVar)))
}
}
}
@Test
fun deletedFunctionInsideStubbed() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(DELETED_FUN_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.stringData())
assertEquals(MESSAGE, (obj as HasUnwantedFun).unwantedFun(MESSAGE))
}
assertThat("unwantedFun not found", kotlin.declaredMemberFunctions, hasItem(unwantedFun))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(DELETED_FUN_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertFailsWith<UnsupportedOperationException> { obj.stringData() }.also { ex ->
assertThat(ex).hasMessage("Method has been deleted")
}
assertFailsWith<AbstractMethodError> { (obj as HasUnwantedFun).unwantedFun(MESSAGE) }
}
assertThat("unwantedFun still exists", kotlin.declaredMemberFunctions, not(hasItem(unwantedFun)))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
}
}
}
@Test
fun deletedVarInsideStubbed() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(DELETED_VAR_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.stringData())
(obj as HasUnwantedVar).also {
assertEquals(DEFAULT_MESSAGE, it.unwantedVar)
it.unwantedVar = MESSAGE
assertEquals(MESSAGE, it.unwantedVar)
}
}
assertThat("unwantedVar not found", kotlin.declaredMemberProperties, hasItem(unwantedVar))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(DELETED_VAR_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
assertThat("unwantedVar still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
assertThat("getUnwantedVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVar)))
assertThat("setUnwantedVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(setUnwantedVar)))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
assertThat("stringData() not found", kotlin.javaDeclaredMethods, hasItem(stringDataJava))
}
}
}
@Test
fun deletedValInsideStubbed() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(DELETED_VAL_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.stringData())
assertEquals(MESSAGE, (obj as HasUnwantedVal).unwantedVal)
}
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(DELETED_VAL_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
assertThat("getUnwantedVal() still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
assertThat("stringData() not found", kotlin.javaDeclaredMethods, hasItem(stringDataJava))
}
}
}
@Test
fun deletePackageWithStubbed() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(DELETED_PKG_CLASS).apply {
getDeclaredMethod("stubbed", String::class.java).also { method ->
assertEquals("[$MESSAGE]", method.invoke(null, MESSAGE))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
assertFailsWith<ClassNotFoundException> { cl.load<Any>(DELETED_PKG_CLASS) }
}
}
}

@ -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<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
assertEquals(BIG_NUMBER, it.longData())
}
assertThat("<init>(J) not found", kotlin.constructors, hasItem(longConstructor))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(Long::class.java) }
assertThat("<init>(J) still exists", kotlin.constructors, not(hasItem(longConstructor)))
assertNotNull("primary constructor missing", kotlin.primaryConstructor)
}
}
}
@Test
fun deleteConstructorWithStringParameter() {
val stringConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, hasParam(String::class))
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertEquals(MESSAGE, it.stringData())
}
assertThat("<init>(String) not found", kotlin.constructors, hasItem(stringConstructor))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(String::class.java) }
assertThat("<init>(String) still exists", kotlin.constructors, not(hasItem(stringConstructor)))
assertNotNull("primary constructor missing", kotlin.primaryConstructor)
}
}
}
@Test
fun showUnannotatedConstructorIsUnaffected() {
val intConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, hasParam(Int::class))
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasAll>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
assertEquals(NUMBER, it.intData())
assertEquals(NUMBER.toLong(), it.longData())
assertEquals("<nothing>", it.stringData())
}
assertThat("<init>(Int) not found", kotlin.constructors, hasItem(intConstructor))
assertNotNull("primary constructor missing", kotlin.primaryConstructor)
}
}
}
@Test
fun deletePrimaryConstructorWithStringParameter() {
val stringConstructor = isConstructor(STRING_PRIMARY_CONSTRUCTOR_CLASS, hasParam(String::class))
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertEquals(MESSAGE, it.stringData())
}
assertThat("<init>(String) not found", kotlin.constructors, hasItem(stringConstructor))
assertThat("primary constructor missing", kotlin.primaryConstructor!!, stringConstructor)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(String::class.java) }
assertThat("<init>(String) still exists", kotlin.constructors, not(hasItem(stringConstructor)))
assertNull("primary constructor still exists", kotlin.primaryConstructor)
}
}
}
@Test
fun deletePrimaryConstructorWithLongParameter() {
val longConstructor = isConstructor(LONG_PRIMARY_CONSTRUCTOR_CLASS, hasParam(Long::class))
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
assertEquals(BIG_NUMBER, it.longData())
}
assertThat("<init>(J) not found", kotlin.constructors, hasItem(longConstructor))
assertThat("primary constructor missing", kotlin.primaryConstructor!!, longConstructor)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(Long::class.java) }
assertThat("<init>(J) still exists", kotlin.constructors, not(hasItem(longConstructor)))
assertNull("primary constructor still exists", kotlin.primaryConstructor)
}
}
}
@Test
fun deletePrimaryConstructorWithIntParameter() {
val intConstructor = isConstructor(INT_PRIMARY_CONSTRUCTOR_CLASS, hasParam(Int::class))
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
assertEquals(NUMBER, it.intData())
}
assertThat("<init>(I) not found", kotlin.constructors, hasItem(intConstructor))
assertThat("primary constructor missing", kotlin.primaryConstructor!!, intConstructor)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(Int::class.java) }
assertThat("<init>(I) still exists", kotlin.constructors, not(hasItem(intConstructor)))
assertNull("primary constructor still exists", kotlin.primaryConstructor)
}
}
}
}

@ -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<HasUnwantedVal>(PROPERTY_CLASS).apply {
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
assertThat("List.unwantedVal not found", kotlin.declaredMemberExtensionProperties, hasItem(unwantedVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
assertThat("List.unwantedVal still exists", kotlin.declaredMemberExtensionProperties, not(hasItem(unwantedVal)))
}
}
}
}

@ -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<Any>(STRING_FIELD_CLASS).apply {
val obj: Any = getDeclaredConstructor(String::class.java).newInstance(MESSAGE)
getDeclaredField("stringField").also { field ->
assertEquals(MESSAGE, field.get(obj))
}
assertThat("stringField not found", kotlin.declaredMemberProperties, hasItem(stringField))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(STRING_FIELD_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
assertFailsWith<NoSuchFieldException> { getDeclaredField("stringField") }
assertThat("stringField still exists", kotlin.declaredMemberProperties, not(hasItem(stringField)))
}
}
}
@Test
fun deleteLongField() {
val longField = isProperty("longField", Long::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(LONG_FIELD_CLASS).apply {
val obj: Any = getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
getDeclaredField("longField").also { field ->
assertEquals(BIG_NUMBER, field.get(obj))
}
assertThat("longField not found", kotlin.declaredMemberProperties, hasItem(longField))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(LONG_FIELD_CLASS).apply {
assertNotNull(getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER))
assertFailsWith<NoSuchFieldException> { getDeclaredField("longField") }
assertThat("longField still exists", kotlin.declaredMemberProperties, not(hasItem(longField)))
}
}
}
@Test
fun deleteIntegerField() {
val intField = isProperty("intField", Int::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(INTEGER_FIELD_CLASS).apply {
val obj: Any = getDeclaredConstructor(Int::class.java).newInstance(NUMBER)
getDeclaredField("intField").also { field ->
assertEquals(NUMBER, field.get(obj))
}
assertThat("intField not found", kotlin.declaredMemberProperties, hasItem(intField))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(INTEGER_FIELD_CLASS).apply {
assertNotNull(getDeclaredConstructor(Int::class.java).newInstance(NUMBER))
assertFailsWith<NoSuchFieldException> { getDeclaredField("intField") }
assertThat("intField still exists", kotlin.declaredMemberProperties, not(hasItem(intField)))
}
}
}
}

@ -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<HasUnwantedFun>(FUNCTION_CLASS).apply {
newInstance().also {
assertEquals(MESSAGE, it.unwantedFun(MESSAGE))
}
assertThat("unwantedFun(String) not found", kotlin.declaredFunctions, hasItem(unwantedFun))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
newInstance().also {
assertFailsWith<AbstractMethodError> { it.unwantedFun(MESSAGE) }
}
assertThat("unwantedFun(String) still exists", kotlin.declaredFunctions, not(hasItem(unwantedFun)))
}
}
}
@Test
fun deleteIndirectFunction() {
val stringData = isFunction("stringData", String::class)
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedFun>(INDIRECT_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertEquals(MESSAGE, it.unwantedFun(MESSAGE))
assertEquals(MESSAGE, (it as HasString).stringData())
}
assertThat("unwantedFun(String) not found", kotlin.declaredFunctions, hasItem(unwantedFun))
assertThat("stringData() not found", kotlin.declaredFunctions, hasItem(stringData))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedFun>(INDIRECT_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
assertFailsWith<AbstractMethodError> { it.unwantedFun(MESSAGE) }
assertFailsWith<AbstractMethodError> { (it as HasString).stringData() }
}
assertThat("unwantedFun(String) still exists", kotlin.declaredFunctions, not(hasItem(unwantedFun)))
assertThat("stringData still exists", kotlin.declaredFunctions, not(hasItem(stringData)))
}
}
}
}

@ -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<String>
private lateinit var filteredClasses: List<String>
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
@BeforeClass
@JvmStatic
fun setup() {
sourceClasses = testProject.sourceJar.getClassNames(LAZY_VAL_CLASS)
filteredClasses = testProject.filteredJar.getClassNames(LAZY_VAL_CLASS)
}
}
@Test
fun deletedClasses() {
assertThat(sourceClasses).contains(LAZY_VAL_CLASS)
assertThat(filteredClasses).containsExactly(LAZY_VAL_CLASS)
}
@Test
fun deleteLazyVal() {
assertThat(sourceClasses).anyMatch { it.contains("\$unwantedVal\$") }
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVal>(LAZY_VAL_CLASS).apply {
getConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVal)
}
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVal>(LAZY_VAL_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getConstructor(String::class.java) }
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
}
}
}
}

@ -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<Any>(MULTIFILE_CLASS).apply {
getMethod(STRING_METHOD, String::class.java).also { method ->
method.invoke(null, MESSAGE).also { result ->
assertThat(result)
.isInstanceOf(String::class.java)
.isEqualTo(MESSAGE)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod(STRING_METHOD, String::class.java) }
}
}
}
@Test
fun deleteLongFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
getMethod(LONG_METHOD, Long::class.java).also { method ->
method.invoke(null, BIG_NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Long::class.javaObjectType)
.isEqualTo(BIG_NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod(LONG_METHOD, Long::class.java) }
}
}
}
@Test
fun deleteIntFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
getMethod(INT_METHOD, Int::class.java).also { method ->
method.invoke(null, NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Int::class.javaObjectType)
.isEqualTo(NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(MULTIFILE_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod(INT_METHOD, Int::class.java) }
}
}
}
}

@ -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<Any>(DELETED_CLASS)
val kept = cl.load<Any>(KEPT_CLASS)
cl.load<Any>(HOST_CLASS).apply {
assertThat(declaredClasses).containsExactlyInAnyOrder(deleted, kept)
assertThat("OneToThrowAway class is missing", kotlin.nestedClasses, hasItem(deletedClass))
assertThat("OneToKeep class is missing", kotlin.nestedClasses, hasItem(keptClass))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
assertFailsWith<ClassNotFoundException> { cl.load<Any>(DELETED_CLASS) }
val kept = cl.load<Any>(KEPT_CLASS)
cl.load<Any>(HOST_CLASS).apply {
assertThat(declaredClasses).containsExactly(kept)
assertThat("OneToThrowAway class still exists", kotlin.nestedClasses, not(hasItem(deletedClass)))
assertThat("OneToKeep class is missing", kotlin.nestedClasses, hasItem(keptClass))
}
}
}
@Test
fun deleteFromSealedClass() {
classLoaderFor(testProject.sourceJar).use { cl ->
val unwanted = cl.load<Any>(UNWANTED_SUBCLASS)
val wanted = cl.load<Any>(WANTED_SUBCLASS)
cl.load<Any>(SEALED_CLASS).apply {
assertTrue(kotlin.isSealed)
assertThat(declaredClasses).containsExactlyInAnyOrder(wanted, unwanted)
assertThat("Wanted class is missing", kotlin.nestedClasses, hasItem(wantedSubclass))
assertThat("Unwanted class is missing", kotlin.nestedClasses, hasItem(unwantedSubclass))
assertThat(classMetadata.sealedSubclasses).containsExactlyInAnyOrder(WANTED_SUBCLASS, UNWANTED_SUBCLASS)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
assertFailsWith<ClassNotFoundException> { cl.load<Any>(UNWANTED_SUBCLASS) }
val wanted = cl.load<Any>(WANTED_SUBCLASS)
cl.load<Any>(SEALED_CLASS).apply {
assertTrue(kotlin.isSealed)
assertThat(declaredClasses).containsExactly(wanted)
assertThat("Unwanted class still exists", kotlin.nestedClasses, not(hasItem(unwantedSubclass)))
assertThat("Wanted class is missing", kotlin.nestedClasses, hasItem(wantedSubclass))
assertThat(classMetadata.sealedSubclasses).containsExactly(WANTED_SUBCLASS)
}
}
}
}

@ -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<String>
private lateinit var filteredClasses: List<String>
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
@BeforeClass
@JvmStatic
fun setup() {
sourceClasses = testProject.sourceJar.getClassNames(OBJECT_CLASS)
filteredClasses = testProject.filteredJar.getClassNames(OBJECT_CLASS)
}
}
@Test
fun deletedClasses() {
assertThat(sourceClasses).contains(OBJECT_CLASS)
assertThat(filteredClasses).containsExactly(OBJECT_CLASS)
}
@Test
fun deleteObject() {
assertThat(sourceClasses).anyMatch { it.contains("\$unwantedObj\$") }
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(OBJECT_CLASS).apply {
getDeclaredMethod(UNWANTED_OBJ_METHOD).also { method ->
(method.invoke(null) as HasUnwantedFun).also { obj ->
assertEquals(MESSAGE, obj.unwantedFun(MESSAGE))
}
}
getDeclaredField(UNWANTED_OBJ_FIELD).also { field ->
assertFalse(field.isAccessible)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(OBJECT_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod(UNWANTED_OBJ_METHOD) }
assertFailsWith<NoSuchFieldException> { getDeclaredField(UNWANTED_OBJ_FIELD) }
}
}
}
@Test
fun deleteFunctionWithObject() {
assertThat(sourceClasses).anyMatch { it.contains("\$unwantedFun\$") }
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(OBJECT_CLASS).apply {
getDeclaredMethod(UNWANTED_FUN_METHOD).also { method ->
assertEquals("<default-value>", method.invoke(null))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(OBJECT_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod(UNWANTED_FUN_METHOD) }
}
}
}
}

@ -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<Any>(WANTED_SUBCLASS)
cl.load<Any>(UNWANTED_SUBCLASS)
cl.load<Any>(SEALED_CLASS).apply {
assertTrue(kotlin.isSealed)
assertThat(classMetadata.sealedSubclasses)
.containsExactlyInAnyOrder(WANTED_SUBCLASS, UNWANTED_SUBCLASS)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(WANTED_SUBCLASS)
assertFailsWith<ClassNotFoundException> { cl.load<Any>(UNWANTED_SUBCLASS) }
cl.load<Any>(SEALED_CLASS).apply {
assertTrue(kotlin.isSealed)
assertThat(classMetadata.sealedSubclasses)
.containsExactly(WANTED_SUBCLASS)
}
}
}
}

@ -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<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredField("stringField")
assertEquals(DEFAULT_MESSAGE, getter.get(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchFieldException> { getDeclaredField("stringField") }
}
}
}
@Test
fun deleteLongField() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredField("longField")
assertEquals(DEFAULT_BIG_NUMBER, getter.get(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchFieldException> { getDeclaredField("longField") }
}
}
}
@Test
fun deleteIntField() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredField("intField")
assertEquals(DEFAULT_NUMBER, getter.get(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchFieldException> { getDeclaredField("intField") }
}
}
}
}

@ -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<Any>(FUNCTION_CLASS).apply {
getMethod("unwantedStringToDelete", String::class.java).also { method ->
method.invoke(null, MESSAGE).also { result ->
assertThat(result)
.isInstanceOf(String::class.java)
.isEqualTo(MESSAGE)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod("unwantedStringToDelete", String::class.java) }
}
}
}
@Test
fun deleteLongFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("unwantedLongToDelete", Long::class.java).also { method ->
method.invoke(null, BIG_NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Long::class.javaObjectType)
.isEqualTo(BIG_NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod("unwantedLongToDelete", Long::class.java) }
}
}
}
@Test
fun deleteIntFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("unwantedIntToDelete", Int::class.java).also { method ->
method.invoke(null, NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Int::class.javaObjectType)
.isEqualTo(NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod("unwantedIntToDelete", Int::class.java) }
}
}
}
}

@ -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<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getStringVal")
assertEquals(DEFAULT_MESSAGE, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getStringVal") }
}
}
}
@Test
fun deleteLongVal() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getLongVal")
assertEquals(DEFAULT_BIG_NUMBER, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getLongVal") }
}
}
}
@Test
fun deleteIntVal() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getIntVal")
assertEquals(DEFAULT_NUMBER, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getIntVal") }
}
}
}
@Test
fun deleteMemberVal() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getMemberVal", Any::class.java)
assertEquals(LocalBlob, getter.invoke(null, LocalBlob))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getMemberVal", Any::class.java) }
}
}
}
}

@ -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<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getStringVar")
val setter = getDeclaredMethod("setStringVar", String::class.java)
assertEquals(DEFAULT_MESSAGE, getter.invoke(null))
setter.invoke(null, MESSAGE)
assertEquals(MESSAGE, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getStringVar") }
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setStringVar", String::class.java) }
}
}
}
@Test
fun deleteLongVar() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getLongVar")
val setter = getDeclaredMethod("setLongVar", Long::class.java)
assertEquals(DEFAULT_BIG_NUMBER, getter.invoke(null))
setter.invoke(null, BIG_NUMBER)
assertEquals(BIG_NUMBER, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getLongVar") }
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setLongVar", Long::class.java) }
}
}
}
@Test
fun deleteIntVar() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getIntVar")
val setter = getDeclaredMethod("setIntVar", Int::class.java)
assertEquals(DEFAULT_NUMBER, getter.invoke(null))
setter.invoke(null, NUMBER)
assertEquals(NUMBER, getter.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getIntVar") }
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setIntVar", Int::class.java) }
}
}
}
@Test
fun deleteMemberVar() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
val getter = getDeclaredMethod("getMemberVar", Any::class.java)
val setter = getDeclaredMethod("setMemberVar", Any::class.java, Any::class.java)
assertEquals(LocalBlob, getter.invoke(null, LocalBlob))
setter.invoke(null, LocalBlob, LocalBlob)
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(PROPERTY_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getMemberVar", Any::class.java) }
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setMemberVar", Any::class.java, Any::class.java) }
}
}
}
}

@ -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<String>
private lateinit var filteredClasses: List<String>
@ClassRule
@JvmField
val rules: TestRule = RuleChain
.outerRule(testProjectDir)
.around(testProject)
@BeforeClass
@JvmStatic
fun setup() {
sourceClasses = testProject.sourceJar.getClassNames(TYPEALIAS_CLASS)
filteredClasses = testProject.filteredJar.getClassNames(TYPEALIAS_CLASS)
}
}
@Test
fun deleteTypeAlias() {
classLoaderFor(testProject.sourceJar).use { cl ->
val metadata = cl.load<Any>(TYPEALIAS_CLASS).fileMetadata
assertThat(metadata.typeAliasNames)
.containsExactlyInAnyOrder("FileWantedType", "FileUnwantedType")
}
classLoaderFor(testProject.filteredJar).use { cl ->
val metadata = cl.load<Any>(TYPEALIAS_CLASS).fileMetadata
assertThat(metadata.typeAliasNames)
.containsExactly("FileWantedType")
}
}
}

@ -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<HasUnwantedVal>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVal)
}
assertFalse(getDeclaredField("unwantedVal").isAccessible)
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertFailsWith<AbstractMethodError> { obj.unwantedVal }
}
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVal") }
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
}
}
}
@Test
fun deleteGetter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVal>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVal)
}
assertFalse(getDeclaredField("unwantedVal").isAccessible)
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVal>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertFailsWith<AbstractMethodError> { obj.unwantedVal }
}
assertFalse(getDeclaredField("unwantedVal").isAccessible)
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
}
}
}
@Test
fun deleteJvmField() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(JVM_FIELD_CLASS).apply {
val obj = getDeclaredConstructor(String::class.java).newInstance(MESSAGE)
getDeclaredField("unwantedVal").also { field ->
assertEquals(MESSAGE, field.get(obj))
}
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(JVM_FIELD_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVal") }
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
}
}
}
}

@ -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<HasUnwantedVar>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
obj.unwantedVar = MESSAGE
assertEquals(MESSAGE, obj.unwantedVar)
}
assertFalse(getDeclaredField("unwantedVar").isAccessible)
assertThat("unwantedVar not found", kotlin.declaredMemberProperties, hasItem(unwantedVar))
assertThat("getUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVar))
assertThat("setUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(setUnwantedVar))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVar>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertFailsWith<AbstractMethodError> { obj.unwantedVar }
assertFailsWith<AbstractMethodError> { obj.unwantedVar = MESSAGE }
}
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVar") }
assertThat("unwantedVar still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
assertThat("getUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVar)))
assertThat("setUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(setUnwantedVar)))
}
}
}
@Test
fun deleteGetter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVar)
}
assertFalse(getDeclaredField("unwantedVar").isAccessible)
assertThat("getUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVar))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertFailsWith<AbstractMethodError> { obj.unwantedVar }
}
assertFalse(getDeclaredField("unwantedVar").isAccessible)
assertThat("getUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVar)))
}
}
}
@Test
fun deleteSetter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
obj.unwantedVar = MESSAGE
assertEquals(MESSAGE, obj.unwantedVar)
}
getDeclaredField("unwantedVar").also { field ->
assertFalse(field.isAccessible)
}
assertThat("setUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(setUnwantedVar))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
assertFailsWith<AbstractMethodError> { obj.unwantedVar = MESSAGE }
}
getDeclaredField("unwantedVar").also { field ->
assertFalse(field.isAccessible)
}
assertThat("setUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(setUnwantedVar)))
}
}
}
@Test
fun deleteJvmField() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(JVM_FIELD_CLASS).apply {
val obj: Any = getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE)
getDeclaredField("unwantedVar").also { field ->
assertEquals(DEFAULT_MESSAGE, field.get(obj))
field.set(obj, MESSAGE)
assertEquals(MESSAGE, field.get(obj))
}
assertThat("unwantedVar not found", kotlin.declaredMemberProperties, hasItem(unwantedVar))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(JVM_FIELD_CLASS).apply {
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE))
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVar") }
assertThat("unwantedVar still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
}
}
}
}

@ -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()
}
}
}
}

@ -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

@ -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)
}
}

@ -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 <reified T: R, reified R: Any> transform(): Class<out R> = transform(T::class.java, R::class.java)
private fun <T: R, R: Any> transform(type: Class<in T>, asType: Class<out R>): Class<out R> {
val bytecode = type.bytecode.execute({ writer ->
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<SampleGenericField<String>, HasGenericField<String>>()
.getDeclaredConstructor(Any::class.java).newInstance(MESSAGE)
assertFailsWith<AbstractMethodError> { targetField.objectField }
assertFailsWith<AbstractMethodError> { targetField.objectField = "New Value" }
assertThat("objectField still exists", targetField::class.declaredMemberProperties, not(hasItem(objectField)))
}
@Test
fun removeLong() {
val sourceField = SampleLongField(BIG_NUMBER)
assertEquals(BIG_NUMBER, sourceField.longField)
assertThat("longField not found", sourceField::class.declaredMemberProperties, hasItem(longField))
val targetField = transform<SampleLongField, HasLongField>()
.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
assertFailsWith<AbstractMethodError> { targetField.longField }
assertFailsWith<AbstractMethodError> { targetField.longField = 10L }
assertThat("longField still exists", targetField::class.declaredMemberProperties, not(hasItem(longField)))
}
@Test
fun removeInt() {
val sourceField = SampleIntField(NUMBER)
assertEquals(NUMBER, sourceField.intField)
assertThat("intField not found", sourceField::class.declaredMemberProperties, hasItem(intField))
val targetField = transform<SampleIntField, HasIntField>()
.getDeclaredConstructor(Int::class.java).newInstance(NUMBER)
assertFailsWith<AbstractMethodError> { targetField.intField }
assertFailsWith<AbstractMethodError> { targetField.intField = 100 }
assertThat("intField still exists", targetField::class.declaredMemberProperties, not(hasItem(intField)))
}
@Test
fun removeShort() {
val sourceField = SampleShortField(SHORT_NUMBER)
assertEquals(SHORT_NUMBER, sourceField.shortField)
assertThat("shortField not found", sourceField::class.declaredMemberProperties, hasItem(shortField))
val targetField = transform<SampleShortField, HasShortField>()
.getDeclaredConstructor(Short::class.java).newInstance(SHORT_NUMBER)
assertFailsWith<AbstractMethodError> { targetField.shortField }
assertFailsWith<AbstractMethodError> { targetField.shortField = 15 }
assertThat("shortField still exists", targetField::class.declaredMemberProperties, not(hasItem(shortField)))
}
@Test
fun removeByte() {
val sourceField = SampleByteField(BYTE_NUMBER)
assertEquals(BYTE_NUMBER, sourceField.byteField)
assertThat("byteField not found", sourceField::class.declaredMemberProperties, hasItem(byteField))
val targetField = transform<SampleByteField, HasByteField>()
.getDeclaredConstructor(Byte::class.java).newInstance(BYTE_NUMBER)
assertFailsWith<AbstractMethodError> { targetField.byteField }
assertFailsWith<AbstractMethodError> { targetField.byteField = 16 }
assertThat("byteField still exists", targetField::class.declaredMemberProperties, not(hasItem(byteField)))
}
@Test
fun removeBoolean() {
val sourceField = SampleBooleanField(true)
assertTrue(sourceField.booleanField)
assertThat("booleanField not found", sourceField::class.declaredMemberProperties, hasItem(booleanField))
val targetField = transform<SampleBooleanField, HasBooleanField>()
.getDeclaredConstructor(Boolean::class.java).newInstance(true)
assertFailsWith<AbstractMethodError> { targetField.booleanField }
assertFailsWith<AbstractMethodError> { targetField.booleanField = false }
assertThat("booleanField still exists", targetField::class.declaredMemberProperties, not(hasItem(booleanField)))
}
@Test
fun removeChar() {
val sourceField = SampleCharField('?')
assertEquals('?', sourceField.charField)
assertThat("charField not found", sourceField::class.declaredMemberProperties, hasItem(charField))
val targetField = transform<SampleCharField, HasCharField>()
.getDeclaredConstructor(Char::class.java).newInstance('?')
assertFailsWith<AbstractMethodError> { targetField.charField }
assertFailsWith<AbstractMethodError> { targetField.charField = 'A' }
assertThat("charField still exists", targetField::class.declaredMemberProperties, not(hasItem(charField)))
}
@Test
fun removeDouble() {
val sourceField = SampleDoubleField(BIG_FLOATING_POINT)
assertEquals(BIG_FLOATING_POINT, sourceField.doubleField)
assertThat("doubleField not found", sourceField::class.declaredMemberProperties, hasItem(doubleField))
val targetField = transform<SampleDoubleField, HasDoubleField>()
.getDeclaredConstructor(Double::class.java).newInstance(BIG_FLOATING_POINT)
assertFailsWith<AbstractMethodError> { targetField.doubleField }
assertFailsWith<AbstractMethodError> { targetField.doubleField = 12345.678 }
assertThat("doubleField still exists", targetField::class.declaredMemberProperties, not(hasItem(doubleField)))
}
@Test
fun removeFloat() {
val sourceField = SampleFloatField(FLOATING_POINT)
assertEquals(FLOATING_POINT, sourceField.floatField)
assertThat("floatField not found", sourceField::class.declaredMemberProperties, hasItem(floatField))
val targetField = transform<SampleFloatField, HasFloatField>()
.getDeclaredConstructor(Float::class.java).newInstance(FLOATING_POINT)
assertFailsWith<AbstractMethodError> { targetField.floatField }
assertFailsWith<AbstractMethodError> { targetField.floatField = 123.45f }
assertThat("floatField still exists", targetField::class.declaredMemberProperties, not(hasItem(floatField)))
}
@Test
fun removeArray() {
val sourceField = SampleArrayField(byteArrayOf())
assertArrayEquals(byteArrayOf(), sourceField.arrayField)
assertThat("arrayField not found", sourceField::class.declaredMemberProperties, hasItem(arrayField))
val targetField = transform<SampleArrayField, HasArrayField>()
.getDeclaredConstructor(ByteArray::class.java).newInstance(byteArrayOf())
assertFailsWith<AbstractMethodError> { targetField.arrayField }
assertFailsWith<AbstractMethodError> { targetField.arrayField = byteArrayOf(0x35, 0x73) }
assertThat("arrayField still exists", targetField::class.declaredMemberProperties, not(hasItem(arrayField)))
}
}
interface HasGenericField<T> { var objectField: T }
interface HasLongField { var longField: Long }
interface HasIntField { var intField: Int }
interface HasShortField { var shortField: Short }
interface HasByteField { var byteField: Byte }
interface HasBooleanField { var booleanField: Boolean }
interface HasCharField { var charField: Char }
interface HasFloatField { var floatField: Float }
interface HasDoubleField { var doubleField: Double }
interface HasArrayField { var arrayField: ByteArray }
internal class SampleGenericField<T>(@Deletable override var objectField: T) : HasGenericField<T>
internal class SampleLongField(@Deletable override var longField: Long) : HasLongField
internal class SampleIntField(@Deletable override var intField: Int) : HasIntField
internal class SampleShortField(@Deletable override var shortField: Short) : HasShortField
internal class SampleByteField(@Deletable override var byteField: Byte) : HasByteField
internal class SampleBooleanField(@Deletable override var booleanField: Boolean) : HasBooleanField
internal class SampleCharField(@Deletable override var charField: Char) : HasCharField
internal class SampleFloatField(@Deletable override var floatField: Float) : HasFloatField
internal class SampleDoubleField(@Deletable override var doubleField: Double) : HasDoubleField
internal class SampleArrayField(@Deletable override var arrayField: ByteArray) : HasArrayField

@ -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<Any>(FUNCTION_CLASS).apply {
getMethod("toDelete", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
assertFailsWith<NoSuchMethodException> { getMethod("toDelete", Long::class.java) }
}
}
}
@Test
fun cannotStubInterfaceFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("toStubOut", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getMethod("toStubOut", Long::class.java).also { method ->
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
}
}
}
}
}

@ -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")
}
}

@ -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()
}
}
}
}

@ -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)
}
}

@ -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("<init>() not found", sourceClass.kotlin.constructors, hasItem(defaultCon))
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = sourceClass.bytecode.fixMetadata(logger, pathsOf(SimpleAnnotation::class))
.toClass<SimpleAnnotation, Any>()
assertThat("<init>() not found", fixedClass.kotlin.constructors, hasItem(defaultCon))
}
@Test
fun testAnnotationWithValue() {
val sourceClass = AnnotationWithValue::class.java
assertThat("<init>(String) not found", sourceClass.kotlin.constructors, hasItem(valueCon))
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = sourceClass.bytecode.fixMetadata(logger, pathsOf(AnnotationWithValue::class))
.toClass<AnnotationWithValue, Any>()
assertThat("<init>(String) not found", fixedClass.kotlin.constructors, hasItem(valueCon))
}
}
annotation class AnnotationWithValue(val str: String)
annotation class SimpleAnnotation

@ -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")
}
}

@ -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<WithConstructor, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithConstructor, HasLong>()
// Check that the unwanted constructor has been successfully
// added to the metadata, and that the class is valid.
val sourceObj = sourceClass.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
assertEquals(BIG_NUMBER, sourceObj.longData())
assertThat("<init>(Int,Long) not found", sourceClass.kotlin.constructors, hasItem(unwantedCon))
assertThat("<init>(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<WithConstructor, HasLong>()
val fixedObj = fixedClass.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
assertEquals(BIG_NUMBER, fixedObj.longData())
assertThat("<init>(Int,Long) still exists", fixedClass.kotlin.constructors, not(hasItem(unwantedCon)))
assertThat("<init>(Long) not found", fixedClass.kotlin.constructors, hasItem(wantedCon))
}
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
}

@ -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<WithFunction, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithFunction, HasLong>()
// Check that the unwanted function has been successfully
// added to the metadata, and that the class is valid.
val sourceObj = sourceClass.newInstance()
assertEquals(BIG_NUMBER, sourceObj.longData())
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<WithFunction, HasLong>()
val fixedObj = fixedClass.newInstance()
assertEquals(BIG_NUMBER, fixedObj.longData())
assertThat("unwantedFun(String) still exists", fixedClass.kotlin.declaredFunctions, not(hasItem(unwantedFun)))
assertThat("longData not found", fixedClass.kotlin.declaredFunctions, hasItem(longData))
}
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
}

@ -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<WithNestedClass, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithNestedClass, Any>()
assertThat(sourceClass.classMetadata.nestedClasses).containsExactlyInAnyOrder(WANTED_CLASS, UNWANTED_CLASS)
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithNestedClass::class, WithNestedClass.Wanted::class))
.toClass<WithNestedClass, Any>()
assertThat(fixedClass.classMetadata.nestedClasses).containsExactly(WANTED_CLASS)
}
@Test
fun testAllNestedClassesRemovedFromMetadata() {
val bytecode = recodeMetadataFor<WithoutNestedClass, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithoutNestedClass, Any>()
assertThat(sourceClass.classMetadata.nestedClasses)
.containsExactlyInAnyOrder("${WithoutNestedClass::class.jvmName}\$Wanted", "${WithoutNestedClass::class.jvmName}\$Unwanted")
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithoutNestedClass::class))
.toClass<WithoutNestedClass, Any>()
assertThat(fixedClass.classMetadata.nestedClasses).isEmpty()
}
@Suppress("UNUSED")
class MetadataTemplate {
class Wanted
class Unwanted
}
}
class WithNestedClass {
class Wanted
}
class WithoutNestedClass

@ -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<out Any>
private lateinit var fixedClass: Class<out Any>
@BeforeClass
@JvmStatic
fun setup() {
val emptyClass = Class.forName(EMPTY_CLASS)
val bytecode = emptyClass.metadataAs(Class.forName(TEMPLATE_CLASS))
sourceClass = bytecode.toClass(emptyClass, Any::class.java)
fixedClass = bytecode.fixMetadata(logger, setOf(EMPTY_CLASS)).toClass(sourceClass, Any::class.java)
}
}
@Test
fun testPackageFunction() {
assertFailsWith<UnsupportedOperationException> { 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<UnsupportedOperationException> { 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<UnsupportedOperationException> { 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

@ -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()
}
}
}
}

@ -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<MetaSealedClass, MetadataTemplate>()
val sourceClass = bytecode.toClass<MetaSealedClass, Any>()
assertThat(sourceClass.classMetadata.sealedSubclasses).containsExactlyInAnyOrder(UNWANTED_CLASS, WANTED_CLASS)
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(MetaSealedClass::class, MetaSealedClass.Wanted::class))
.toClass<MetaSealedClass, Any>()
assertThat(fixedClass.classMetadata.sealedSubclasses).containsExactly(WANTED_CLASS)
}
@Suppress("UNUSED")
sealed class MetadataTemplate {
class Wanted : MetadataTemplate()
class Unwanted : MetadataTemplate()
}
}
sealed class MetaSealedClass {
class Wanted : MetaSealedClass()
}

@ -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)
}
}

@ -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<WithValProperty, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithValProperty, HasIntVal>()
// Check that the unwanted property has been successfully
// added to the metadata, and that the class is valid.
val sourceObj = sourceClass.newInstance()
assertEquals(NUMBER, sourceObj.intVal)
assertThat("unwantedVal not found", sourceClass.kotlin.declaredMemberProperties, hasItem(unwantedVal))
assertThat("intVal not found", sourceClass.kotlin.declaredMemberProperties, hasItem(intVal))
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithValProperty::class)).toClass<WithValProperty, HasIntVal>()
val fixedObj = fixedClass.newInstance()
assertEquals(NUMBER, fixedObj.intVal)
assertThat("unwantedVal still exists", fixedClass.kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
assertThat("intVal not found", fixedClass.kotlin.declaredMemberProperties, hasItem(intVal))
}
class MetadataTemplate : HasIntVal {
override val intVal: Int = 0
@Suppress("UNUSED") val unwantedVal: String = "UNWANTED"
}
}
class WithValProperty : HasIntVal {
override val intVal: Int = NUMBER
}

@ -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<WithVarProperty, MetadataTemplate>()
val sourceClass = bytecode.toClass<WithVarProperty, HasIntVar>()
// Check that the unwanted property has been successfully
// added to the metadata, and that the class is valid.
val sourceObj = sourceClass.newInstance()
assertEquals(NUMBER, sourceObj.intVar)
assertThat("unwantedVar not found", sourceClass.kotlin.declaredMemberProperties, hasItem(unwantedVar))
assertThat("intVar not found", sourceClass.kotlin.declaredMemberProperties, hasItem(intVar))
// Rewrite the metadata according to the contents of the bytecode.
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithVarProperty::class)).toClass<WithVarProperty, HasIntVar>()
val fixedObj = fixedClass.newInstance()
assertEquals(NUMBER, fixedObj.intVar)
assertThat("unwantedVar still exists", fixedClass.kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
assertThat("intVar not found", fixedClass.kotlin.declaredMemberProperties, hasItem(intVar))
}
class MetadataTemplate : HasIntVar {
override var intVar: Int = 0
@Suppress("UNUSED") var unwantedVar: String = "UNWANTED"
}
}
class WithVarProperty : HasIntVar {
override var intVar: Int = NUMBER
}

@ -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)
}
}

@ -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<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
assertNotNull(getAnnotation(removeMe))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
assertNull(getAnnotation(removeMe))
}
}
}
@Test
fun deleteFromDefaultConstructor() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getDeclaredConstructor().also { con ->
assertNotNull(con.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getDeclaredConstructor().also { con ->
assertNull(con.getAnnotation(removeMe))
}
}
}
}
@Test
fun deleteFromPrimaryConstructor() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getDeclaredConstructor(Long::class.java, String::class.java).also { con ->
assertNotNull(con.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getDeclaredConstructor(Long::class.java, String::class.java).also { con ->
assertNull(con.getAnnotation(removeMe))
}
}
}
}
@Test
fun deleteFromField() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getField("longField").also { field ->
assertNotNull(field.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<Any>(ANNOTATED_CLASS).apply {
getField("longField").also { field ->
assertNull(field.getAnnotation(removeMe))
}
}
}
}
@Test
fun deleteFromMethod() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedFun>(ANNOTATED_CLASS).apply {
getMethod("unwantedFun", String::class.java).also { method ->
assertNotNull(method.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedFun>(ANNOTATED_CLASS).apply {
getMethod("unwantedFun", String::class.java).also { method ->
assertNull(method.getAnnotation(removeMe))
}
}
}
}
@Test
fun deleteFromValProperty() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedVal>(ANNOTATED_CLASS).apply {
getMethod("getUnwantedVal").also { method ->
assertNotNull(method.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedVal>(ANNOTATED_CLASS).apply {
getMethod("getUnwantedVal").also { method ->
assertNull(method.getAnnotation(removeMe))
}
}
}
}
@Test
fun deleteFromVarProperty() {
classLoaderFor(testProject.sourceJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedVar>(ANNOTATED_CLASS).apply {
getMethod("getUnwantedVar").also { method ->
assertNotNull(method.getAnnotation(removeMe))
}
getMethod("setUnwantedVar", String::class.java).also { method ->
assertNotNull(method.getAnnotation(removeMe))
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
cl.load<HasUnwantedVar>(ANNOTATED_CLASS).apply {
getMethod("getUnwantedVar").also { method ->
assertNull(method.getAnnotation(removeMe))
}
getMethod("setUnwantedVar", String::class.java).also { method ->
assertNull(method.getAnnotation(removeMe))
}
}
}
}
}

@ -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 <clinit> block.
* Show that deleting some field references doesn't break the other
* properties' initialisation code.
*/
class StaticFieldRemovalTest {
companion object {
private val logger: Logger = StdOutLogging(StaticFieldRemovalTest::class)
private const val FIELD_CLASS = "net.corda.gradle.jarfilter.StaticFields"
private lateinit var sourceClass: Class<out Any>
private lateinit var targetClass: Class<out Any>
private fun <T : R, R : Any> transform(type: Class<in T>, asType: Class<out R>): Class<out R> {
val bytecode = type.bytecode.execute({ writer ->
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<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticString") }
}
@Test
fun deleteStaticLong() {
assertEquals(2L, sourceClass.getDeclaredMethod("getStaticLong").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticLong") }
}
@Test
fun deleteStaticInt() {
assertEquals(3, sourceClass.getDeclaredMethod("getStaticInt").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticInt") }
}
@Test
fun deleteStaticShort() {
assertEquals(4.toShort(), sourceClass.getDeclaredMethod("getStaticShort").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticShort") }
}
@Test
fun deleteStaticByte() {
assertEquals(5.toByte(), sourceClass.getDeclaredMethod("getStaticByte").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticByte") }
}
@Test
fun deleteStaticChar() {
assertEquals(6.toChar(), sourceClass.getDeclaredMethod("getStaticChar").invoke(null))
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticChar") }
}
@Test
fun checkSeedHasBeenIncremented() {
assertEquals(6, sourceClass.getDeclaredMethod("getStaticSeed").invoke(null))
assertEquals(6, targetClass.getDeclaredMethod("getStaticSeed").invoke(null))
}
}
private var seed: Int = 0
val staticSeed get() = seed
@Deletable val staticString: String = (++seed).toString()
@Deletable val staticLong: Long = (++seed).toLong()
@Deletable val staticInt: Int = ++seed
@Deletable val staticShort: Short = (++seed).toShort()
@Deletable val staticByte: Byte = (++seed).toByte()
@Deletable val staticChar: Char = (++seed).toChar()

@ -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)
}
}

@ -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<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { obj ->
assertEquals(BIG_NUMBER, obj.longData())
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).also {
assertFailsWith<InvocationTargetException> { it.newInstance(BIG_NUMBER) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubConstructorWithStringParameter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.stringData())
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).also {
assertFailsWith<InvocationTargetException> { it.newInstance(MESSAGE) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun showUnannotatedConstructorIsUnaffected() {
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasAll>(SECONDARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { obj ->
assertEquals(NUMBER, obj.intData())
assertEquals(NUMBER.toLong(), obj.longData())
assertEquals("<nothing>", obj.stringData())
}
}
}
}
@Test
fun stubPrimaryConstructorWithStringParameter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.stringData())
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(String::class.java).also {
assertFailsWith<InvocationTargetException> { it.newInstance(MESSAGE) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubPrimaryConstructorWithLongParameter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { obj ->
assertEquals(BIG_NUMBER, obj.longData())
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Long::class.java).also {
assertFailsWith<InvocationTargetException> { it.newInstance(BIG_NUMBER) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubPrimaryConstructorWithIntParameter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { obj ->
assertEquals(NUMBER, obj.intData())
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
getDeclaredConstructor(Int::class.java).apply {
val error = assertFailsWith<InvocationTargetException> { newInstance(NUMBER) }.targetException
assertThat(error)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}

@ -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<Annotation>(STUB_ME_OUT_ANNOTATION)
val parameter = cl.load<Annotation>(PARAMETER_ANNOTATION)
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
newInstance().also { obj ->
assertEquals(MESSAGE, obj.unwantedFun(MESSAGE))
}
getMethod("unwantedFun", String::class.java).also { method ->
assertTrue("StubMeOut annotation missing", method.isAnnotationPresent (stubMeOut))
assertTrue("Resource annotation missing", method.isAnnotationPresent(Resource::class.java))
method.parameterAnnotations.also { paramAnns ->
assertEquals(1, paramAnns.size)
assertThat(paramAnns[0])
.hasOnlyOneElementSatisfying { a -> a.javaClass.isInstance(parameter) }
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
val stubMeOut = cl.load<Annotation>(STUB_ME_OUT_ANNOTATION)
val parameter = cl.load<Annotation>(PARAMETER_ANNOTATION)
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
newInstance().also { obj ->
assertFailsWith<UnsupportedOperationException> { obj.unwantedFun(MESSAGE) }.also { ex ->
assertEquals("Method has been deleted", ex.message)
}
}
getMethod("unwantedFun", String::class.java).also { method ->
assertFalse("StubMeOut annotation present", method.isAnnotationPresent(stubMeOut))
assertTrue("Resource annotation missing", method.isAnnotationPresent(Resource::class.java))
method.parameterAnnotations.also { paramAnns ->
assertEquals(1, paramAnns.size)
assertThat(paramAnns[0])
.hasOnlyOneElementSatisfying { a -> a.javaClass.isInstance(parameter) }
}
}
}
}
}
}

@ -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<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedStringToStub", String::class.java).also { method ->
method.invoke(null, MESSAGE).also { result ->
assertThat(result)
.isInstanceOf(String::class.java)
.isEqualTo(MESSAGE)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedStringToStub", String::class.java).also { method ->
assertFailsWith<InvocationTargetException> { method.invoke(null, MESSAGE) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubLongFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedLongToStub", Long::class.java).also { method ->
method.invoke(null, BIG_NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Long::class.javaObjectType)
.isEqualTo(BIG_NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedLongToStub", Long::class.java).also { method ->
assertFailsWith<InvocationTargetException> { method.invoke(null, BIG_NUMBER) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubIntFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedIntToStub", Int::class.java).also { method ->
method.invoke(null, NUMBER).also { result ->
assertThat(result)
.isInstanceOf(Int::class.javaObjectType)
.isEqualTo(NUMBER)
}
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
getDeclaredMethod("unwantedIntToStub", Int::class.java).also { method ->
assertFailsWith<InvocationTargetException> { method.invoke(null, NUMBER) }.targetException.also { ex ->
assertThat(ex)
.isInstanceOf(UnsupportedOperationException::class.java)
.hasMessage("Method has been deleted")
}
}
}
}
}
@Test
fun stubVoidFunction() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
val staticSeed = getDeclaredMethod("getStaticSeed")
assertEquals(0, staticSeed.invoke(null))
getDeclaredMethod("unwantedVoidToStub").invoke(null)
assertEquals(1, staticSeed.invoke(null))
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<Any>(FUNCTION_CLASS).apply {
val staticSeed = getDeclaredMethod("getStaticSeed")
assertEquals(0, staticSeed.invoke(null))
getDeclaredMethod("unwantedVoidToStub").invoke(null)
assertEquals(0, staticSeed.invoke(null))
}
}
}
}

@ -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<HasUnwantedVal>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVal)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertFailsWith<UnsupportedOperationException> { obj.unwantedVal }.also { ex ->
assertEquals("Method has been deleted", ex.message)
}
}
}
}
}
}

@ -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<HasUnwantedVar>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertEquals(MESSAGE, obj.unwantedVar)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
assertFailsWith<UnsupportedOperationException> { obj.unwantedVar }.also { ex ->
assertEquals("Method has been deleted", ex.message)
}
}
}
}
}
@Test
fun deleteSetter() {
classLoaderFor(testProject.sourceJar).use { cl ->
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
obj.unwantedVar = MESSAGE
assertEquals(MESSAGE, obj.unwantedVar)
}
}
}
classLoaderFor(testProject.filteredJar).use { cl ->
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
obj.unwantedVar = MESSAGE
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
}
}
}
}
}

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

@ -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<GradleException> { rethrowAsUncheckedException(IOException(MESSAGE)) }
assertThat(ex)
.hasMessage(MESSAGE)
.hasCauseExactlyInstanceOf(IOException::class.java)
}
@Test
fun testRethrowingCheckExceptionWithoutMessage() {
val ex = assertFailsWith<GradleException> { rethrowAsUncheckedException(IOException()) }
assertThat(ex)
.hasMessage("")
.hasCauseExactlyInstanceOf(IOException::class.java)
}
@Test
fun testRethrowingUncheckedException() {
val ex = assertFailsWith<IllegalArgumentException> { rethrowAsUncheckedException(IllegalArgumentException(MESSAGE)) }
assertThat(ex)
.hasMessage(MESSAGE)
.hasNoCause()
}
@Test
fun testRethrowingGradleException() {
val ex = assertFailsWith<InvalidUserDataException> { rethrowAsUncheckedException(InvalidUserDataException(MESSAGE)) }
assertThat(ex)
.hasMessage(MESSAGE)
.hasNoCause()
}
}

@ -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

@ -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 <reified T: R, reified R: Any> ByteArray.toClass(): Class<out R> = toClass(T::class.java, R::class.java)
fun <T: R, R: Any> ByteArray.toClass(type: Class<in T>, asType: Class<out R>): Class<out R>
= BytecodeClassLoader(this, type.name, type.classLoader).createClass().asSubclass(asType)
private class BytecodeClassLoader(
private val bytecode: ByteArray,
private val className: String,
parent: ClassLoader
) : ClassLoader(parent) {
internal fun createClass(): Class<*> {
return defineClass(className, bytecode, 0, bytecode.size).apply { resolveClass(this) }
}
// Ensure that the class we create also honours Class<*>.bytecode (above).
override fun getResourceAsStream(name: String): InputStream? {
return if (name == className.resourceName) ByteArrayInputStream(bytecode) else super.getResourceAsStream(name)
}
}

@ -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<String>,
d2: List<String>
) : MetadataTransformer<ProtoBuf.Class>(
logger,
emptyList(),
emptyList(),
emptyList(),
emptyList(),
emptyList(),
{},
d1,
d2,
ProtoBuf.Class::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val className = nameResolver.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<String> = sealedSubclassNames.map {
// Transform "a/b/c/BaseName.SubclassName" -> "a.b.c.BaseName$SubclassName"
nameResolver.getString(it).replace('.', '$').toPackageFormat }.toList()
val nestedClasses: List<String>
init {
val internalClassName = className.toPackageFormat
nestedClasses = nestedClassNames.map { "$internalClassName\$${nameResolver.getString(it)}" }.toList()
}
}

@ -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<String>,
d2: List<String>
) : MetadataTransformer<ProtoBuf.Package>(
logger,
emptyList(),
emptyList(),
emptyList(),
emptyList(),
emptyList(),
{},
d1,
d2,
ProtoBuf.Package::parseFrom
) {
override val typeTable = TypeTable(message.typeTable)
override val properties = mutableList(message.propertyList)
override val functions = mutableList(message.functionList)
override val typeAliases = mutableList(message.typeAliasList)
override fun rebuild(): ProtoBuf.Package = message
val typeAliasNames: List<String> = typeAliases.map { nameResolver.getString(it.name) }.toList()
}

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

@ -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<in String>, returnType: Matcher<in Class<*>>, vararg parameters: Matcher<in Class<*>>): Matcher<Method> {
return MethodMatcher(name, returnType, *parameters)
}
fun isMethod(name: String, returnType: Class<*>, vararg parameters: Class<*>): Matcher<Method> {
return isMethod(equalTo(name), equalTo(returnType), *parameters.map(::equalTo).toTypedArray())
}
val <T: Any> KClass<T>.javaDeclaredMethods: List<Method> get() = java.declaredMethods.toList()
/**
* Matcher logic for a Java [Method] object. Also applicable to constructors.
*/
private class MethodMatcher(
private val name: Matcher<in String>,
private val returnType: Matcher<in Class<*>>,
vararg parameters: Matcher<in Class<*>>
) : DiagnosingMatcher<Method>() {
private val parameters = listOf(*parameters)
override fun describeTo(description: Description) {
description.appendText("Method[name as ").appendDescriptionOf(name)
.appendText(", returnType as ").appendDescriptionOf(returnType)
.appendText(", parameters as '")
if (parameters.isNotEmpty()) {
val param = parameters.iterator()
description.appendValue(param.next())
while (param.hasNext()) {
description.appendText(",").appendValue(param.next())
}
}
description.appendText("']")
}
override fun matches(obj: Any?, mismatch: Description): Boolean {
if (obj == null) {
mismatch.appendText("is null")
return false
}
val method: Method = obj as? Method ?: return false
if (!name.matches(method.name)) {
mismatch.appendText("name is ").appendValue(method.name)
return false
}
method.returnType.apply {
if (!returnType.matches(this)) {
mismatch.appendText("returnType is ").appendValue(this.name)
return false
}
}
if (method.parameterTypes.size != parameters.size) {
mismatch.appendText("number of parameters is ").appendValue(method.parameterTypes.size)
.appendText(", parameters=").appendValueList("[", ",", "]", method.parameterTypes)
return false
}
var i = 0
method.parameterTypes.forEach { param ->
if (!parameters[i].matches(param)) {
mismatch.appendText("parameter[").appendValue(i).appendText("] is ").appendValue(param)
return false
}
++i
}
return true
}
}

@ -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<in String>, returnType: Matcher<in String>, vararg parameters: Matcher<in KParameter>): Matcher<KFunction<*>> {
return KFunctionMatcher(name, returnType, *parameters)
}
fun isFunction(name: String, returnType: KClass<*>, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
return isFunction(equalTo(name), matches(returnType), *parameters.map(::hasParam).toTypedArray())
}
fun isConstructor(returnType: Matcher<in String>, vararg parameters: Matcher<in KParameter>): Matcher<KFunction<*>> {
return KFunctionMatcher(equalTo("<init>"), returnType, *parameters)
}
fun isConstructor(returnType: KClass<*>, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
return isConstructor(matches(returnType), *parameters.map(::hasParam).toTypedArray())
}
fun isConstructor(returnType: String, vararg parameters: Matcher<in KParameter>): Matcher<KFunction<*>> {
return isConstructor(equalTo(returnType), *parameters)
}
fun hasParam(type: Matcher<in String>): Matcher<KParameter> = KParameterMatcher(type)
fun hasParam(type: KClass<*>): Matcher<KParameter> = hasParam(matches(type))
fun isProperty(name: String, type: KClass<*>): Matcher<KProperty<*>> = isProperty(equalTo(name), matches(type))
fun isProperty(name: Matcher<in String>, type: Matcher<in String>): Matcher<KProperty<*>> = KPropertyMatcher(name, type)
fun isClass(name: String): Matcher<KClass<*>> = KClassMatcher(equalTo(name))
fun matches(type: KClass<*>): Matcher<in String> = equalTo(type.qualifiedName)
/**
* Matcher logic for a Kotlin [KFunction] object. Also applicable to constructors.
*/
private class KFunctionMatcher(
private val name: Matcher<in String>,
private val returnType: Matcher<in String>,
vararg parameters: Matcher<in KParameter>
) : DiagnosingMatcher<KFunction<*>>() {
private val parameters = listOf(*parameters)
override fun describeTo(description: Description) {
description.appendText("KFunction[name as ").appendDescriptionOf(name)
.appendText(", returnType as ").appendDescriptionOf(returnType)
.appendText(", parameters as '")
if (parameters.isNotEmpty()) {
val param = parameters.iterator()
description.appendValue(param.next())
while (param.hasNext()) {
description.appendText(",").appendValue(param.next())
}
}
description.appendText("']")
}
override fun matches(obj: Any?, mismatch: Description): Boolean {
if (obj == null) {
mismatch.appendText("is null")
return false
}
val function: KFunction<*> = obj as? KFunction<*> ?: return false
if (!name.matches(function.name)) {
mismatch.appendText("name is ").appendValue(function.name)
return false
}
function.returnType.toString().apply {
if (!returnType.matches(this)) {
mismatch.appendText("returnType is ").appendValue(this)
return false
}
}
if (function.valueParameters.size != parameters.size) {
mismatch.appendText("number of parameters is ").appendValue(function.valueParameters.size)
.appendText(", parameters=").appendValueList("[", ",", "]", function.valueParameters)
return false
}
var i = 0
function.valueParameters.forEach { param ->
if (!parameters[i].matches(param)) {
mismatch.appendText("parameter[").appendValue(i).appendText("] is ").appendValue(param)
return false
}
++i
}
return true
}
}
/**
* Matcher logic for a Kotlin [KParameter] object.
*/
private class KParameterMatcher(
private val type: Matcher<in String>
) : DiagnosingMatcher<KParameter>() {
override fun describeTo(description: Description) {
description.appendText("KParameter[type as ").appendDescriptionOf(type)
.appendText("]")
}
override fun matches(obj: Any?, mismatch: Description): Boolean {
if (obj == null) {
mismatch.appendText("is null")
return false
}
val parameter: KParameter = obj as? KParameter ?: return false
parameter.type.toString().apply {
if (!type.matches(this)) {
mismatch.appendText("type is ").appendValue(this)
return false
}
}
return true
}
}
/**
* Matcher logic for a Kotlin [KProperty] object.
*/
private class KPropertyMatcher(
private val name: Matcher<in String>,
private val type: Matcher<in String>
) : DiagnosingMatcher<KProperty<*>>() {
override fun describeTo(description: Description) {
description.appendText("KProperty[name as ").appendDescriptionOf(name)
.appendText(", type as ").appendDescriptionOf(type)
.appendText("]")
}
override fun matches(obj: Any?, mismatch: Description): Boolean {
if (obj == null) {
mismatch.appendText("is null")
return false
}
val property: KProperty<*> = obj as? KProperty<*> ?: return false
if (!name.matches(property.name)) {
mismatch.appendText("name is ").appendValue(property.name)
return false
}
property.returnType.toString().apply {
if (!type.matches(this)) {
mismatch.appendText("type is ").appendValue(this)
return false
}
}
return true
}
}
/**
* Matcher logic for a Kotlin [KClass] object.
*/
private class KClassMatcher(private val className: Matcher<in String>) : DiagnosingMatcher<KClass<*>>() {
override fun describeTo(description: Description) {
description.appendText("KClass[name as ").appendDescriptionOf(className)
.appendText("]")
}
override fun matches(obj: Any?, mismatch: Description): Boolean {
if (obj == null) {
mismatch.appendText("is null")
return false
}
val type: KClass<*> = obj as? KClass<*> ?: return false
type.jvmName.apply {
if (!className.matches(this)) {
mismatch.appendText("name is ").appendValue(this)
return false
}
}
return true
}
}

@ -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"]
}
}

@ -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
}

@ -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

@ -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

@ -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

@ -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"]
}
}

@ -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)

@ -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
}

@ -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
}

@ -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"]
}
}

@ -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("<nothing>", data)
constructor(data: Int) : this("<nothing>", data.toLong())
override fun stringData(): String = message
override fun longData(): Long = data
override fun intData(): Int = data.toInt()
}

@ -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
}

@ -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"]
}
}

@ -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<String>.unwantedVal: String get() = this[0]
}

@ -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"]
}
}

@ -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
}

@ -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"]
}
}

@ -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() = "<value>"

@ -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"]
}
}

@ -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
}
}

@ -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)
}

@ -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"]
}
}

@ -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
}
}

@ -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"]
}
}

@ -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

@ -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

@ -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

@ -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"]
}
}

@ -0,0 +1,10 @@
@file:Suppress("UNUSED")
package net.corda.gradle
import net.corda.gradle.jarfilter.DeleteMe
class HasNestedClasses {
class OneToKeep
@DeleteMe class OneToThrowAway
}

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