Skip to content

Commit bbec4c4

Browse files
committed
New: Add INVOKEDYNAMIC instrumentation.
1 parent e5cd723 commit bbec4c4

File tree

16 files changed

+779
-36
lines changed

16 files changed

+779
-36
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2000-2024 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.intellij.rt.coverage.util;
18+
19+
import com.intellij.rt.coverage.instrumentation.CoverageRuntime;
20+
21+
import java.lang.invoke.CallSite;
22+
import java.lang.invoke.ConstantCallSite;
23+
import java.lang.invoke.MethodHandles;
24+
import java.lang.invoke.MethodType;
25+
26+
@SuppressWarnings("unused")
27+
public class IndyUtils {
28+
public static CallSite getHits(MethodHandles.Lookup lookup, String name, MethodType type, String className) {
29+
return constant(CoverageRuntime.getHits(className));
30+
}
31+
32+
public static CallSite getHitsMask(MethodHandles.Lookup lookup, String name, MethodType type, String className) {
33+
return constant(CoverageRuntime.getHitsMask(className));
34+
}
35+
36+
public static CallSite getTraceMask(MethodHandles.Lookup lookup, String name, MethodType type, String className) {
37+
return constant(CoverageRuntime.getTraceMask(className));
38+
}
39+
40+
public static CallSite loadClassData(MethodHandles.Lookup lookup, String name, MethodType type, String className) {
41+
return constant(CoverageRuntime.loadClassData(className));
42+
}
43+
44+
private static CallSite constant(Object cst) {
45+
return new ConstantCallSite(MethodHandles.constant(Object.class, cst));
46+
}
47+
}

