Package se.deversity.asynctest.analysis
Class StaticPinningScanner
java.lang.Object
se.deversity.asynctest.analysis.StaticPinningScanner
Compile-time / class-load-time static bytecode scanner that identifies virtual-thread
pinning sites without running any tests.
Problem
VirtualThreadPinningDetector (in the async-test-lib artifact — deliberately not a
@link, since this module depends on nothing else in the project) finds pinning at
runtime by monitoring thread states during stress tests. If the pinning code path is
rarely exercised, it goes undetected. The runtime detector also requires virtual threads
to be actively scheduled and pinned during the observation window.
Approach
Using the ASM bytecode library this scanner walks compiled.class files and flags any method that:
- Enters a monitor via
MONITORENTER - While inside that monitor, calls a blocking JDK method
(see
BLOCKING_METHODS)
Limitations
- Tracks monitor nesting depth only within a single method body; cross-method
synchronization (e.g. a
synchronizedmethod calling a blocking helper) requires inter-procedural analysis and is not detected. - Exception-handler edges (
MONITOREXITin finally blocks) may undercount nesting depth; false negatives are possible but false positives are not.
- Since:
- 1.6.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordA detected pinning site within a compiled class. -
Method Summary
Modifier and TypeMethodDescriptionstatic List<StaticPinningScanner.PinningSite> scanClass(byte[] classBytes) Scans a single.classfile and returns all detected pinning sites.static List<StaticPinningScanner.PinningSite> scanClass(String binaryName, ClassLoader classLoader) Convenience overload: loads the class bytes from aClassLoaderby converting the binary name to a resource path and delegates toscanClass(byte[]).static List<StaticPinningScanner.PinningSite> scanDirectory(Path root) Scans all.classfiles reachable fromroot(recursively) and returns every detected pinning site.
-
Method Details
-
scanClass
Scans a single.classfile and returns all detected pinning sites.- Parameters:
classBytes- raw bytecode of the compiled class- Returns:
- unmodifiable list of pinning sites (empty if none found)
-
scanDirectory
Scans all.classfiles reachable fromroot(recursively) and returns every detected pinning site.Useful as a post-compile step or as part of a JUnit
@BeforeAllsanity check:@BeforeAll static void noLoomPinningSites() throws IOException { Path classes = Path.of("target/classes"); List<PinningSite> sites = StaticPinningScanner.scanDirectory(classes); assertTrue(sites.isEmpty(), "Pinning sites detected:\n" + sites); }- Parameters:
root- directory containing compiled.classfiles- Returns:
- unmodifiable list of all pinning sites found under
root - Throws:
IOException- if any.classfile cannot be read
-
scanClass
public static List<StaticPinningScanner.PinningSite> scanClass(String binaryName, ClassLoader classLoader) throws IOException Convenience overload: loads the class bytes from aClassLoaderby converting the binary name to a resource path and delegates toscanClass(byte[]).- Parameters:
binaryName- e.g."com.example.MyService"classLoader- class loader to load the resource from- Throws:
IOException- if the resource cannot be readIllegalArgumentException- if the class resource is not found
-