Why am I getting ArrayIndexOutOfBoundsException doing this particular Cucumber step in Kotlin?

I am working with a JCM Cucumber feature file using Java8 and PicoContainer. I removed these steps so that they are empty and I still get the error. Here's my feature:

Feature: Full Journey

Scenario: Can load a typical JIRA csv and calculate the distribution from it

Given a typical JIRA export "/closed_only_JIRA.csv"
When I import it into Montecarluni
Then I should see the distribution
"""
6, 15, 3, 14, 2, 5, 6, 8, 5, 10, 15, 4, 2, 1
"""
When I copy it to the clipboard
Then I should be able to paste it somewhere else

      

(Yes, this is the full path, not a BDD script.)

For some reason, doing this step in Kotlin throws an error:

import cucumber.api.java8.En

class ClipboardSteps(val world : World) : En {
    init {
        When("^I copy it to the clipboard$", {
            // Errors even without any code here 
        })
    }
}

      

So far, this Java class works just fine:

import cucumber.api.java8.En;

public class JavaClipboardSteps implements En {

    public JavaClipboardSteps(World world) {
        When("^I copy it to the clipboard$", () -> {
            // Works just fine with code or without
        });
    }
}

      

I am completely overwhelmed, not least because the "Then" in this Kotlin step class works great and this other step runs without error:

import cucumber.api.java8.En

class FileImportSteps(val world: World) : En {
    init {
        // There a Given here

        When("^I import it into Montecarluni$", {
            // There some code here
        })
    }
}

      

Runner to complete:

import cucumber.api.CucumberOptions
import cucumber.api.junit.Cucumber
import org.junit.runner.RunWith

@RunWith(Cucumber::class)
@CucumberOptions(
    format = arrayOf("pretty"),
    glue = arrayOf("com.lunivore.montecarluni.glue"),
    features = arrayOf("."))
class Runner {
}

      

Stacktrace:

cucumber.runtime.CucumberException: java.lang.ArrayIndexOutOfBoundsException: 52

at cucumber.runtime.java.JavaBackend.addStepDefinition(JavaBackend.java:166)
at cucumber.api.java8.En.Then(En.java:280)
at com.lunivore.montecarluni.glue.DistributionSteps.<init>(DistributionSteps.kt:8)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.picocontainer.injectors.AbstractInjector.newInstance(AbstractInjector.java:145)
at org.picocontainer.injectors.ConstructorInjector$1.run(ConstructorInjector.java:342)
at org.picocontainer.injectors.AbstractInjector$ThreadLocalCyclicDependencyGuard.observe(AbstractInjector.java:270)
at org.picocontainer.injectors.ConstructorInjector.getComponentInstance(ConstructorInjector.java:364)
at org.picocontainer.injectors.AbstractInjectionFactory$LifecycleAdapter.getComponentInstance(AbstractInjectionFactory.java:56)
at org.picocontainer.behaviors.AbstractBehavior.getComponentInstance(AbstractBehavior.java:64)
at org.picocontainer.behaviors.Stored.getComponentInstance(Stored.java:91)
at org.picocontainer.DefaultPicoContainer.getInstance(DefaultPicoContainer.java:699)
at org.picocontainer.DefaultPicoContainer.getComponent(DefaultPicoContainer.java:647)
at org.picocontainer.DefaultPicoContainer.getComponent(DefaultPicoContainer.java:678)
at cucumber.runtime.java.picocontainer.PicoFactory.getInstance(PicoFactory.java:40)
at cucumber.runtime.java.JavaBackend.buildWorld(JavaBackend.java:131)
at cucumber.runtime.Runtime.buildBackendWorlds(Runtime.java:141)
at cucumber.runtime.model.CucumberScenario.run(CucumberScenario.java:38)
at cucumber.runtime.junit.ExecutionUnitRunner.run(ExecutionUnitRunner.java:102)
at cucumber.runtime.junit.FeatureRunner.runChild(FeatureRunner.java:63)
at cucumber.runtime.junit.FeatureRunner.runChild(FeatureRunner.java:18)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at cucumber.runtime.junit.FeatureRunner.run(FeatureRunner.java:70)
at cucumber.api.junit.Cucumber.runChild(Cucumber.java:95)
at cucumber.api.junit.Cucumber.runChild(Cucumber.java:38)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at cucumber.api.junit.Cucumber.run(Cucumber.java:100)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Caused by: java.lang.ArrayIndexOutOfBoundsException: 52
at jdk.internal.org.objectweb.asm.Type.getArgumentTypes(Type.java:358)
at cucumber.runtime.java8.ConstantPoolTypeIntrospector.getGenericTypes(ConstantPoolTypeIntrospector.java:32)
at cucumber.runtime.java.Java8StepDefinition.getParameterInfos(Java8StepDefinition.java:54)
at cucumber.runtime.java.Java8StepDefinition.<init>(Java8StepDefinition.java:44)
at cucumber.runtime.java.JavaBackend.addStepDefinition(JavaBackend.java:162)
... 44 more

      

What's happening?

All source code currently tagged with Kotlin step is commented out, here . (Please excuse the confusion as I'm new to a lot of the things I use, refactoring from the initial splash continues.)

+3


source to share


1 answer


This seems to be an unfortunate interaction between Kotlin optimizations making compilation of anonymous blocks of code, guess what Cucumber does about how the JVM stores references to lambdas, and Cucumber uses some JVM components to keep it from getting too close!

Your other Kotlin steps do not throw an error for different (different) reasons.

In short, if Kotlin can implement a block or lambda as a static singleton, then this is presumably for performance reasons. This interferes with some unconventional reflection magic that the cucumber is performing (details below).

The fix would be to add an extra check in the Cucumber code, although perhaps a better solution would be to rewrite the Cucumber code to use generics correctly .

A workaround is to ensure that Kotlin doesn't optimize the lambda by including a reference to the containing instance. Even something as simple as linking to this

:

When("^I import it into Montecarluni$") {
    this
    // your code
}

      

enough to convince Kotlin not to do the optimization.



Details

When cucumbers add pitch definition with lambda eg. cucumber.api.java8.En it parses the lambda to get information about generics.

The way it is done is to use an access hacker to reach the field sun.reflect.ConstantPool

in the definition of the lambda class. It is a native type and represents an implementation detail of the class, while retaining references to the constants used by the class. The cucumber then iterates backward looking at the constant representing the lambda constructor. It then uses another internal hack, a static method called getArgumentTypes

on jdk.internal.org.objectweb.asm.Type

, to determine the lambda signature.

By running javap -v against the generated classes, it turned out that when Kotlin does a lambda block in a static singleton, it adds a constant field called INSTANCE

, which then appears in the class's constant pool. This field is an instance of an anonymous inner class with a type name ClipboardSteps$1

, not a lambda per se, so its inner typestring breaks the miniparser internally getArgumentTypes

, which is the error you are seeing.

Thus, a quick fix in Cucumber is to check that the name of the constant pool member "<init>"

that the lambda constructor represents and ignore anything else, like our member INSTANCE

.

Correct fix would be to rewrite the Cucumber type introspection to not use persistent pool at all!

+10


source







All Articles