From 72fb549bb5bdac36e1eef31430ac7a1d258529fe Mon Sep 17 00:00:00 2001 From: Pavel Egipti Date: Thu, 3 Aug 2023 13:47:58 +0300 Subject: [PATCH 01/13] Add working version of branch coverage --- utbot-instrumentation/build.gradle.kts | 5 + .../org/jacoco/core/internal/instr/Api.kt | 28 ++++ .../instr/MyClassFieldProbeArrayStrategy.kt | 93 ++++++++++++ .../internal/instr/MyClassInstrumenter.kt | 25 ++++ .../internal/instr/MyMethodInstrumenter.kt | 134 ++++++++++++++++++ .../core/internal/instr/MyProbeInserter.kt | 18 +++ .../coverage/BranchCoverageInstrumentation.kt | 70 +++++++++ .../coverage/CoverageInstrumentation.kt | 10 +- .../instrumenter/Instrumenter.kt | 18 +++ 9 files changed, 398 insertions(+), 3 deletions(-) create mode 100644 utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/Api.kt create mode 100644 utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyClassFieldProbeArrayStrategy.kt create mode 100644 utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyClassInstrumenter.kt create mode 100644 utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyMethodInstrumenter.kt create mode 100644 utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyProbeInserter.kt create mode 100644 utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/coverage/BranchCoverageInstrumentation.kt diff --git a/utbot-instrumentation/build.gradle.kts b/utbot-instrumentation/build.gradle.kts index 718aa6b29b..581354f2bf 100644 --- a/utbot-instrumentation/build.gradle.kts +++ b/utbot-instrumentation/build.gradle.kts @@ -14,6 +14,8 @@ val rdVersion: String by rootProject val mockitoVersion: String by rootProject val mockitoInlineVersion: String by rootProject +val jacocoVersion: String by rootProject + plugins { id("com.github.johnrengelman.shadow") version "7.1.2" id("java") @@ -52,6 +54,7 @@ dependencies { implementation("org.ow2.asm:asm:$asmVersion") implementation("org.ow2.asm:asm-commons:$asmVersion") + implementation("org.ow2.asm:asm-util:$asmVersion") implementation("io.github.microutils:kotlin-logging:$kotlinLoggingVersion") implementation("com.jetbrains.rd:rd-framework:$rdVersion") @@ -62,6 +65,8 @@ dependencies { implementation("org.mockito:mockito-core:$mockitoVersion") implementation("org.mockito:mockito-inline:$mockitoInlineVersion") + implementation("org.jacoco:org.jacoco.core:$jacocoVersion") + implementation(project(":utbot-spring-commons-api")) if (projectType == springEdition || projectType == ultimateEdition) { fetchSpringCommonsJar(project(":utbot-spring-commons", configuration = "springCommonsJar")) diff --git a/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/Api.kt b/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/Api.kt new file mode 100644 index 0000000000..2ae5e23b80 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/Api.kt @@ -0,0 +1,28 @@ +package org.jacoco.core.internal.instr + +import org.jacoco.core.internal.flow.ClassProbesAdapter +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.util.TraceClassVisitor +import java.io.PrintWriter +import java.io.StringWriter + +val sw: StringWriter = StringWriter() + +fun createJacocoClassVisitorForCollectionBranchCoverage(writer: ClassWriter): ClassVisitor { + val strategy = NoneProbeArrayStrategy() + val tcv = TraceClassVisitor(writer, PrintWriter(sw)) + return ClassProbesAdapter( + MyClassInstrumenter(strategy, tcv), + true + ) +} + +fun createJacocoClassVisitorForBytecodeInstrumentation(writer: ClassWriter, className: String): ClassVisitor { + val strategy = MyClassFieldProbeArrayStrategy(className) + val tcv = TraceClassVisitor(writer, PrintWriter(sw)) + return ClassProbesAdapter( + ClassInstrumenter(strategy, tcv), + true + ) +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyClassFieldProbeArrayStrategy.kt b/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyClassFieldProbeArrayStrategy.kt new file mode 100644 index 0000000000..521b0a37f9 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyClassFieldProbeArrayStrategy.kt @@ -0,0 +1,93 @@ +package org.jacoco.core.internal.instr + +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.utbot.instrumentation.Settings + +/** + * Inspired by [org.jacoco.core.internal.instr.ClassFieldProbeArrayStrategy] + */ +class MyClassFieldProbeArrayStrategy(private val className: String) : IProbeArrayStrategy { + + private val FRAME_STACK_EMPTY = arrayOf(0) + + private val FRAME_LOCALS_EMPTY = arrayOfNulls(0) + + override fun storeInstance(mv: MethodVisitor, clinit: Boolean, variable: Int): Int { + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + className, + // TODO change method name + InstrSupport.INITMETHOD_NAME, + InstrSupport.INITMETHOD_DESC, + false + ) + mv.visitVarInsn(Opcodes.ASTORE, variable) + return 1 + } + + override fun addMembers(cv: ClassVisitor, probeCount: Int) { + createDataField(cv) + createInitMethod(cv, probeCount) + } + + private fun createDataField(cv: ClassVisitor) { + cv.visitField( + Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, + Settings.PROBES_ARRAY_NAME, + Settings.PROBES_ARRAY_DESC, + null, + null + ) + } + + private fun createInitMethod(cv: ClassVisitor, probeCount: Int) { + val mv = cv.visitMethod( + InstrSupport.INITMETHOD_ACC, + // TODO change method name + InstrSupport.INITMETHOD_NAME, + InstrSupport.INITMETHOD_DESC, + null, + null + ) + + mv.visitCode() + mv.visitFieldInsn( + Opcodes.GETSTATIC, + className, + Settings.PROBES_ARRAY_NAME, + Settings.PROBES_ARRAY_DESC + ) + val alreadyInitialized = Label() + mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized) + mv.visitIntInsn(Opcodes.BIPUSH, probeCount) + mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BOOLEAN) + mv.visitFieldInsn( + Opcodes.PUTSTATIC, + className, + Settings.PROBES_ARRAY_NAME, + Settings.PROBES_ARRAY_DESC + ) + mv.visitFrame( + Opcodes.F_NEW, + 0, + FRAME_LOCALS_EMPTY, + 0, + FRAME_STACK_EMPTY + ) + mv.visitLabel(alreadyInitialized) + mv.visitFieldInsn( + Opcodes.GETSTATIC, + className, + Settings.PROBES_ARRAY_NAME, + Settings.PROBES_ARRAY_DESC + ) + mv.visitInsn(Opcodes.ARETURN) + + mv.visitMaxs(1, 0) + mv.visitEnd() + } + +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyClassInstrumenter.kt b/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyClassInstrumenter.kt new file mode 100644 index 0000000000..0a4a9d0f2b --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyClassInstrumenter.kt @@ -0,0 +1,25 @@ +package org.jacoco.core.internal.instr + +import org.jacoco.core.internal.flow.MethodProbesVisitor +import org.objectweb.asm.ClassVisitor + +class MyClassInstrumenter( + probeArrayStrategy: IProbeArrayStrategy, + cv: ClassVisitor +) : ClassInstrumenter(probeArrayStrategy, cv) { + + override fun visitMethod( + access: Int, + name: String, + desc: String, + signature: String?, + exceptions: Array? + ): MethodProbesVisitor { + val mv = cv.visitMethod(access, name, desc, signature, exceptions) + + val frameEliminator = DuplicateFrameEliminator(mv) + val probeVariableInserter = MyProbeInserter(name, frameEliminator) + return MyMethodInstrumenter(probeVariableInserter, probeVariableInserter) + } + +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyMethodInstrumenter.kt b/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyMethodInstrumenter.kt new file mode 100644 index 0000000000..3039b35897 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/jacoco/core/internal/instr/MyMethodInstrumenter.kt @@ -0,0 +1,134 @@ +package org.jacoco.core.internal.instr + +import org.jacoco.core.internal.flow.IFrame +import org.jacoco.core.internal.flow.LabelInfo +import org.jacoco.core.internal.flow.MethodProbesVisitor +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +internal class MyMethodInstrumenter( + mv: MethodVisitor, + private val probeInserter: IProbeInserter +) : MethodProbesVisitor(mv) { + + override fun visitProbe(probeId: Int) { + probeInserter.insertProbe(probeId) + } + + override fun visitInsnWithProbe(opcode: Int, probeId: Int) { + probeInserter.insertProbe(probeId) + mv.visitInsn(opcode) + } + + override fun visitJumpInsnWithProbe(opcode: Int, label: Label?, probeId: Int, frame: IFrame) { + if (opcode == Opcodes.GOTO) { + probeInserter.insertProbe(probeId) + mv.visitJumpInsn(Opcodes.GOTO, label) + } else { + val intermediate = Label() + mv.visitJumpInsn(getInverted(opcode), intermediate) + probeInserter.insertProbe(probeId) + mv.visitJumpInsn(Opcodes.GOTO, label) + mv.visitLabel(intermediate) + frame.accept(mv) + } + } + + private fun getInverted(opcode: Int): Int = + when (opcode) { + Opcodes.IFEQ -> Opcodes.IFNE + Opcodes.IFNE -> Opcodes.IFEQ + Opcodes.IFLT -> Opcodes.IFGE + Opcodes.IFGE -> Opcodes.IFLT + Opcodes.IFGT -> Opcodes.IFLE + Opcodes.IFLE -> Opcodes.IFGT + Opcodes.IF_ICMPEQ -> Opcodes.IF_ICMPNE + Opcodes.IF_ICMPNE -> Opcodes.IF_ICMPEQ + Opcodes.IF_ICMPLT -> Opcodes.IF_ICMPGE + Opcodes.IF_ICMPGE -> Opcodes.IF_ICMPLT + Opcodes.IF_ICMPGT -> Opcodes.IF_ICMPLE + Opcodes.IF_ICMPLE -> Opcodes.IF_ICMPGT + Opcodes.IF_ACMPEQ -> Opcodes.IF_ACMPNE + Opcodes.IF_ACMPNE -> Opcodes.IF_ACMPEQ + Opcodes.IFNULL -> Opcodes.IFNONNULL + Opcodes.IFNONNULL -> Opcodes.IFNULL + else -> throw IllegalArgumentException() + } + + override fun visitTableSwitchInsnWithProbes( + min: Int, + max: Int, + dflt: Label, + labels: Array