-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Static memory snapshot: add support for Unsafe, VarHandle, AFU, kotlinx.atomicfu #429
base: develop
Are you sure you want to change the base?
Changes from 22 commits
1ac184f
03236cf
cb3b56f
1f71651
609acf5
26ffcd7
f6f8a43
333a850
ae3e92d
4650ce0
8b68f53
bd6db91
ce3d410
43d404c
ddc4cb9
7b76402
cb63b27
e36dbeb
154e98b
67dffec
b0d83e0
2fd1a05
765d728
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,7 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater | |
internal object AtomicFieldUpdaterNames { | ||
|
||
@Suppress("DEPRECATION") | ||
internal fun getAtomicFieldUpdaterName(updater: Any): String? { | ||
internal fun getAtomicFieldUpdaterName(updater: Any): AtomicFieldUpdaterDescriptor? { | ||
if (updater !is AtomicIntegerFieldUpdater<*> && updater !is AtomicLongFieldUpdater<*> && updater !is AtomicReferenceFieldUpdater<*, *>) { | ||
throw IllegalArgumentException("Provided object is not a recognized Atomic*FieldUpdater type.") | ||
} | ||
|
@@ -37,11 +37,22 @@ internal object AtomicFieldUpdaterNames { | |
val offsetField = updater.javaClass.getDeclaredField("offset") | ||
val offset = UNSAFE.getLong(updater, UNSAFE.objectFieldOffset(offsetField)) | ||
|
||
return findFieldNameByOffsetViaUnsafe(targetType, offset) | ||
return findFieldNameByOffsetViaUnsafe(targetType, offset).let { fieldName -> | ||
if (fieldName != null) AtomicFieldUpdaterDescriptor(targetType, fieldName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please always add |
||
else null | ||
} | ||
} catch (t: Throwable) { | ||
t.printStackTrace() | ||
} | ||
|
||
return null // Field not found | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Descriptor of the Java AFU instance. | ||
*/ | ||
internal data class AtomicFieldUpdaterDescriptor( | ||
val targetType: Class<*>, | ||
val fieldName: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -950,6 +950,63 @@ abstract class ManagedStrategy( | |
staticMemorySnapshot.trackObjects(objs) | ||
} | ||
|
||
|
||
|
||
/** | ||
* Tracks fields that are accessed via System.arraycopy, Unsafe API, VarHandle API, Java AFU API, and kotlinx.atomicfu. | ||
*/ | ||
private fun processMethodEffectOnStaticSnapshot(owner: Any?, params: Array<Any?>) { | ||
when { | ||
// Unsafe API | ||
isUnsafe(owner) -> { | ||
val methodType: UnsafeName = UnsafeNames.getMethodCallType(params) | ||
when (methodType) { | ||
is UnsafeInstanceMethod -> { | ||
staticMemorySnapshot.trackField(methodType.owner, methodType.owner.javaClass, methodType.fieldName) | ||
} | ||
is UnsafeStaticMethod -> { | ||
staticMemorySnapshot.trackField(null, methodType.clazz, methodType.fieldName) | ||
} | ||
is UnsafeArrayMethod -> { | ||
staticMemorySnapshot.trackArrayCell(methodType.array, methodType.index) | ||
} | ||
else -> {} | ||
} | ||
} | ||
// VarHandle API | ||
isVarHandle(owner) && | ||
( | ||
VarHandleNames.isInstanceVarHandle(owner) && staticMemorySnapshot.isTracked(params.firstOrNull()) || | ||
VarHandleNames.isArrayVarHandle(owner) && staticMemorySnapshot.isTracked(params.firstOrNull()) || | ||
VarHandleNames.isStaticVarHandle(owner) | ||
) -> { | ||
val methodType: VarHandleMethodType = VarHandleNames.varHandleMethodType(owner, params) | ||
when (methodType) { | ||
is InstanceVarHandleMethod -> { | ||
staticMemorySnapshot.trackField(methodType.owner, methodType.owner.javaClass, methodType.fieldName) | ||
} | ||
is StaticVarHandleMethod -> { | ||
staticMemorySnapshot.trackField(null, methodType.ownerClass, methodType.fieldName) | ||
} | ||
is ArrayVarHandleMethod -> { | ||
staticMemorySnapshot.trackArrayCell(methodType.array, methodType.index) | ||
} | ||
else -> {} | ||
} | ||
} | ||
// Java AFU (this also automatically handles the `kotlinx.atomicfu`, since they are compiled to Java AFU + Java atomic arrays) | ||
isAtomicFieldUpdater(owner) -> { | ||
val obj = params[0] | ||
val afuDesc: AtomicFieldUpdaterDescriptor? = AtomicFieldUpdaterNames.getAtomicFieldUpdaterName(owner!!) | ||
check(afuDesc != null) { "Cannot extract field name referenced by Java AFU object $owner" } | ||
|
||
staticMemorySnapshot.trackField(obj, afuDesc.targetType, afuDesc.fieldName) | ||
} | ||
// TODO: System.arraycopy | ||
// TODO: reflexivity | ||
} | ||
} | ||
|
||
private fun methodGuaranteeType(owner: Any?, className: String, methodName: String): ManagedGuaranteeType? = runInIgnoredSection { | ||
userDefinedGuarantees?.forEach { guarantee -> | ||
val ownerName = owner?.javaClass?.canonicalName ?: className | ||
|
@@ -969,9 +1026,15 @@ abstract class ManagedStrategy( | |
methodId: Int, | ||
params: Array<Any?> | ||
) { | ||
val guarantee = runInIgnoredSection { | ||
var guarantee: ManagedGuaranteeType? = null | ||
|
||
runInIgnoredSection { | ||
// process method effect on the static memory snapshot | ||
processMethodEffectOnStaticSnapshot(owner, params) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this mean? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean the name of the method or what it does? |
||
|
||
// process known method concurrency guarantee | ||
val atomicMethodDescriptor = getAtomicMethodDescriptor(owner, methodName) | ||
val guarantee = when { | ||
guarantee = when { | ||
(atomicMethodDescriptor != null) -> ManagedGuaranteeType.TREAT_AS_ATOMIC | ||
else -> methodGuaranteeType(owner, className, methodName) | ||
} | ||
|
@@ -988,8 +1051,8 @@ abstract class ManagedStrategy( | |
if (guarantee == null) { | ||
loopDetector.beforeMethodCall(codeLocation, params) | ||
} | ||
guarantee | ||
} | ||
|
||
if (guarantee == ManagedGuaranteeType.IGNORE || | ||
guarantee == ManagedGuaranteeType.TREAT_AS_ATOMIC) { | ||
// It's important that this method can't be called inside runInIgnoredSection, as the ignored section | ||
|
@@ -1346,7 +1409,7 @@ abstract class ManagedStrategy( | |
atomicUpdater: Any, | ||
parameters: Array<Any?>, | ||
): MethodCallTracePoint { | ||
getAtomicFieldUpdaterName(atomicUpdater)?.let { tracePoint.initializeOwnerName(it) } | ||
getAtomicFieldUpdaterName(atomicUpdater)?.let { tracePoint.initializeOwnerName(it.fieldName) } | ||
tracePoint.initializeParameters(parameters.drop(1).map { adornedStringRepresentation(it) }) | ||
return tracePoint | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems the code was moved from somewhere, but I do not see it removed in the original location.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I would suggest keeping the transformation-related logic in the corresponding package.