From d654b71383156eed930fce483b058fe7a440f06b Mon Sep 17 00:00:00 2001 From: Tony Robalik Date: Sat, 28 Aug 2021 18:50:46 -0700 Subject: [PATCH] Resolves issue 395. Declared exceptions are part of the ABI. --- .../jvm/AbiExceptionsSpec.groovy | 24 ++++++ .../jvm/projects/AbiExceptionsProject.groovy | 86 +++++++++++++++++++ .../internal/kotlin/PublicApiDump.kt | 5 +- .../internal/kotlin/abiDependencies.kt | 6 +- .../internal/kotlin/asmUtils.kt | 14 ++- 5 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 src/functionalTest/groovy/com/autonomousapps/jvm/AbiExceptionsSpec.groovy create mode 100644 src/functionalTest/groovy/com/autonomousapps/jvm/projects/AbiExceptionsProject.groovy diff --git a/src/functionalTest/groovy/com/autonomousapps/jvm/AbiExceptionsSpec.groovy b/src/functionalTest/groovy/com/autonomousapps/jvm/AbiExceptionsSpec.groovy new file mode 100644 index 000000000..e05e66d30 --- /dev/null +++ b/src/functionalTest/groovy/com/autonomousapps/jvm/AbiExceptionsSpec.groovy @@ -0,0 +1,24 @@ +package com.autonomousapps.jvm + +import com.autonomousapps.jvm.projects.AbiExceptionsProject + +import static com.autonomousapps.utils.Runner.build +import static com.google.common.truth.Truth.assertThat + +final class AbiExceptionsSpec extends AbstractJvmSpec { + + def "declared exceptions are part of the abi (#gradleVersion)"() { + given: + def project = new AbiExceptionsProject() + gradleProject = project.gradleProject + + when: + build(gradleVersion, gradleProject.rootDir, ':buildHealth') + + then: + assertThat(project.actualBuildHealth()).containsExactlyElementsIn(project.expectedBuildHealth) + + where: + gradleVersion << gradleVersions() + } +} diff --git a/src/functionalTest/groovy/com/autonomousapps/jvm/projects/AbiExceptionsProject.groovy b/src/functionalTest/groovy/com/autonomousapps/jvm/projects/AbiExceptionsProject.groovy new file mode 100644 index 000000000..6c9fb47c6 --- /dev/null +++ b/src/functionalTest/groovy/com/autonomousapps/jvm/projects/AbiExceptionsProject.groovy @@ -0,0 +1,86 @@ +package com.autonomousapps.jvm.projects + +import com.autonomousapps.AbstractProject +import com.autonomousapps.advice.ComprehensiveAdvice +import com.autonomousapps.kit.GradleProject +import com.autonomousapps.kit.Plugin +import com.autonomousapps.kit.Source +import com.autonomousapps.kit.SourceType + +import static com.autonomousapps.AdviceHelper.actualBuildHealth +import static com.autonomousapps.AdviceHelper.emptyBuildHealthFor +import static com.autonomousapps.kit.Dependency.project + +final class AbiExceptionsProject extends AbstractProject { + + final GradleProject gradleProject + + AbiExceptionsProject() { + this.gradleProject = build() + } + + private GradleProject build() { + def builder = newGradleProjectBuilder() + builder.withSubproject('proj') { s -> + s.sources = libSources + s.withBuildScript { bs -> + bs.plugins = [Plugin.javaLibraryPlugin] + bs.dependencies = [project('api', ':exceptions')] + } + } + builder.withSubproject('exceptions') { s -> + s.sources = exceptionsSources + s.withBuildScript { bs -> + bs.plugins = [Plugin.javaLibraryPlugin] + } + } + + def project = builder.build() + project.writer().write() + return project + } + + private libSources = [ + new Source( + SourceType.JAVA, "Sup", "com/example", + """\ + package com.example; + + public interface Sup {} + """.stripIndent() + ), + new Source( + SourceType.JAVA, "Main", "com/example", + """\ + package com.example; + + import com.example.exception.FancyException; + + public class Main implements Sup { + public String magic() throws FancyException { + return "42"; + } + } + """.stripIndent() + ) + ] + + private exceptionsSources = [ + new Source( + SourceType.JAVA, "FancyException", "com/example/exception", + """\ + package com.example.exception; + + public class FancyException extends RuntimeException {} + """.stripIndent() + ) + ] + + List actualBuildHealth() { + return actualBuildHealth(gradleProject) + } + + final List expectedBuildHealth = emptyBuildHealthFor( + ':proj', ':exceptions', ':' + ) +} diff --git a/src/main/kotlin/com/autonomousapps/internal/kotlin/PublicApiDump.kt b/src/main/kotlin/com/autonomousapps/internal/kotlin/PublicApiDump.kt index 850aa931a..4565a3d87 100644 --- a/src/main/kotlin/com/autonomousapps/internal/kotlin/PublicApiDump.kt +++ b/src/main/kotlin/com/autonomousapps/internal/kotlin/PublicApiDump.kt @@ -91,7 +91,10 @@ fun getBinaryAPI(classStreams: Sequence, visibilityFilter: (String) parameterAnnotations = parameterAnnotations, typeAnnotations = typeAnnotations, isPublishedApi = isPublishedApi(), - access = AccessFlags(access) + access = AccessFlags(access), + // nb: MethodNode.exceptions is NOT expressed as a type descriptor, rather as a path. + // e.g., not `Lcom/example/Foo;`, but just `com/example/Foo` + exceptions = exceptions ) } } diff --git a/src/main/kotlin/com/autonomousapps/internal/kotlin/abiDependencies.kt b/src/main/kotlin/com/autonomousapps/internal/kotlin/abiDependencies.kt index f92f53c71..a07caeb61 100644 --- a/src/main/kotlin/com/autonomousapps/internal/kotlin/abiDependencies.kt +++ b/src/main/kotlin/com/autonomousapps/internal/kotlin/abiDependencies.kt @@ -83,8 +83,12 @@ private fun List.dependencies( .flatMapToSet { it.typeAnnotations } .flatMapToSet { DESC_REGEX.findAll(it).allItems() } + val exceptions = classSignature.memberSignatures + .filterIsInstance() + .flatMapToSet { it.exceptions } // no need for DESC_REGEX + // return - superTypes + memberTypes + memberGenericTypes + classAnnotations + memberAnnotations + parameterAnnotations + typeAnnotations + superTypes + memberTypes + memberGenericTypes + classAnnotations + memberAnnotations + parameterAnnotations + typeAnnotations + exceptions }.mapToSet { it.replace("/", ".") } diff --git a/src/main/kotlin/com/autonomousapps/internal/kotlin/asmUtils.kt b/src/main/kotlin/com/autonomousapps/internal/kotlin/asmUtils.kt index 50b6f9522..806cf0252 100644 --- a/src/main/kotlin/com/autonomousapps/internal/kotlin/asmUtils.kt +++ b/src/main/kotlin/com/autonomousapps/internal/kotlin/asmUtils.kt @@ -68,13 +68,21 @@ data class MethodBinarySignature( override val jvmMember: JvmMethodSignature, override val genericTypes: Set, override val annotations: Set, + override val isPublishedApi: Boolean, + override val access: AccessFlags, val parameterAnnotations: List, val typeAnnotations: List, - override val isPublishedApi: Boolean, - override val access: AccessFlags + /** Not expressed as type descriptors, instead just `com/example/Foo`. */ + val exceptions: List ) : MemberBinarySignature { + override val signature: String - get() = "${access.getModifierString()} fun $name $desc" + get() = "${access.getModifierString()} fun $name $desc$throws" + + private val throws = if (exceptions.isEmpty()) "" else exceptions.joinToString(prefix = " throws ") { + // The ABI dump uses descriptor strings + "L$it;" + } override fun isEffectivelyPublic(classAccess: AccessFlags, classVisibility: ClassVisibility?) = super.isEffectivelyPublic(classAccess, classVisibility)