diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index bfe13a7e..39d88db1 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -14,6 +14,8 @@ * limitations under the License. */ +import com.intellij.rt.coverage.utils.ClassFileUpgradeTask + sourceSets { jmh.java.srcDirs = [file('jmh')] main.java.srcDirs = [] @@ -34,3 +36,13 @@ dependencies { jmhImplementation 'junit:junit:4.13.1' jmhImplementation fileTree('lib') } + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +task upgradeJmhClasspath(type: ClassFileUpgradeTask) { + inputFiles = sourceSets.jmh.runtimeClasspath + outputDirectory = layout.buildDirectory.dir("jmhClasspath").get() +} \ No newline at end of file diff --git a/benchmarks/jmh.gradle b/benchmarks/jmh.gradle index 9ed1c8e3..eac052a3 100644 --- a/benchmarks/jmh.gradle +++ b/benchmarks/jmh.gradle @@ -41,10 +41,11 @@ task NoAgent(type: JavaExec) { def configureBenchmark(JavaExec benchmark, Closure> jvmArgs = {[]}) { benchmark.with { group = 'benchmarks' - dependsOn ":benchmarks:jmhClasses" + dependsOn(":benchmarks:jmhClasses", ":benchmarks:upgradeJmhClasspath") main = 'org.openjdk.jmh.Main' doFirst { - classpath = project(":benchmarks").sourceSets.jmh.runtimeClasspath + classpath = fileTree(project(":benchmarks").upgradeJmhClasspath.outputs.files.singleFile) + classpath += project(":benchmarks").sourceSets.jmh.output.classesDirs args = [ '-jvmArgs', '-Dfile.encoding=UTF-8', // benchmarks @@ -73,27 +74,38 @@ def configureBenchmark(JavaExec benchmark, Closure> jvmArgs = {[]}) } } +ext.configureBenchmark = this.&configureBenchmark + def benchmarkReport(Task benchmark) { file("$benchmark.temporaryDir/${benchmark.name}.json") } - -ext.configureCompareWith = { benchmark, Closure> jvmArgs, basicBenchmark -> - configureBenchmark(benchmark, jvmArgs) - benchmark.with { - dependsOn(basicBenchmark) +ext.configureComparison = { combined, benchmark1, benchmark2 -> + combined.with { + group = 'benchmarks' + if (benchmark1 != combined) { + dependsOn benchmark1 + } + if (benchmark2 != combined) { + dependsOn benchmark2 + } doLast { - def noAgentReport = ResourceGroovyMethods.getText(benchmarkReport(basicBenchmark), 'UTF-8') - def currentReport = ResourceGroovyMethods.getText(benchmarkReport(benchmark), 'UTF-8') + def firstReport = ResourceGroovyMethods.getText(benchmarkReport(benchmark1), 'UTF-8') + def secondReport = ResourceGroovyMethods.getText(benchmarkReport(benchmark2), 'UTF-8') project.logger.quiet """Benchmark score: - $basicBenchmark.name vs $benchmark.name: - ${ReportReader.readScore(noAgentReport, currentReport, secondaryMetrics)} + $benchmark1.name vs $benchmark2.name: + ${ReportReader.readScore(firstReport, secondReport, secondaryMetrics)} """ } } } -ext.configureCompare = { benchmark, Closure> jvmArgs -> +ext.configureCompareWith = { benchmark, Closure> jvmArgs, basicBenchmark -> + configureBenchmark(benchmark, jvmArgs) + configureComparison(benchmark, basicBenchmark, benchmark) +} + +ext.configureCompareWithNoAgent = { benchmark, Closure> jvmArgs -> configureCompareWith(benchmark, jvmArgs, NoAgent) } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 7e2ef1ac..71f0cec4 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -24,4 +24,7 @@ repositories { dependencies { implementation("com.guardsquare:proguard-gradle:7.3.0") + implementation("org.ow2.asm:asm-tree:9.7") + implementation("org.ow2.asm:asm-commons:9.7") + implementation("commons-io:commons-io:2.16.1") } diff --git a/buildSrc/src/main/groovy/com/intellij/rt/coverage/utils/ClassFileUpgradeTask.groovy b/buildSrc/src/main/groovy/com/intellij/rt/coverage/utils/ClassFileUpgradeTask.groovy new file mode 100644 index 00000000..43e05f4e --- /dev/null +++ b/buildSrc/src/main/groovy/com/intellij/rt/coverage/utils/ClassFileUpgradeTask.groovy @@ -0,0 +1,110 @@ +/* + * Copyright 2000-2024 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.rt.coverage.utils + +import groovy.transform.CompileStatic +import org.apache.commons.io.IOUtils +import org.gradle.api.DefaultTask +import org.gradle.api.file.Directory +import org.gradle.api.file.FileCollection +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.objectweb.asm.ClassReader + +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream + +@CompileStatic +abstract class ClassFileUpgradeTask extends DefaultTask { + @InputFiles + FileCollection inputFiles + + @OutputDirectory + Directory outputDirectory + + @TaskAction + void transform() { + def inputJars = inputFiles.files.findAll { it.name.endsWith(".jar") } + List zipFiles = [] + try { + inputJars.each { + zipFiles << new ZipFile(it) + } + process(zipFiles) + } finally { + zipFiles.each { it.close() } + } + } + + private void process(List zipFiles) { + def superClasses = collectSuperClasses(zipFiles) + transformJars(zipFiles, superClasses) + } + + private Map collectSuperClasses(List zipFiles) { + Map superClasses = [:] + zipFiles.each { zip -> + eachEntry(zip) { ZipEntry classEntry -> + if (!classEntry.name.endsWith(".class")) { + return + } + zip.getInputStream(classEntry).withStream { inputStream -> + def reader = new ClassReader(inputStream) + superClasses[reader.className] = reader.superName + } + } + } + return superClasses + } + + private void transformJars(List zipFiles, Map superClasses) { + zipFiles.each { zip -> + def outputFile = outputDirectory.file(new File(zip.name).name).asFile + new ZipOutputStream(outputFile.newOutputStream()).withStream { output -> + eachEntry(zip) { ZipEntry entry -> + output.putNextEntry(new ZipEntry(entry.name)) + if (!entry.isDirectory()) { + zip.getInputStream(entry).withStream { input -> + if (entry.name.endsWith(".class")) { + def bytes = transformClass(input, superClasses) + IOUtils.write(bytes, output) + } else { + IOUtils.copy(input, output) + } + } + } + output.closeEntry() + } + } + } + } + + private byte[] transformClass(InputStream stream, Map superClasses) { + def reader = new ClassReader(stream) + def writer = new HierarchyClassWriter(superClasses) + reader.accept(new UpgradingClassVisitor(writer), 0) + return writer.toByteArray() + } + + private void eachEntry(ZipFile zip, Closure callback) { + zip.entries().iterator().each { ZipEntry entry -> + callback(entry) + } + } +} diff --git a/buildSrc/src/main/groovy/com/intellij/rt/coverage/utils/HierarchyClassWriter.groovy b/buildSrc/src/main/groovy/com/intellij/rt/coverage/utils/HierarchyClassWriter.groovy new file mode 100644 index 00000000..28aa15ab --- /dev/null +++ b/buildSrc/src/main/groovy/com/intellij/rt/coverage/utils/HierarchyClassWriter.groovy @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2024 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.rt.coverage.utils + +import groovy.transform.CompileStatic +import org.objectweb.asm.ClassWriter + +@CompileStatic +class HierarchyClassWriter extends ClassWriter { + private final Map superClasses + private final Map> hierarchies = [:] + + HierarchyClassWriter(Map superClasses) { + super(COMPUTE_FRAMES) + this.superClasses = superClasses + } + + @Override + protected String getCommonSuperClass(String type1, String type2) { + def hierarchy1 = getHierarchy(type1) + def hierarchy2 = getHierarchy(type2) + return hierarchy1.find { it in hierarchy2 } + } + + private LinkedHashSet getHierarchy(String type) { + LinkedHashSet result = hierarchies[type] + if (result != null) { + return result + } + result = [] + hierarchies[type] = result + while (type != null) { + result << type + type = getSuperClass(type) + } + result << "java/lang/Object" + return result + } + + private String getSuperClass(String type) { + String known = superClasses[type] + if (known != null) { + return known + } + def clazz = Class.forName(type.replace('/', '.'), false, classLoader) + return clazz.superclass?.name?.replace('.', '/') + } +} diff --git a/buildSrc/src/main/groovy/com/intellij/rt/coverage/utils/UpgradingClassVisitor.groovy b/buildSrc/src/main/groovy/com/intellij/rt/coverage/utils/UpgradingClassVisitor.groovy new file mode 100644 index 00000000..af671d4a --- /dev/null +++ b/buildSrc/src/main/groovy/com/intellij/rt/coverage/utils/UpgradingClassVisitor.groovy @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2024 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.rt.coverage.utils + +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.commons.JSRInlinerAdapter + +class UpgradingClassVisitor extends ClassVisitor { + private int version + + protected UpgradingClassVisitor(ClassVisitor classVisitor) { + super(Opcodes.ASM9, classVisitor) + } + + @Override + void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.version = version + if (version < Opcodes.V11) { + version = Opcodes.V11 + } + super.visit(version, access, name, signature, superName, interfaces) + } + + @Override + MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + def mv = super.visitMethod(access, name, descriptor, signature, exceptions) + if (version < Opcodes.V1_7) { + return new JSRInlinerAdapter(mv, access, name, descriptor, signature, exceptions) + } + return mv + } +} diff --git a/instrumentation/java7-utils/src/com/intellij/rt/coverage/util/IndyUtils.java b/instrumentation/java7-utils/src/com/intellij/rt/coverage/util/IndyUtils.java new file mode 100644 index 00000000..d6eef4c2 --- /dev/null +++ b/instrumentation/java7-utils/src/com/intellij/rt/coverage/util/IndyUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2024 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.rt.coverage.util; + +import com.intellij.rt.coverage.instrumentation.CoverageRuntime; + +import java.lang.invoke.CallSite; +import java.lang.invoke.ConstantCallSite; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +@SuppressWarnings("unused") +public class IndyUtils { + public static CallSite getHits(MethodHandles.Lookup lookup, String name, MethodType type, String className) { + return constant(CoverageRuntime.getHits(className)); + } + + public static CallSite getHitsMask(MethodHandles.Lookup lookup, String name, MethodType type, String className) { + return constant(CoverageRuntime.getHitsMask(className)); + } + + public static CallSite getTraceMask(MethodHandles.Lookup lookup, String name, MethodType type, String className) { + return constant(CoverageRuntime.getTraceMask(className)); + } + + public static CallSite loadClassData(MethodHandles.Lookup lookup, String name, MethodType type, String className) { + return constant(CoverageRuntime.loadClassData(className)); + } + + private static CallSite constant(Object cst) { + return new ConstantCallSite(MethodHandles.constant(Object.class, cst)); + } +} diff --git a/instrumentation/src/com/intellij/rt/coverage/instrumentation/CoverageTransformer.java b/instrumentation/src/com/intellij/rt/coverage/instrumentation/CoverageTransformer.java index 934f32ab..4b5aafd1 100644 --- a/instrumentation/src/com/intellij/rt/coverage/instrumentation/CoverageTransformer.java +++ b/instrumentation/src/com/intellij/rt/coverage/instrumentation/CoverageTransformer.java @@ -47,6 +47,8 @@ private CoverageDataAccess createDataAccess(String className, ClassReader cr) { if (OptionsUtil.FIELD_INSTRUMENTATION_ENABLED) { if (InstrumentationUtils.isCondyEnabled(cr)) { return new CondyCoverageDataAccess(createCondyInit(className, cr)); + } else if (InstrumentationUtils.isIndyEnabled(cr)) { + return new IndyCoverageDataAccess(createIndyInit(className, cr)); } else { return new FieldCoverageDataAccess(cr, className, createInit(className, cr, false)); } @@ -64,6 +66,17 @@ protected CoverageDataAccess.Init createInit(String className, ClassReader cr, b methodName, "(Ljava/lang/String;)" + arrayType, new Object[]{className}); } + protected CoverageDataAccess.Init createIndyInit(String className, ClassReader cr) { + boolean calculateHits = myProjectContext.getOptions().isCalculateHits; + String arrayType = calculateHits ? DataAccessUtil.HITS_ARRAY_TYPE : DataAccessUtil.MASK_ARRAY_TYPE; + String methodName = calculateHits ? "getHits" : "getHitsMask"; + return new CoverageDataAccess.Init( + "__$hits$__", arrayType, "com/intellij/rt/coverage/util/IndyUtils", methodName, + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;", + new Object[]{className} + ); + } + protected CoverageDataAccess.Init createCondyInit(String className, ClassReader cr) { boolean calculateHits = myProjectContext.getOptions().isCalculateHits; String arrayType = calculateHits ? DataAccessUtil.HITS_ARRAY_TYPE : DataAccessUtil.MASK_ARRAY_TYPE; diff --git a/instrumentation/src/com/intellij/rt/coverage/instrumentation/InstrumentationUtils.java b/instrumentation/src/com/intellij/rt/coverage/instrumentation/InstrumentationUtils.java index 1debcaa7..389b6ab6 100644 --- a/instrumentation/src/com/intellij/rt/coverage/instrumentation/InstrumentationUtils.java +++ b/instrumentation/src/com/intellij/rt/coverage/instrumentation/InstrumentationUtils.java @@ -68,6 +68,10 @@ public static boolean isCondyEnabled(ClassReader cr) { return OptionsUtil.CONDY_ENABLED && getBytecodeVersion(cr) >= Opcodes.V11; } + public static boolean isIndyEnabled(ClassReader cr) { + return OptionsUtil.INDY_ENABLED && getBytecodeVersion(cr) >= Opcodes.V1_7; + } + public static boolean isIntConstLoading(int opcode) { return Opcodes.ICONST_M1 <= opcode && opcode <= Opcodes.ICONST_5 || opcode == Opcodes.BIPUSH diff --git a/instrumentation/src/com/intellij/rt/coverage/instrumentation/dataAccess/DataAccessUtil.java b/instrumentation/src/com/intellij/rt/coverage/instrumentation/dataAccess/DataAccessUtil.java index 4ae39e09..98eb9135 100644 --- a/instrumentation/src/com/intellij/rt/coverage/instrumentation/dataAccess/DataAccessUtil.java +++ b/instrumentation/src/com/intellij/rt/coverage/instrumentation/dataAccess/DataAccessUtil.java @@ -21,6 +21,7 @@ import com.intellij.rt.coverage.instrumentation.data.InstrumentationData; import com.intellij.rt.coverage.instrumentation.data.Key; import com.intellij.rt.coverage.util.OptionsUtil; +import org.jetbrains.coverage.org.objectweb.asm.ClassReader; public class DataAccessUtil { public static final String HITS_ARRAY_TYPE = "[I"; @@ -31,14 +32,21 @@ public class DataAccessUtil { public static CoverageDataAccess createTestTrackingDataAccess(InstrumentationData data, boolean isArray) { String className = data.get(Key.CLASS_NAME); boolean fieldInstrumentation = OptionsUtil.FIELD_INSTRUMENTATION_ENABLED; - if (fieldInstrumentation && InstrumentationUtils.isCondyEnabled(data.get(Key.CLASS_READER))) { - CoverageDataAccess.Init init = isArray ? createTestTrackingArrayCondyInit(className) : createTestTrackingCondyInit(className); - return new CondyCoverageDataAccess(init); + if (fieldInstrumentation) { + ClassReader reader = data.get(Key.CLASS_READER); + if (InstrumentationUtils.isCondyEnabled(reader)) { + CoverageDataAccess.Init init = isArray ? createTestTrackingArrayCondyInit(className) : createTestTrackingCondyInit(className); + return new CondyCoverageDataAccess(init); + } else if (InstrumentationUtils.isIndyEnabled(reader)) { + CoverageDataAccess.Init init = isArray ? createTestTrackingArrayIndyInit(className) : createTestTrackingIndyInit(className); + return new IndyCoverageDataAccess(init); + } else { + CoverageDataAccess.Init init = isArray ? createTestTrackingArrayInit(className) : createTestTrackingInit(className, false); + return new FieldCoverageDataAccess(reader, className, init); + } } else { - CoverageDataAccess.Init init = isArray ? createTestTrackingArrayInit(className) : createTestTrackingInit(className, !fieldInstrumentation); - return fieldInstrumentation - ? new FieldCoverageDataAccess(data.get(Key.CLASS_READER), className, init) - : new NameCoverageDataAccess(init); + CoverageDataAccess.Init init = isArray ? createTestTrackingArrayInit(className) : createTestTrackingInit(className, true); + return new NameCoverageDataAccess(init); } } @@ -47,6 +55,14 @@ private static CoverageDataAccess.Init createTestTrackingInit(String className, needCache ? "loadClassDataCached" : "loadClassData", "(Ljava/lang/String;)" + InstrumentationUtils.OBJECT_TYPE, new Object[]{className}); } + private static CoverageDataAccess.Init createTestTrackingIndyInit(String className) { + return new CoverageDataAccess.Init( + "__$classData$__", InstrumentationUtils.OBJECT_TYPE, "com/intellij/rt/coverage/util/IndyUtils", "loadClassData", + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;", + new Object[]{className} + ); + } + private static CoverageDataAccess.Init createTestTrackingCondyInit(String className) { return new CoverageDataAccess.Init("__$classData$__", InstrumentationUtils.OBJECT_TYPE, "com/intellij/rt/coverage/util/CondyUtils", "loadClassData", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)" + InstrumentationUtils.OBJECT_TYPE, new Object[]{className}); @@ -57,6 +73,14 @@ private static CoverageDataAccess.Init createTestTrackingArrayInit(String classN "getTraceMask", "(Ljava/lang/String;)" + TEST_MASK_ARRAY_TYPE, new Object[]{className}); } + private static CoverageDataAccess.Init createTestTrackingArrayIndyInit(String className) { + return new CoverageDataAccess.Init( + "__$traceMask$__", TEST_MASK_ARRAY_TYPE, "com/intellij/rt/coverage/util/IndyUtils", "getTraceMask", + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;", + new Object[]{className} + ); + } + private static CoverageDataAccess.Init createTestTrackingArrayCondyInit(String className) { return new CoverageDataAccess.Init("__$traceMask$__", TEST_MASK_ARRAY_TYPE, "com/intellij/rt/coverage/util/CondyUtils", "getTraceMask", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)" + TEST_MASK_ARRAY_TYPE, new Object[]{className}); diff --git a/instrumentation/src/com/intellij/rt/coverage/instrumentation/dataAccess/IndyCoverageDataAccess.java b/instrumentation/src/com/intellij/rt/coverage/instrumentation/dataAccess/IndyCoverageDataAccess.java new file mode 100644 index 00000000..3d0d9830 --- /dev/null +++ b/instrumentation/src/com/intellij/rt/coverage/instrumentation/dataAccess/IndyCoverageDataAccess.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2022 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.rt.coverage.instrumentation.dataAccess; + +import com.intellij.rt.coverage.instrumentation.InstrumentationUtils; +import org.jetbrains.coverage.org.objectweb.asm.*; + +/** + * Loads coverage data using an INVOKEDYNAMIC (indy). + * After the first invocation, the array becomes bound to the call-site and no lookup is necessary. + * Supported for class files version 7+. + */ +public class IndyCoverageDataAccess extends CoverageDataAccess { + private final Handle myBsm; + + public IndyCoverageDataAccess(Init init) { + super(init); + myBsm = new Handle(Opcodes.H_INVOKESTATIC, init.initOwner, init.initName, init.initDesc, false); + } + + @Override + public void onMethodStart(MethodVisitor mv, int localVariable) { + mv.visitInvokeDynamicInsn( + myInit.name, + "()" + InstrumentationUtils.OBJECT_TYPE, + myBsm, + myInit.params + ); + if (!InstrumentationUtils.OBJECT_TYPE.equals(myInit.desc)) { + mv.visitTypeInsn(Opcodes.CHECKCAST, myInit.desc); + } + mv.visitVarInsn(Opcodes.ASTORE, localVariable); + } +} diff --git a/offline-runtime/java7-utils/src/com/intellij/rt/coverage/offline/IndyUtils.java b/offline-runtime/java7-utils/src/com/intellij/rt/coverage/offline/IndyUtils.java new file mode 100644 index 00000000..6ca283f0 --- /dev/null +++ b/offline-runtime/java7-utils/src/com/intellij/rt/coverage/offline/IndyUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2024 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.rt.coverage.offline; + +import java.lang.invoke.CallSite; +import java.lang.invoke.ConstantCallSite; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +@SuppressWarnings("unused") +public class IndyUtils { + public static CallSite getOrCreateHits(MethodHandles.Lookup lookup, String name, MethodType type, String className, int length) { + return constant(RawProjectInit.getOrCreateHits(className, length)); + } + + public static CallSite getOrCreateHitsMask(MethodHandles.Lookup lookup, String name, MethodType type, String className, int length) { + return constant(RawProjectInit.getOrCreateHitsMask(className, length)); + } + + private static CallSite constant(Object cst) { + return new ConstantCallSite(MethodHandles.constant(Object.class, cst)); + } +} diff --git a/reporter/src/com/intellij/rt/coverage/instrument/OfflineCoverageTransformer.java b/reporter/src/com/intellij/rt/coverage/instrument/OfflineCoverageTransformer.java index 3ba5b79d..a61cf83a 100644 --- a/reporter/src/com/intellij/rt/coverage/instrument/OfflineCoverageTransformer.java +++ b/reporter/src/com/intellij/rt/coverage/instrument/OfflineCoverageTransformer.java @@ -50,6 +50,19 @@ protected CoverageDataAccess.Init createInit(String className, ClassReader cr, b methodName, "(Ljava/lang/String;I)" + arrayType, new Object[]{className, length}); } + @Override + protected CoverageDataAccess.Init createIndyInit(String className, ClassReader cr) { + final int length = getRequiredArrayLength(cr); + boolean calculateHits = myProjectContext.getOptions().isCalculateHits; + String arrayType = calculateHits ? DataAccessUtil.HITS_ARRAY_TYPE : DataAccessUtil.MASK_ARRAY_TYPE; + String methodName = calculateHits ? "getOrCreateHits" : "getOrCreateHitsMask"; + return new CoverageDataAccess.Init( + "__$hits$__", arrayType, "com/intellij/rt/coverage/offline/IndyUtils", methodName, + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;", + new Object[]{className, length} + ); + } + @Override protected CoverageDataAccess.Init createCondyInit(String className, ClassReader cr) { final int length = getRequiredArrayLength(cr); diff --git a/reporter/test/com/intellij/rt/coverage/instrument/InstrumentatorTest.kt b/reporter/test/com/intellij/rt/coverage/instrument/InstrumentatorTest.kt index f9f87329..4f89abb2 100644 --- a/reporter/test/com/intellij/rt/coverage/instrument/InstrumentatorTest.kt +++ b/reporter/test/com/intellij/rt/coverage/instrument/InstrumentatorTest.kt @@ -99,6 +99,7 @@ private fun checkOfflineInstrumentation(roots: List, outputRoots: List()V + INVOKEDYNAMIC __$hits$__()Ljava/lang/Object; [ + // handle kind 0x6 : INVOKESTATIC + com/intellij/rt/coverage/util/IndyUtils.getHitsMask(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "testData.simple.branches.MyBranchedClass" + ] + CHECKCAST [Z + ASTORE 1 + L0 + LINENUMBER 22 L0 + ALOAD 1 + ICONST_0 + ICONST_1 + BASTORE + ALOAD 0 + INVOKESPECIAL java/lang/Object. ()V + RETURN + L1 + LOCALVARIABLE this LtestData/simple/branches/MyBranchedClass; L0 L1 0 + LOCALVARIABLE __$coverage_local$__ [Z L0 L1 1 + MAXSTACK = 3 + MAXLOCALS = 2 + + // access flags 0x11 + public final foo(I)V + INVOKEDYNAMIC __$hits$__()Ljava/lang/Object; [ + // handle kind 0x6 : INVOKESTATIC + com/intellij/rt/coverage/util/IndyUtils.getHitsMask(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "testData.simple.branches.MyBranchedClass" + ] + CHECKCAST [Z + ASTORE 2 + L0 + LINENUMBER 24 L0 + ALOAD 2 + ICONST_1 + ICONST_1 + BASTORE + ILOAD 1 + IFGE L1 + GOTO L2 + L1 + ALOAD 2 + ICONST_2 + ICONST_1 + BASTORE + GOTO L3 + L2 + ALOAD 2 + ICONST_3 + ICONST_1 + BASTORE + L4 + LINENUMBER 25 L4 + ALOAD 2 + ICONST_4 + ICONST_1 + BASTORE + LDC "LE" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + GOTO L5 + L3 + LINENUMBER 26 L3 + ALOAD 2 + ICONST_5 + ICONST_1 + BASTORE + L6 + FRAME APPEND [[Z] + ILOAD 1 + IFNE L7 + GOTO L8 + L7 + ALOAD 2 + BIPUSH 6 + ICONST_1 + BASTORE + GOTO L9 + L8 + ALOAD 2 + BIPUSH 7 + ICONST_1 + BASTORE + L10 + LINENUMBER 27 L10 + ALOAD 2 + BIPUSH 8 + ICONST_1 + BASTORE + LDC "EQ" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + GOTO L5 + L9 + LINENUMBER 29 L9 + ALOAD 2 + BIPUSH 9 + ICONST_1 + BASTORE + L11 + FRAME SAME + LDC "GE" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + L5 + LINENUMBER 31 L5 + FRAME SAME + RETURN + L12 + LOCALVARIABLE this LtestData/simple/branches/MyBranchedClass; L0 L12 0 + LOCALVARIABLE value I L0 L12 1 + LOCALVARIABLE __$coverage_local$__ [Z L0 L12 2 + MAXSTACK = 3 + MAXLOCALS = 3 +} diff --git a/test-kotlin/test-sources/resources/bytecode/simple/branches/branch_indy_with_hits.txt b/test-kotlin/test-sources/resources/bytecode/simple/branches/branch_indy_with_hits.txt new file mode 100644 index 00000000..8aded3dc --- /dev/null +++ b/test-kotlin/test-sources/resources/bytecode/simple/branches/branch_indy_with_hits.txt @@ -0,0 +1,158 @@ +// access flags 0x31 +public final class testData/simple/branches/MyBranchedClass { + + // compiled from: test.kt + + + // access flags 0x1 + public ()V + INVOKEDYNAMIC __$hits$__()Ljava/lang/Object; [ + // handle kind 0x6 : INVOKESTATIC + com/intellij/rt/coverage/util/IndyUtils.getHits(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "testData.simple.branches.MyBranchedClass" + ] + CHECKCAST [I + ASTORE 1 + L0 + LINENUMBER 22 L0 + ALOAD 1 + ICONST_0 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + ALOAD 0 + INVOKESPECIAL java/lang/Object. ()V + RETURN + L1 + LOCALVARIABLE this LtestData/simple/branches/MyBranchedClass; L0 L1 0 + LOCALVARIABLE __$coverage_local$__ [I L0 L1 1 + MAXSTACK = 4 + MAXLOCALS = 2 + + // access flags 0x11 + public final foo(I)V + INVOKEDYNAMIC __$hits$__()Ljava/lang/Object; [ + // handle kind 0x6 : INVOKESTATIC + com/intellij/rt/coverage/util/IndyUtils.getHits(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "testData.simple.branches.MyBranchedClass" + ] + CHECKCAST [I + ASTORE 2 + L0 + LINENUMBER 24 L0 + ALOAD 2 + ICONST_1 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + ILOAD 1 + IFGE L1 + GOTO L2 + L1 + ALOAD 2 + ICONST_2 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + GOTO L3 + L2 + ALOAD 2 + ICONST_3 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + L4 + LINENUMBER 25 L4 + ALOAD 2 + ICONST_4 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + LDC "LE" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + GOTO L5 + L3 + LINENUMBER 26 L3 + ALOAD 2 + ICONST_5 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + L6 + FRAME APPEND [[I] + ILOAD 1 + IFNE L7 + GOTO L8 + L7 + ALOAD 2 + BIPUSH 6 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + GOTO L9 + L8 + ALOAD 2 + BIPUSH 7 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + L10 + LINENUMBER 27 L10 + ALOAD 2 + BIPUSH 8 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + LDC "EQ" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + GOTO L5 + L9 + LINENUMBER 29 L9 + ALOAD 2 + BIPUSH 9 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + L11 + FRAME SAME + LDC "GE" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + L5 + LINENUMBER 31 L5 + FRAME SAME + RETURN + L12 + LOCALVARIABLE this LtestData/simple/branches/MyBranchedClass; L0 L12 0 + LOCALVARIABLE value I L0 L12 1 + LOCALVARIABLE __$coverage_local$__ [I L0 L12 2 + MAXSTACK = 4 + MAXLOCALS = 3 +} diff --git a/test-kotlin/test-sources/resources/bytecode/simple/branches/line_indy.txt b/test-kotlin/test-sources/resources/bytecode/simple/branches/line_indy.txt new file mode 100644 index 00000000..72f4ce3e --- /dev/null +++ b/test-kotlin/test-sources/resources/bytecode/simple/branches/line_indy.txt @@ -0,0 +1,104 @@ +// access flags 0x31 +public final class testData/simple/branches/MyBranchedClass { + + // compiled from: test.kt + + + // access flags 0x1 + public ()V + INVOKEDYNAMIC __$hits$__()Ljava/lang/Object; [ + // handle kind 0x6 : INVOKESTATIC + com/intellij/rt/coverage/util/IndyUtils.getHitsMask(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "testData.simple.branches.MyBranchedClass" + ] + CHECKCAST [Z + ASTORE 1 + L0 + LINENUMBER 22 L0 + ALOAD 1 + ICONST_0 + ICONST_1 + BASTORE + ALOAD 0 + INVOKESPECIAL java/lang/Object. ()V + RETURN + L1 + LOCALVARIABLE this LtestData/simple/branches/MyBranchedClass; L0 L1 0 + LOCALVARIABLE __$coverage_local$__ [Z L0 L1 1 + MAXSTACK = 3 + MAXLOCALS = 2 + + // access flags 0x11 + public final foo(I)V + INVOKEDYNAMIC __$hits$__()Ljava/lang/Object; [ + // handle kind 0x6 : INVOKESTATIC + com/intellij/rt/coverage/util/IndyUtils.getHitsMask(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "testData.simple.branches.MyBranchedClass" + ] + CHECKCAST [Z + ASTORE 2 + L0 + LINENUMBER 24 L0 + ALOAD 2 + ICONST_1 + ICONST_1 + BASTORE + ILOAD 1 + IFGE L1 + L2 + LINENUMBER 25 L2 + ALOAD 2 + ICONST_2 + ICONST_1 + BASTORE + LDC "LE" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + GOTO L3 + L1 + LINENUMBER 26 L1 + ALOAD 2 + ICONST_3 + ICONST_1 + BASTORE + L4 + FRAME APPEND [[Z] + ILOAD 1 + IFNE L5 + L6 + LINENUMBER 27 L6 + ALOAD 2 + ICONST_4 + ICONST_1 + BASTORE + LDC "EQ" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + GOTO L3 + L5 + LINENUMBER 29 L5 + ALOAD 2 + ICONST_5 + ICONST_1 + BASTORE + L7 + FRAME SAME + LDC "GE" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + L3 + LINENUMBER 31 L3 + FRAME SAME + RETURN + L8 + LOCALVARIABLE this LtestData/simple/branches/MyBranchedClass; L0 L8 0 + LOCALVARIABLE value I L0 L8 1 + LOCALVARIABLE __$coverage_local$__ [Z L0 L8 2 + MAXSTACK = 3 + MAXLOCALS = 3 +} diff --git a/test-kotlin/test-sources/resources/bytecode/simple/branches/line_indy_with_hits.txt b/test-kotlin/test-sources/resources/bytecode/simple/branches/line_indy_with_hits.txt new file mode 100644 index 00000000..e00d42c6 --- /dev/null +++ b/test-kotlin/test-sources/resources/bytecode/simple/branches/line_indy_with_hits.txt @@ -0,0 +1,122 @@ +// access flags 0x31 +public final class testData/simple/branches/MyBranchedClass { + + // compiled from: test.kt + + + // access flags 0x1 + public ()V + INVOKEDYNAMIC __$hits$__()Ljava/lang/Object; [ + // handle kind 0x6 : INVOKESTATIC + com/intellij/rt/coverage/util/IndyUtils.getHits(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "testData.simple.branches.MyBranchedClass" + ] + CHECKCAST [I + ASTORE 1 + L0 + LINENUMBER 22 L0 + ALOAD 1 + ICONST_0 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + ALOAD 0 + INVOKESPECIAL java/lang/Object. ()V + RETURN + L1 + LOCALVARIABLE this LtestData/simple/branches/MyBranchedClass; L0 L1 0 + LOCALVARIABLE __$coverage_local$__ [I L0 L1 1 + MAXSTACK = 4 + MAXLOCALS = 2 + + // access flags 0x11 + public final foo(I)V + INVOKEDYNAMIC __$hits$__()Ljava/lang/Object; [ + // handle kind 0x6 : INVOKESTATIC + com/intellij/rt/coverage/util/IndyUtils.getHits(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "testData.simple.branches.MyBranchedClass" + ] + CHECKCAST [I + ASTORE 2 + L0 + LINENUMBER 24 L0 + ALOAD 2 + ICONST_1 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + ILOAD 1 + IFGE L1 + L2 + LINENUMBER 25 L2 + ALOAD 2 + ICONST_2 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + LDC "LE" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + GOTO L3 + L1 + LINENUMBER 26 L1 + ALOAD 2 + ICONST_3 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + L4 + FRAME APPEND [[I] + ILOAD 1 + IFNE L5 + L6 + LINENUMBER 27 L6 + ALOAD 2 + ICONST_4 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + LDC "EQ" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + GOTO L3 + L5 + LINENUMBER 29 L5 + ALOAD 2 + ICONST_5 + DUP2 + IALOAD + ICONST_1 + IADD + IASTORE + L7 + FRAME SAME + LDC "GE" + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + SWAP + INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V + L3 + LINENUMBER 31 L3 + FRAME SAME + RETURN + L8 + LOCALVARIABLE this LtestData/simple/branches/MyBranchedClass; L0 L8 0 + LOCALVARIABLE value I L0 L8 1 + LOCALVARIABLE __$coverage_local$__ [I L0 L8 2 + MAXSTACK = 4 + MAXLOCALS = 3 +} diff --git a/test-kotlin/test-sources/test/com/intellij/rt/coverage/caseTests/InstrumentedBytecodeTest.kt b/test-kotlin/test-sources/test/com/intellij/rt/coverage/caseTests/InstrumentedBytecodeTest.kt index 57183699..b39554f4 100644 --- a/test-kotlin/test-sources/test/com/intellij/rt/coverage/caseTests/InstrumentedBytecodeTest.kt +++ b/test-kotlin/test-sources/test/com/intellij/rt/coverage/caseTests/InstrumentedBytecodeTest.kt @@ -50,10 +50,10 @@ class InstrumentedBytecodeTest { val expectedRoot = "bytecode/${testName.replace(".", "/")}" assertBytecode("$expectedRoot/original.txt", originalBytes) - val condyPossible = InstrumentationUtils.getBytecodeVersion(ClassReader(originalBytes)) >= Opcodes.V11 + val classVersion = InstrumentationUtils.getBytecodeVersion(ClassReader(originalBytes)) for (coverage in Coverage.values()) { for (hits in listOf(true, false)) { - val expectedCoverage = getExpectedCoverage(coverage, condyPossible) + val expectedCoverage = getExpectedCoverage(coverage, classVersion) val expectedFileName = createExpectedFileName(expectedRoot, expectedCoverage, null, hits) doTest(null, coverage, originalBytes, className, expectedFileName, hits) } @@ -61,16 +61,22 @@ class InstrumentedBytecodeTest { for (testTracking in TestTracking.values()) { val coverage = Coverage.BRANCH_FIELD val hits = false - val expectedCoverage = getExpectedCoverage(coverage, condyPossible) + val expectedCoverage = getExpectedCoverage(coverage, classVersion) val expectedFileName = createExpectedFileName(expectedRoot, expectedCoverage, testTracking, hits) doTest(testTracking, coverage, originalBytes, className, expectedFileName, hits) } } - private fun getExpectedCoverage(coverage: Coverage, condyPossible: Boolean): Coverage = - if (coverage.isCondyEnabled() && !condyPossible) { - if (coverage.isBranchCoverage()) Coverage.BRANCH_FIELD else Coverage.LINE_FIELD - } else coverage + private fun getExpectedCoverage(attempted: Coverage, classVersion: Int): Coverage { + var result = attempted + if (result.isCondyEnabled() && classVersion < Opcodes.V11) { + result = if (result.isBranchCoverage()) Coverage.BRANCH_INDY else Coverage.LINE_INDY + } + if (result.isIndyEnabled() && classVersion < Opcodes.V1_7) { + result = if (result.isBranchCoverage()) Coverage.BRANCH_FIELD else Coverage.LINE_FIELD + } + return result + } private fun doTest( testTracking: TestTracking?, @@ -79,24 +85,30 @@ class InstrumentedBytecodeTest { className: String, expectedFileName: String, calculateHits: Boolean, - ) = runWithOptions( - mapOf( + ) { + val systemProps = mutableMapOf( OptionsUtil::FIELD_INSTRUMENTATION_ENABLED to (coverage != Coverage.LINE && coverage != Coverage.BRANCH), - OptionsUtil::CONDY_ENABLED to coverage.isCondyEnabled(), OptionsUtil::CALCULATE_HITS_COUNT to calculateHits, ) - ) { - val testTrackingMode = testTracking.createMode() - val projectData = ProjectData(testTrackingMode?.createTestTrackingCallback(null)) - val options = InstrumentationOptions.Builder() - .setBranchCoverage(coverage.isBranchCoverage()) - .setTestTrackingMode(testTrackingMode) - .build() - val transformer = CoverageTransformer(projectData, ProjectContext(options)) - val bytes = transformer.instrument(originalBytes, className, null, false) - - assertBytecode(expectedFileName, bytes) + if (!coverage.isCondyEnabled()) { + systemProps[OptionsUtil::CONDY_ENABLED] = false + if (!coverage.isIndyEnabled()) { + systemProps[OptionsUtil::INDY_ENABLED] = false + } + } + runWithOptions(systemProps) { + val testTrackingMode = testTracking.createMode() + val projectData = ProjectData(testTrackingMode?.createTestTrackingCallback(null)) + val options = InstrumentationOptions.Builder() + .setBranchCoverage(coverage.isBranchCoverage()) + .setTestTrackingMode(testTrackingMode) + .build() + val transformer = CoverageTransformer(projectData, ProjectContext(options)) + val bytes = transformer.instrument(originalBytes, className, null, false) + + assertBytecode(expectedFileName, bytes) // File("resources", expectedFileName).writeText(getReadableBytecode(bytes).preprocess()) + } } private fun getExpectedFile(expectedFileName: String): File { @@ -126,6 +138,8 @@ private fun createExpectedFileName( Coverage.LINE_FIELD -> "line_field" Coverage.BRANCH -> "branch" Coverage.BRANCH_FIELD -> "branch_field" + Coverage.LINE_INDY -> "line_indy" + Coverage.BRANCH_INDY -> "branch_indy" Coverage.LINE_CONDY -> "line_condy" Coverage.BRANCH_CONDY -> "branch_condy" } diff --git a/test-kotlin/test-utils/src/com/intellij/rt/coverage/configuration.kt b/test-kotlin/test-utils/src/com/intellij/rt/coverage/configuration.kt index 8171ddbb..e7d8edd6 100644 --- a/test-kotlin/test-utils/src/com/intellij/rt/coverage/configuration.kt +++ b/test-kotlin/test-utils/src/com/intellij/rt/coverage/configuration.kt @@ -17,14 +17,24 @@ package com.intellij.rt.coverage enum class Coverage { - LINE, LINE_FIELD, BRANCH, BRANCH_FIELD, LINE_CONDY, BRANCH_CONDY; + LINE, LINE_FIELD, BRANCH, BRANCH_FIELD, LINE_INDY, BRANCH_INDY, LINE_CONDY, BRANCH_CONDY; - fun isBranchCoverage() = this == BRANCH || this == BRANCH_FIELD || this == BRANCH_CONDY + fun isBranchCoverage() = this == BRANCH || this == BRANCH_FIELD || this == BRANCH_INDY || this == BRANCH_CONDY + fun isIndyEnabled() = this == LINE_INDY || this == BRANCH_INDY fun isCondyEnabled() = this == LINE_CONDY || this == BRANCH_CONDY companion object { - fun valuesWithCondyWhenPossible() = - if (getVMVersion() >= 11) values() else values().filterNot { it.isCondyEnabled() }.toTypedArray() + fun valuesForCurrentRuntime(): List { + val result = entries.toMutableList() + val vmVersion = getVMVersion() + if (vmVersion < 11) { + result.removeAll { it.isCondyEnabled() } + } + if (vmVersion < 7) { + result.removeAll { it.isIndyEnabled() } + } + return result + } } } @@ -35,13 +45,14 @@ enum class TestTracking { fun getCoverageConfigurations() = if (System.getProperty("coverage.run.fast.tests") != null) { listOfNotNull( arrayOf(Coverage.BRANCH_FIELD, null), + arrayOf(Coverage.BRANCH_INDY, null).takeIf { getVMVersion() >= 7 }, arrayOf(Coverage.LINE_CONDY, null).takeIf { getVMVersion() >= 11 }, ).toTypedArray() } else { allTestTrackingModes() } -fun allTestTrackingModes() = Coverage.valuesWithCondyWhenPossible().toList() +fun allTestTrackingModes() = Coverage.valuesForCurrentRuntime() .product(TestTracking.values().toList().plus(null)) .map { it.toList().toTypedArray() }.toTypedArray() diff --git a/test-kotlin/test-utils/src/com/intellij/rt/coverage/runner.kt b/test-kotlin/test-utils/src/com/intellij/rt/coverage/runner.kt index e8ccdb49..74ac7637 100644 --- a/test-kotlin/test-utils/src/com/intellij/rt/coverage/runner.kt +++ b/test-kotlin/test-utils/src/com/intellij/rt/coverage/runner.kt @@ -141,7 +141,11 @@ internal fun runWithCoverage( mainClass: String = getTestFile(testName).mainClass ): ProjectData { when (coverage) { - Coverage.LINE_FIELD, Coverage.BRANCH_FIELD -> extraArgs.add("-Dcoverage.condy.enable=false") + Coverage.LINE_INDY, Coverage.BRANCH_INDY -> extraArgs.add("-Dcoverage.condy.enable=false") + Coverage.LINE_FIELD, Coverage.BRANCH_FIELD -> { + extraArgs.add("-Dcoverage.condy.enable=false") + extraArgs.add("-Dcoverage.indy.enable=false") + } Coverage.LINE, Coverage.BRANCH -> extraArgs.add("-Didea.new.tracing.coverage=false") else -> {} } diff --git a/tests/jmh.gradle b/tests/jmh.gradle index fadcb753..d38f76a1 100644 --- a/tests/jmh.gradle +++ b/tests/jmh.gradle @@ -34,25 +34,25 @@ dependencies { baseline "$group:$coverage_jar_name:$baselineVersion" } -def ijAgentParams(Configuration configuration, boolean branchCoverage) { +def ijAgentParams(Configuration configuration, boolean branchCoverage, String... jvmArgs) { def agentPath = configuration == configurations.head ? rootProject.file("dist").listFiles().find { it.name.startsWith(coverage_jar_name) }.absolutePath : configuration.find { it.name.startsWith(coverage_jar_name) }.absolutePath return [ "-javaagent:${agentPath}=${coverageFile} false false false ${!branchCoverage} org.joda.* org.apache.commons.*", - "-Didea.new.sampling.coverage=true", "-Didea.new.tracing.coverage=true" + *jvmArgs ] } def branchCoverage = true task BaselineCoverage(type: JavaExec) { - configureCompare(it) { ijAgentParams(configurations.baseline, branchCoverage) } + configureCompareWithNoAgent(it) { ijAgentParams(configurations.baseline, branchCoverage) } clear(it) } task HeadCoverage(type: JavaExec) { - configureCompare(it) { ijAgentParams(configurations.head, branchCoverage) } + configureCompareWithNoAgent(it) { ijAgentParams(configurations.head, branchCoverage) } clear(it) } @@ -66,6 +66,54 @@ task LineVsBranchCoverage(type: JavaExec) { clear(it) } +task CondyCoverage(type: JavaExec) { + configureBenchmark(it) { + ijAgentParams(configurations.head, branchCoverage) + } + clear(it) +} + +task IndyCoverage(type: JavaExec) { + configureBenchmark(it) { + ijAgentParams(configurations.head, branchCoverage, "-Dcoverage.condy.enable=false") + } + clear(it) +} + +task FieldCoverage(type: JavaExec) { + configureBenchmark(it) { + ijAgentParams(configurations.head, branchCoverage, "-Dcoverage.condy.enable=false", "-Dcoverage.indy.enable=false") + } + clear(it) +} + +task OldCoverage(type: JavaExec) { + configureBenchmark(it) { + ijAgentParams(configurations.head, branchCoverage, "-Didea.new.sampling.coverage=false", "-Didea.new.tracing.coverage=false") + } + clear(it) +} + +task IndyVsCondyCoverage { + configureComparison(it, IndyCoverage, CondyCoverage) + clear(it) +} + +task FieldVsIndyCoverage { + configureComparison(it, FieldCoverage, IndyCoverage) + clear(it) +} + +task OldVsFieldCoverage { + configureComparison(it, OldCoverage, FieldCoverage) + clear(it) +} + +task FieldVsCondyCoverage { + configureComparison(it, FieldCoverage, CondyCoverage) + clear(it) +} + def clear(Task task) { task.doLast { delete(coverageFile)