rules_kotlin icon indicating copy to clipboard operation
rules_kotlin copied to clipboard

Failed on inline functions when coverage is enabled

Open jameslan opened this issue 4 years ago • 4 comments

On the master branch commit 89a082c69ce3bc7b0a996aea12cb2d118ab0bbc7 of rules_kotlin.

It either complains of accessing private member $jacocoInit from outside of the class, at runtime; or failed with the message of invalid bytecode at compile time. While the source code could pass tests and run flawlessly.

I think the reason is that the inline functions will be embedded into the caller, and jacoco instrumentation breaks it. For example, An inline function Foo.func will be embedded to the caller Bar.func in another jar, and jacoco will generate an instrumented version of Foo.func(let's call it Foo.func_inst, although its name is still Foo.func), in which a private member Foo.$jacocoInit is called. If Bar.func embeds this Foo.func_inst, it also embeds the invocation to Foo.$jacocoInit, which is causing compile-time error or runtime error.

I think the solution would be generating two jars side by side: one is original and one is instrumented. At compile time, always use the original one, but at runtime, instrumented one could be used to collect coverage. In this way, the instrumentation will be always after the code generation.

jameslan avatar Mar 22 '21 20:03 jameslan

I am seeing this issue as well. Here is a simple repro based off of a rules_kotlin example.

https://github.com/bazelbuild/rules_kotlin/pull/600/files

The snippet is from a project we are migrating from gradle to bazel. Instrumentation worked in gradle. Note that in gradle, we did precisely what James Lan is describing.

thomasbao12 avatar Oct 14 '21 20:10 thomasbao12

There are two approaches to solve these inline function related issues:

  1. Similar to gradle, we need to use uninstrumented classes during compilation stage but use instrumented classes during dexing stage. Gradle achieves this by having instrumented classes under a separated folder. This approach requires much more code changes because we have to generate different output follow and change the way dexer works in bazel repository.

  2. We noticed that this issue can be workaround by enabling experimental_use_abi_jars flag in kotlin rule. However, some targets which is incompatible with abi jar solution will still cause issues after applying kt_abi_plugin_incompatible. What we can do is to use uncovered jar as abi jar for those targets which is incompatible with experimental_use_abi_jars flag here

  if (outputs.abijar.isNotEmpty() || (outputs.abijar.isEmpty() && coverageIsOn) {
      if (coverageIsOn)
          context.execute("create uncovered jar to serve as abi jar", ::createOutputJar)
        } else {
           context.execute("create abi jar", ::createAbiJar)
        }

ThomasCJY avatar Dec 10 '21 20:12 ThomasCJY

I think we should revisit this issue. Internally we have this workaround in compile.bzl under _run_kt_java_builder_actions

    # Build Kotlin
    if has_kt_sources:
...
        # This is used to workaround https://github.com/bazelbuild/rules_kotlin/issues/509
        if ctx.coverage_instrumented():
            uninstrumented_jar = ctx.actions.declare_file(ctx.label.name + "-kt-uninstrumented.jar")
            outputs["uninstrumented_jar"] = uninstrumented_jar

        if "kt_abi_plugin_incompatible" in ctx.attr.tags:
            if ctx.coverage_instrumented():
                kt_compile_jar = uninstrumented_jar
            else:
                kt_compile_jar = kt_runtime_jar
        else:
            kt_compile_jar = ctx.actions.declare_file(ctx.label.name + "-kt.abi.jar")
            outputs["abi_jar"] = kt_compile_jar

nkoroste avatar Oct 14 '22 14:10 nkoroste