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); }A
.classfile that cannot be parsed is skipped rather than allowed to abort the scan. ASM rejects a class whose major version it does not know, and truncated or otherwise corrupt bytes surface as unchecked exceptions from deep inside the reader — neither is declared, and both used to propagate out of the@BeforeAllabove and fail the consumer's build over a file that has nothing to do with their code. Skipping costs a false negative on that one file, which is the direction this scanner is allowed to be wrong in.- Parameters:
root- directory containing compiled.classfiles- Returns:
- unmodifiable list of all pinning sites found under
root - Throws:
IOException- if the directory tree cannot be walked, or a file 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
-