instrumentation/src/com/intellij/rt/coverage/instrumentation/CoverageTransformer.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ private CoverageDataAccess createDataAccess(String className, ClassReader cr) {
4747
if (OptionsUtil.FIELD_INSTRUMENTATION_ENABLED) {
4848
if (InstrumentationUtils.isCondyEnabled(cr)) {
4949
return new CondyCoverageDataAccess(createCondyInit(className, cr));
50+
} else if (InstrumentationUtils.isIndyEnabled(cr)) {
51+
return new IndyCoverageDataAccess(createIndyInit(className, cr));
5052
} else {
5153
return new FieldCoverageDataAccess(cr, className, createInit(className, cr, false));
5254
}
@@ -64,6 +66,17 @@ protected CoverageDataAccess.Init createInit(String className, ClassReader cr, b
6466
methodName, "(Ljava/lang/String;)" + arrayType, new Object[]{className});
6567
}
6668

69+
protected CoverageDataAccess.Init createIndyInit(String className, ClassReader cr) {
70+
boolean calculateHits = myProjectContext.getOptions().isCalculateHits;
71+
String arrayType = calculateHits ? DataAccessUtil.HITS_ARRAY_TYPE : DataAccessUtil.MASK_ARRAY_TYPE;
72+
String methodName = calculateHits ? "getHits" : "getHitsMask";
73+
return new CoverageDataAccess.Init(
74+
"__$hits$__", arrayType, "com/intellij/rt/coverage/util/IndyUtils", methodName,
75+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;",
76+
new Object[]{className}
77+
);
78+
}
79+
6780
protected CoverageDataAccess.Init createCondyInit(String className, ClassReader cr) {
6881
boolean calculateHits = myProjectContext.getOptions().isCalculateHits;
6982
String arrayType = calculateHits ? DataAccessUtil.HITS_ARRAY_TYPE : DataAccessUtil.MASK_ARRAY_TYPE;

instrumentation/src/com/intellij/rt/coverage/instrumentation/InstrumentationUtils.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ public static boolean isCondyEnabled(ClassReader cr) {
6868
return OptionsUtil.CONDY_ENABLED && getBytecodeVersion(cr) >= Opcodes.V11;
6969
}
7070

71+
public static boolean isIndyEnabled(ClassReader cr) {
72+
return OptionsUtil.INDY_ENABLED && getBytecodeVersion(cr) >= Opcodes.V1_7;
73+
}
74+
7175
public static boolean isIntConstLoading(int opcode) {
7276
return Opcodes.ICONST_M1 <= opcode && opcode <= Opcodes.ICONST_5
7377
|| opcode == Opcodes.BIPUSH

instrumentation/src/com/intellij/rt/coverage/instrumentation/dataAccess/DataAccessUtil.java

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.intellij.rt.coverage.instrumentation.data.InstrumentationData;
2222
import com.intellij.rt.coverage.instrumentation.data.Key;
2323
import com.intellij.rt.coverage.util.OptionsUtil;
24+
import org.jetbrains.coverage.org.objectweb.asm.ClassReader;
2425

2526
public class DataAccessUtil {
2627
public static final String HITS_ARRAY_TYPE = "[I";
@@ -31,14 +32,21 @@ public class DataAccessUtil {
3132
public static CoverageDataAccess createTestTrackingDataAccess(InstrumentationData data, boolean isArray) {
3233
String className = data.get(Key.CLASS_NAME);
3334
boolean fieldInstrumentation = OptionsUtil.FIELD_INSTRUMENTATION_ENABLED;
34-
if (fieldInstrumentation && InstrumentationUtils.isCondyEnabled(data.get(Key.CLASS_READER))) {
35-
CoverageDataAccess.Init init = isArray ? createTestTrackingArrayCondyInit(className) : createTestTrackingCondyInit(className);
36-
return new CondyCoverageDataAccess(init);
35+
if (fieldInstrumentation) {
36+
ClassReader reader = data.get(Key.CLASS_READER);
37+
if (InstrumentationUtils.isCondyEnabled(reader)) {
38+
CoverageDataAccess.Init init = isArray ? createTestTrackingArrayCondyInit(className) : createTestTrackingCondyInit(className);
39+
return new CondyCoverageDataAccess(init);
40+
} else if (InstrumentationUtils.isIndyEnabled(reader)) {
41+
CoverageDataAccess.Init init = isArray ? createTestTrackingArrayIndyInit(className) : createTestTrackingIndyInit(className);
42+
return new IndyCoverageDataAccess(init);
43+
} else {
44+
CoverageDataAccess.Init init = isArray ? createTestTrackingArrayInit(className) : createTestTrackingInit(className, false);
45+
return new FieldCoverageDataAccess(reader, className, init);
46+
}
3747
} else {
38-
CoverageDataAccess.Init init = isArray ? createTestTrackingArrayInit(className) : createTestTrackingInit(className, !fieldInstrumentation);
39-
return fieldInstrumentation
40-
? new FieldCoverageDataAccess(data.get(Key.CLASS_READER), className, init)
41-
: new NameCoverageDataAccess(init);
48+
CoverageDataAccess.Init init = isArray ? createTestTrackingArrayInit(className) : createTestTrackingInit(className, true);
49+
return new NameCoverageDataAccess(init);
4250
}
4351
}
4452

@@ -47,6 +55,14 @@ private static CoverageDataAccess.Init createTestTrackingInit(String className,
4755
needCache ? "loadClassDataCached" : "loadClassData", "(Ljava/lang/String;)" + InstrumentationUtils.OBJECT_TYPE, new Object[]{className});
4856
}
4957

58+
private static CoverageDataAccess.Init createTestTrackingIndyInit(String className) {
59+
return new CoverageDataAccess.Init(
60+
"__$classData$__", InstrumentationUtils.OBJECT_TYPE, "com/intellij/rt/coverage/util/IndyUtils", "loadClassData",
61+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;",
62+
new Object[]{className}
63+
);
64+
}
65+
5066
private static CoverageDataAccess.Init createTestTrackingCondyInit(String className) {
5167
return new CoverageDataAccess.Init("__$classData$__", InstrumentationUtils.OBJECT_TYPE, "com/intellij/rt/coverage/util/CondyUtils",
5268
"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
5773
"getTraceMask", "(Ljava/lang/String;)" + TEST_MASK_ARRAY_TYPE, new Object[]{className});
5874
}
5975

76+
private static CoverageDataAccess.Init createTestTrackingArrayIndyInit(String className) {
77+
return new CoverageDataAccess.Init(
78+
"__$traceMask$__", TEST_MASK_ARRAY_TYPE, "com/intellij/rt/coverage/util/IndyUtils", "getTraceMask",
79+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;",
80+
new Object[]{className}
81+
);
82+
}
83+
6084
private static CoverageDataAccess.Init createTestTrackingArrayCondyInit(String className) {
6185
return new CoverageDataAccess.Init("__$traceMask$__", TEST_MASK_ARRAY_TYPE, "com/intellij/rt/coverage/util/CondyUtils",
6286
"getTraceMask", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)" + TEST_MASK_ARRAY_TYPE, new Object[]{className});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2000-2022 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.intellij.rt.coverage.instrumentation.dataAccess;
18+
19+
import com.intellij.rt.coverage.instrumentation.InstrumentationUtils;
20+
import org.jetbrains.coverage.org.objectweb.asm.*;
21+
22+
/**
23+
* Loads coverage data using an INVOKEDYNAMIC (indy).
24+
* After the first invocation, the array becomes bound to the call-site and no lookup is necessary.
25+
* Supported for class files version 7+.
26+
*/
27+
public class IndyCoverageDataAccess extends CoverageDataAccess {
28+
private final Handle myBsm;
29+
30+
public IndyCoverageDataAccess(Init init) {
31+
super(init);
32+
myBsm = new Handle(Opcodes.H_INVOKESTATIC, init.initOwner, init.initName, init.initDesc, false);
33+
}
34+
35+
@Override
36+
public void onMethodStart(MethodVisitor mv, int localVariable) {
37+
mv.visitInvokeDynamicInsn(
38+
myInit.name,
39+
"()" + InstrumentationUtils.OBJECT_TYPE,
40+
myBsm,
41+
myInit.params
42+
);
43+
if (!InstrumentationUtils.OBJECT_TYPE.equals(myInit.desc)) {
44+
mv.visitTypeInsn(Opcodes.CHECKCAST, myInit.desc);
45+
}
46+
mv.visitVarInsn(Opcodes.ASTORE, localVariable);
47+
}
48+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2000-2024 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.intellij.rt.coverage.offline;
18+
19+
import java.lang.invoke.CallSite;
20+
import java.lang.invoke.ConstantCallSite;
21+
import java.lang.invoke.MethodHandles;
22+
import java.lang.invoke.MethodType;
23+
24+
@SuppressWarnings("unused")
25+
public class IndyUtils {
26+
public static CallSite getOrCreateHits(MethodHandles.Lookup lookup, String name, MethodType type, String className, int length) {
27+
return constant(RawProjectInit.getOrCreateHits(className, length));
28+
}
29+
30+
public static CallSite getOrCreateHitsMask(MethodHandles.Lookup lookup, String name, MethodType type, String className, int length) {
31+
return constant(RawProjectInit.getOrCreateHitsMask(className, length));
32+
}
33+
34+
private static CallSite constant(Object cst) {
35+
return new ConstantCallSite(MethodHandles.constant(Object.class, cst));
36+
}
37+
}

reporter/src/com/intellij/rt/coverage/instrument/OfflineCoverageTransformer.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ protected CoverageDataAccess.Init createInit(String className, ClassReader cr, b
5050
methodName, "(Ljava/lang/String;I)" + arrayType, new Object[]{className, length});
5151
}
5252

53+
@Override
54+
protected CoverageDataAccess.Init createIndyInit(String className, ClassReader cr) {
55+
final int length = getRequiredArrayLength(cr);
56+
boolean calculateHits = myProjectContext.getOptions().isCalculateHits;
57+
String arrayType = calculateHits ? DataAccessUtil.HITS_ARRAY_TYPE : DataAccessUtil.MASK_ARRAY_TYPE;
58+
String methodName = calculateHits ? "getOrCreateHits" : "getOrCreateHitsMask";
59+
return new CoverageDataAccess.Init(
60+
"__$hits$__", arrayType, "com/intellij/rt/coverage/offline/IndyUtils", methodName,
61+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;",
62+
new Object[]{className, length}
63+
);
64+
}
65+
5366
@Override
5467
protected CoverageDataAccess.Init createCondyInit(String className, ClassReader cr) {
5568
final int length = getRequiredArrayLength(cr);

reporter/test/com/intellij/rt/coverage/instrument/InstrumentatorTest.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ private fun checkOfflineInstrumentation(roots: List<File>, outputRoots: List<Fil
9999

100100
private fun ByteArray.isInstrumented(): Boolean {
101101
var hasInstrumentation = false
102+
var hasIndyInstrumentation = false
102103
var hasCondyInstrumentation = false
103104
val visitor = object : ClassVisitor(Opcodes.API_VERSION) {
104105
override fun visitMethod(
@@ -123,10 +124,24 @@ private fun ByteArray.isInstrumented(): Boolean {
123124
hasCondyInstrumentation = true
124125
}
125126
}
127+
128+
override fun visitInvokeDynamicInsn(
129+
name: String?,
130+
descriptor: String?,
131+
bootstrapMethodHandle: Handle?,
132+
vararg bootstrapMethodArguments: Any?
133+
) {
134+
if (name == "__\$hits\$__") {
135+
hasIndyInstrumentation = true
136+
}
137+
}
126138
}
127139
}
128140
}
129141
ClassReader(this).accept(visitor, ClassReader.SKIP_FRAMES or ClassReader.SKIP_DEBUG)
130-
check(!(hasInstrumentation && hasCondyInstrumentation))
131-
return hasInstrumentation || hasCondyInstrumentation
142+
val instrumentationsFound = booleanArrayOf(
143+
hasInstrumentation, hasIndyInstrumentation, hasCondyInstrumentation
144+
).count { it }
145+
check(instrumentationsFound <= 1)
146+
return instrumentationsFound == 1
132147
}

src/com/intellij/rt/coverage/util/OptionsUtil.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class OptionsUtil {
2222
&& "true".equals(System.getProperty("idea.new.tracing.coverage", "true"));
2323
public static final boolean NEW_TEST_TRACKING_ENABLED = "true".equals(System.getProperty("idea.new.test.tracking.coverage", "true"));
2424
public static boolean CONDY_ENABLED = "true".equals(System.getProperty("coverage.condy.enable", "true"));
25+
public static boolean INDY_ENABLED = "true".equals(System.getProperty("coverage.indy.enable", "true"));
2526
public static final boolean INSTRUCTIONS_COVERAGE_ENABLED = "true".equals(System.getProperty("coverage.instructions.enable", "false"));
2627
public static boolean CALCULATE_HITS_COUNT = "true".equals(System.getProperty("idea.coverage.calculate.hits", "false"));
2728
public static boolean IGNORE_LOCAL_FUNCTIONS_IN_IGNORED_METHODS = "true".equals(System.getProperty("idea.coverage.ignore.local.functions.in.ignored.methods", "true"));

0 commit comments

Comments
 (0)