Skip to content

Commit 11eefac

Browse files
authored
Detect atomic primitive method calls dynamically (#336)
--------- Signed-off-by: Evgeniy Moiseenko <[email protected]>
1 parent 8c3ed5d commit 11eefac

File tree

9 files changed

+511
-304
lines changed

9 files changed

+511
-304
lines changed

bootstrap/src/sun/nio/ch/lincheck/EventTracker.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ interface EventTracker {
4545
fun afterReflectiveSetter(receiver: Any?, value: Any?)
4646

4747
fun beforeMethodCall(owner: Any?, className: String, methodName: String, codeLocation: Int, params: Array<Any?>)
48-
fun beforeAtomicMethodCall(owner: Any?, className: String, methodName: String, codeLocation: Int, params: Array<Any?>)
4948
fun onMethodCallReturn(result: Any?)
5049
fun onMethodCallException(t: Throwable)
5150

bootstrap/src/sun/nio/ch/lincheck/Injections.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,15 +249,6 @@ public static void beforeMethodCall(Object owner, String className, String metho
249249
getEventTracker().beforeMethodCall(owner, className, methodName, codeLocation, params);
250250
}
251251

252-
/**
253-
* Called from the instrumented code before any atomic method call.
254-
* This is just an optimization of [beforeMethodCall] for trusted
255-
* atomic constructs to avoid wrapping the invocations into try-finally blocks.
256-
*/
257-
public static void beforeAtomicMethodCall(Object owner, String className, String methodName, int codeLocation, Object[] params) {
258-
getEventTracker().beforeAtomicMethodCall(owner, className, methodName, codeLocation, params);
259-
}
260-
261252
/**
262253
* Called from the instrumented code after any method successful call, i.e., without any exception.
263254
*/

src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ManagedStrategy.kt

Lines changed: 51 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -878,69 +878,30 @@ abstract class ManagedStrategy(
878878
codeLocation: Int,
879879
params: Array<Any?>
880880
) {
881-
val guarantee = methodGuaranteeType(owner, className, methodName)
882-
when (guarantee) {
883-
ManagedGuaranteeType.IGNORE -> {
884-
if (collectTrace) {
885-
runInIgnoredSection {
886-
val params = if (isSuspendFunction(className, methodName, params)) {
887-
params.dropLast(1).toTypedArray()
888-
} else {
889-
params
890-
}
891-
beforeMethodCall(owner, currentThread, codeLocation, className, methodName, params)
892-
}
893-
}
894-
// It's important that this method can't be called inside runInIgnoredSection, as the ignored section
895-
// flag would be set to false when leaving runInIgnoredSection,
896-
// so enterIgnoredSection would have no effect
897-
enterIgnoredSection()
881+
val guarantee = runInIgnoredSection {
882+
val atomicMethodDescriptor = getAtomicMethodDescriptor(owner, methodName)
883+
val guarantee = when {
884+
(atomicMethodDescriptor != null) -> ManagedGuaranteeType.TREAT_AS_ATOMIC
885+
else -> methodGuaranteeType(owner, className, methodName)
898886
}
899-
900-
ManagedGuaranteeType.TREAT_AS_ATOMIC -> {
901-
runInIgnoredSection {
902-
if (collectTrace) {
903-
beforeMethodCall(owner, currentThread, codeLocation, className, methodName, params)
904-
}
905-
newSwitchPointOnAtomicMethodCall(codeLocation)
906-
}
907-
// It's important that this method can't be called inside runInIgnoredSection, as the ignored section
908-
// flag would be set to false when leaving runInIgnoredSection,
909-
// so enterIgnoredSection would have no effect
910-
enterIgnoredSection()
887+
if (owner == null && atomicMethodDescriptor == null && guarantee == null) { // static method
888+
LincheckJavaAgent.ensureClassHierarchyIsTransformed(className.canonicalClassName)
911889
}
912-
913-
null -> {
914-
if (owner == null) { // static method
915-
runInIgnoredSection {
916-
LincheckJavaAgent.ensureClassHierarchyIsTransformed(className.canonicalClassName)
917-
}
918-
}
919-
if (collectTrace) {
920-
runInIgnoredSection {
921-
val params = if (isSuspendFunction(className, methodName, params)) {
922-
params.dropLast(1).toTypedArray()
923-
} else {
924-
params
925-
}
926-
beforeMethodCall(owner, currentThread, codeLocation, className, methodName, params)
927-
}
928-
}
890+
if (collectTrace) {
891+
addBeforeMethodCallTracePoint(owner, codeLocation, className, methodName, params, atomicMethodDescriptor)
892+
}
893+
if (guarantee == ManagedGuaranteeType.TREAT_AS_ATOMIC) {
894+
newSwitchPointOnAtomicMethodCall(codeLocation)
929895
}
896+
guarantee
930897
}
931-
}
932-
933-
override fun beforeAtomicMethodCall(
934-
owner: Any?,
935-
className: String,
936-
methodName: String,
937-
codeLocation: Int,
938-
params: Array<Any?>
939-
) = runInIgnoredSection {
940-
if (collectTrace) {
941-
beforeMethodCall(owner, currentThread, codeLocation, className, methodName, params)
898+
if (guarantee == ManagedGuaranteeType.IGNORE ||
899+
guarantee == ManagedGuaranteeType.TREAT_AS_ATOMIC) {
900+
// It's important that this method can't be called inside runInIgnoredSection, as the ignored section
901+
// flag would be set to false when leaving runInIgnoredSection,
902+
// so enterIgnoredSection would have no effect
903+
enterIgnoredSection()
942904
}
943-
newSwitchPointOnAtomicMethodCall(codeLocation)
944905
}
945906

946907
override fun onMethodCallReturn(result: Any?) {
@@ -1056,20 +1017,15 @@ abstract class ManagedStrategy(
10561017
suspendedFunctionsStack[iThread].clear()
10571018
}
10581019

1059-
/**
1060-
* This method is invoked by a test thread
1061-
* before each method invocation.
1062-
* @param codeLocation the byte-code location identifier of this invocation
1063-
* @param iThread number of invoking thread
1064-
*/
1065-
private fun beforeMethodCall(
1020+
private fun addBeforeMethodCallTracePoint(
10661021
owner: Any?,
1067-
iThread: Int,
10681022
codeLocation: Int,
10691023
className: String,
10701024
methodName: String,
1071-
params: Array<Any?>,
1025+
methodParams: Array<Any?>,
1026+
atomicMethodDescriptor: AtomicMethodDescriptor?,
10721027
) {
1028+
val iThread = currentThread
10731029
val callStackTrace = callStackTrace[iThread]
10741030
val suspendedMethodStack = suspendedFunctionsStack[iThread]
10751031
val methodId = if (suspendedMethodStack.isNotEmpty()) {
@@ -1081,8 +1037,13 @@ abstract class ManagedStrategy(
10811037
} else {
10821038
methodCallNumber++
10831039
}
1040+
val params = if (isSuspendFunction(className, methodName, methodParams)) {
1041+
methodParams.dropLast(1).toTypedArray()
1042+
} else {
1043+
methodParams
1044+
}
10841045
// Code location of the new method call is currently the last one
1085-
val tracePoint = createBeforeMethodCallTracePoint(owner, iThread, className, methodName, params, codeLocation)
1046+
val tracePoint = createBeforeMethodCallTracePoint(owner, iThread, className, methodName, params, codeLocation, atomicMethodDescriptor)
10861047
methodCallTracePointStack[iThread] += tracePoint
10871048
callStackTrace.add(CallStackTraceElement(tracePoint, methodId))
10881049
if (owner == null) {
@@ -1098,7 +1059,8 @@ abstract class ManagedStrategy(
10981059
className: String,
10991060
methodName: String,
11001061
params: Array<Any?>,
1101-
codeLocation: Int
1062+
codeLocation: Int,
1063+
atomicMethodDescriptor: AtomicMethodDescriptor?,
11021064
): MethodCallTracePoint {
11031065
val callStackTrace = callStackTrace[iThread]
11041066
val tracePoint = MethodCallTracePoint(
@@ -1108,27 +1070,29 @@ abstract class ManagedStrategy(
11081070
methodName = methodName,
11091071
stackTraceElement = CodeLocations.stackTrace(codeLocation)
11101072
)
1111-
if (owner is VarHandle) {
1112-
return initializeVarHandleMethodCallTracePoint(tracePoint, owner, params)
1073+
// handle non-atomic methods
1074+
if (atomicMethodDescriptor == null) {
1075+
val ownerName = if (owner != null) findOwnerName(owner) else simpleClassName(className)
1076+
if (ownerName != null) {
1077+
tracePoint.initializeOwnerName(ownerName)
1078+
}
1079+
tracePoint.initializeParameters(params.map { adornedStringRepresentation(it) })
1080+
return tracePoint
11131081
}
1114-
if (owner is AtomicIntegerFieldUpdater<*> || owner is AtomicLongFieldUpdater<*> || owner is AtomicReferenceFieldUpdater<*, *>) {
1115-
return initializeAtomicUpdaterMethodCallTracePoint(tracePoint, owner, params)
1082+
// handle atomic methods
1083+
if (isVarHandle(owner)) {
1084+
return initializeVarHandleMethodCallTracePoint(tracePoint, owner as VarHandle, params)
11161085
}
1117-
if (isAtomicReference(owner)) {
1086+
if (isAtomicFieldUpdater(owner)) {
1087+
return initializeAtomicUpdaterMethodCallTracePoint(tracePoint, owner!!, params)
1088+
}
1089+
if (isAtomic(owner) || isAtomicArray(owner)) {
11181090
return initializeAtomicReferenceMethodCallTracePoint(tracePoint, owner!!, params)
11191091
}
11201092
if (isUnsafe(owner)) {
11211093
return initializeUnsafeMethodCallTracePoint(tracePoint, owner!!, params)
11221094
}
1123-
1124-
tracePoint.initializeParameters(params.map { adornedStringRepresentation(it) })
1125-
1126-
val ownerName = if (owner != null) findOwnerName(owner) else simpleClassName(className)
1127-
if (ownerName != null) {
1128-
tracePoint.initializeOwnerName(ownerName)
1129-
}
1130-
1131-
return tracePoint
1095+
error("Unknown atomic method $className::$methodName")
11321096
}
11331097

11341098
private fun simpleClassName(className: String) = className.takeLastWhile { it != '/' }
@@ -1178,10 +1142,6 @@ abstract class ManagedStrategy(
11781142
tracePoint.initializeOwnerName((receiverName?.let { "$it." } ?: "") + "${atomicReferenceInfo.fieldName}[${atomicReferenceInfo.index}]")
11791143
tracePoint.initializeParameters(params.drop(1).map { adornedStringRepresentation(it) })
11801144
}
1181-
AtomicReferenceMethodType.TreatAsDefaultMethod -> {
1182-
tracePoint.initializeOwnerName(adornedStringRepresentation(receiver))
1183-
tracePoint.initializeParameters(params.map { adornedStringRepresentation(it) })
1184-
}
11851145
is AtomicReferenceInstanceMethod -> {
11861146
val receiverName = findOwnerName(atomicReferenceInfo.owner)
11871147
tracePoint.initializeOwnerName(receiverName?.let { "$it.${atomicReferenceInfo.fieldName}" } ?: atomicReferenceInfo.fieldName)
@@ -1195,6 +1155,10 @@ abstract class ManagedStrategy(
11951155
tracePoint.initializeOwnerName("${atomicReferenceInfo.ownerClass.simpleName}.${atomicReferenceInfo.fieldName}[${atomicReferenceInfo.index}]")
11961156
tracePoint.initializeParameters(params.drop(1).map { adornedStringRepresentation(it) })
11971157
}
1158+
AtomicReferenceMethodType.TreatAsDefaultMethod -> {
1159+
tracePoint.initializeOwnerName(adornedStringRepresentation(receiver))
1160+
tracePoint.initializeParameters(params.map { adornedStringRepresentation(it) })
1161+
}
11981162
}
11991163
return tracePoint
12001164
}
@@ -1237,20 +1201,6 @@ abstract class ManagedStrategy(
12371201
return tracePoint
12381202
}
12391203

1240-
private fun isAtomicReference(receiver: Any?) = receiver is AtomicReference<*> ||
1241-
receiver is AtomicLong ||
1242-
receiver is AtomicInteger ||
1243-
receiver is AtomicBoolean ||
1244-
receiver is AtomicIntegerArray ||
1245-
receiver is AtomicReferenceArray<*> ||
1246-
receiver is AtomicLongArray
1247-
1248-
private fun isUnsafe(receiver: Any?): Boolean {
1249-
if (receiver == null) return false
1250-
val className = receiver::class.java.name
1251-
return className == "sun.misc.Unsafe" || className == "jdk.internal.misc.Unsafe"
1252-
}
1253-
12541204
/**
12551205
* Returns beautiful string representation of the [owner].
12561206
* If the [owner] is `this` of the current method, then returns `null`.

src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/MethodCallTransformer.kt

Lines changed: 18 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
package org.jetbrains.kotlinx.lincheck.transformation.transformers
1212

13+
import org.jetbrains.kotlinx.lincheck.*
1314
import org.jetbrains.kotlinx.lincheck.transformation.*
15+
import org.jetbrains.kotlinx.lincheck.util.*
1416
import org.objectweb.asm.Opcodes.*
1517
import org.objectweb.asm.Type
1618
import org.objectweb.asm.Type.*
@@ -40,17 +42,6 @@ internal class MethodCallTransformer(
4042
}
4143
return
4244
}
43-
if (isAtomicPrimitiveMethod(owner, name)) {
44-
invokeIfInTestingCode(
45-
original = {
46-
visitMethodInsn(opcode, owner, name, desc, itf)
47-
},
48-
code = {
49-
processAtomicMethodCall(desc, opcode, owner, name, itf)
50-
}
51-
)
52-
return
53-
}
5445
invokeIfInTestingCode(
5546
original = {
5647
visitMethodInsn(opcode, owner, name, desc, itf)
@@ -104,57 +95,6 @@ internal class MethodCallTransformer(
10495
// STACK: result
10596
}
10697

107-
private fun processAtomicMethodCall(desc: String, opcode: Int, owner: String, name: String, itf: Boolean) = adapter.run {
108-
// In the cases of Atomic*FieldUpdater, Unsafe and VarHandle we edit
109-
// the params list before creating a trace point
110-
// to remove redundant parameters as receiver and offset.
111-
// To determine how we should process it, we provide the owner instance.
112-
val provideOwner = opcode != INVOKESTATIC && (
113-
isAtomicClass(owner) ||
114-
isAtomicArrayClass(owner) ||
115-
owner == "sun/misc/Unsafe" ||
116-
owner == "jdk/internal/misc/Unsafe" ||
117-
owner == "java/lang/invoke/VarHandle" ||
118-
(owner.startsWith("java/util/concurrent/atomic") && owner.endsWith("FieldUpdater"))
119-
)
120-
// STACK [INVOKEVIRTUAL]: owner, arguments
121-
// STACK [INVOKESTATIC] : arguments
122-
val argumentLocals = storeArguments(desc)
123-
// STACK [INVOKEVIRTUAL]: owner
124-
// STACK [INVOKESTATIC] : <empty>
125-
if (provideOwner) {
126-
dup()
127-
} else {
128-
visitInsn(ACONST_NULL)
129-
}
130-
// STACK [INVOKEVIRTUAL atomic updater]: owner, owner
131-
// STACK [INVOKESTATIC atomic updater]: null
132-
// STACK [INVOKEVIRTUAL atomic]: owner
133-
// STACK [INVOKESTATIC atomic]: <empty>
134-
push(owner)
135-
push(name)
136-
loadNewCodeLocationId()
137-
// STACK [INVOKEVIRTUAL atomic updater]: owner, owner, className, methodName, codeLocation
138-
// STACK [INVOKESTATIC atomic updater]: null , className, methodName, codeLocation
139-
// STACK [INVOKEVIRTUAL atomic]: owner, className, methodName, codeLocation
140-
// STACK [INVOKESTATIC atomic]: className, methodName, codeLocation
141-
pushArray(argumentLocals)
142-
// STACK: ..., argumentsArray
143-
invokeStatic(Injections::beforeAtomicMethodCall)
144-
invokeBeforeEventIfPluginEnabled("atomic method call $methodName")
145-
// STACK [INVOKEVIRTUAL]: owner
146-
// STACK [INVOKESTATIC] : <empty>
147-
loadLocals(argumentLocals)
148-
// STACK [INVOKEVIRTUAL]: owner, arguments
149-
// STACK [INVOKESTATIC] : arguments
150-
invokeInIgnoredSection {
151-
visitMethodInsn(opcode, owner, name, desc, itf)
152-
}
153-
// STACK: result
154-
processMethodCallResult(desc)
155-
// STACK: result
156-
}
157-
15898
private fun processMethodCallResult(desc: String) = adapter.run {
15999
// STACK: result?
160100
val resultType = Type.getReturnType(desc)
@@ -173,39 +113,21 @@ internal class MethodCallTransformer(
173113
}
174114
}
175115

176-
private fun isIgnoredMethod(owner: String, methodName: String) =
177-
owner.startsWith("sun/nio/ch/lincheck/") ||
178-
owner.startsWith("org/jetbrains/kotlinx/lincheck/") ||
179-
owner == "kotlin/jvm/internal/Intrinsics" ||
180-
owner == "java/util/Objects" ||
181-
owner == "java/lang/String" ||
182-
owner == "java/lang/Boolean" ||
183-
owner == "java/lang/Long" ||
184-
owner == "java/lang/Integer" ||
185-
owner == "java/lang/Short" ||
186-
owner == "java/lang/Byte" ||
187-
owner == "java/lang/Double" ||
188-
owner == "java/lang/Float" ||
189-
owner == "java/util/Locale" ||
190-
owner == "org/slf4j/helpers/Util" ||
191-
owner == "java/util/Properties"
192-
193-
private fun isAtomicClass(className: String) =
194-
className == "java/util/concurrent/atomic/AtomicInteger" ||
195-
className == "java/util/concurrent/atomic/AtomicLong" ||
196-
className == "java/util/concurrent/atomic/AtomicBoolean" ||
197-
className == "java/util/concurrent/atomic/AtomicReference"
198-
199-
private fun isAtomicArrayClass(className: String) =
200-
className == "java/util/concurrent/atomic/AtomicIntegerArray" ||
201-
className == "java/util/concurrent/atomic/AtomicLongArray" ||
202-
className == "java/util/concurrent/atomic/AtomicReferenceArray"
203-
204-
private fun isAtomicPrimitiveMethod(owner: String, methodName: String) =
205-
owner == "sun/misc/Unsafe" ||
206-
owner == "jdk/internal/misc/Unsafe" ||
207-
owner == "java/lang/invoke/VarHandle" ||
208-
owner.startsWith("java/util/concurrent/") && (owner.contains("Atomic")) ||
209-
owner.startsWith("kotlinx/atomicfu/") && (owner.contains("Atomic"))
116+
private fun isIgnoredMethod(className: String, methodName: String) =
117+
className.startsWith("sun/nio/ch/lincheck/") ||
118+
className.startsWith("org/jetbrains/kotlinx/lincheck/") ||
119+
className == "kotlin/jvm/internal/Intrinsics" ||
120+
className == "java/util/Objects" ||
121+
className == "java/lang/String" ||
122+
className == "java/lang/Boolean" ||
123+
className == "java/lang/Long" ||
124+
className == "java/lang/Integer" ||
125+
className == "java/lang/Short" ||
126+
className == "java/lang/Byte" ||
127+
className == "java/lang/Double" ||
128+
className == "java/lang/Float" ||
129+
className == "java/util/Locale" ||
130+
className == "org/slf4j/helpers/Util" ||
131+
className == "java/util/Properties"
210132

211133
}

0 commit comments

Comments
 (0)