3333import java .util .concurrent .atomic .AtomicLong ;
3434import java .util .concurrent .locks .LockSupport ;
3535import java .util .function .Consumer ;
36- import java .util .function .Function ;
3736import java .util .function .Supplier ;
3837import lombok .NonNull ;
3938import org .jetbrains .annotations .Nullable ;
39+ import org .jetbrains .annotations .VisibleForTesting ;
4040
4141/**
4242 * Delegate class that holds current replacements for fields/methods defined in {@code sun.misc.Unsafe}.
5050@ Deprecated
5151public final class UnsafeReplacementDelegate {
5252
53+ // accessor for cleaning up direct byte buffers
54+ @ VisibleForTesting
55+ static final Consumer <ByteBuffer > BB_CLEANER_NOOP = _ -> {
56+ };
57+ @ VisibleForTesting
58+ static final Supplier <Consumer <ByteBuffer >> BB_CLEANER = createByteBufferCleaner ();
59+
5360 // counter for handing out field offsets; mapping for handed-out offsets to their actual field
5461 private static final Map <Long , Field > FIELD_OFFSET_TO_FIELD_LOOKUP = new ConcurrentHashMap <>();
5562 private static final AtomicLong FIELD_OFFSET_COUNTER =
5663 new AtomicLong (ThreadLocalRandom .current ().nextInt (Short .MAX_VALUE , Integer .MAX_VALUE ));
5764
58- // accessors for cleaning up direct byte buffers
59- private static final Supplier <Consumer <ByteBuffer >> BB_CLEANER = createByteBufferCleaner ();
60- private static final Supplier <Function <ByteBuffer , Object >> BB_ATTACHMENT_GETTER = createByteBufferAttachmentGetter ();
61-
6265 // accessor for the operating system mx bean
6366 private static final Supplier <OperatingSystemMXBean > OS_MX_BEAN =
6467 new LazyMemoizingSupplier <>(ManagementFactory ::getOperatingSystemMXBean );
@@ -102,35 +105,6 @@ private UnsafeReplacementDelegate() {
102105 });
103106 }
104107
105- /**
106- * Get a supplier that creates a function to resolve the attachment of a direct byte buffer. The function is only
107- * created once on the first initialization of the supplier.
108- *
109- * @return a supplier that creates a function to resolve the attachment of a direct byte buffer.
110- */
111- private static @ NonNull Supplier <Function <ByteBuffer , Object >> createByteBufferAttachmentGetter () {
112- return new LazyMemoizingSupplier <>(() -> {
113- try {
114- var lookup = OpConstants .TRUSTED_LOOKUP .get ();
115- var directBufferClass = Class .forName ("sun.nio.ch.DirectBuffer" );
116- var attachementMethod = directBufferClass .getMethod ("attachment" );
117- var attachmentMethodHandle = MethodHandles .explicitCastArguments (
118- lookup .unreflect (attachementMethod ),
119- MethodType .methodType (Object .class , ByteBuffer .class ));
120- return buffer -> {
121- try {
122- return attachmentMethodHandle .invokeExact (buffer );
123- } catch (Throwable _) {
124- return null ;
125- }
126- };
127- } catch (Throwable throwable ) {
128- UnsafeLogUtil .debug ("Unable to access byte buffer attachment method; assuming it's never attached" , throwable );
129- return _ -> null ;
130- }
131- });
132- }
133-
134108 /**
135109 * Get a supplier that creates a consumer to clean a direct byte buffer. The consumer is only created once on the
136110 * first initialization of the supplier.
@@ -142,31 +116,60 @@ private UnsafeReplacementDelegate() {
142116 try {
143117 var lookup = OpConstants .TRUSTED_LOOKUP .get ();
144118
145- // get the method handle to get the cleaner of the provided byte buffer (type: (ByteBuffer): Cleaner)
119+ // get the method handle to get the cleaner of the provided byte buffer (type: (ByteBuffer):Cleaner)
146120 var directBufferClass = Class .forName ("sun.nio.ch.DirectBuffer" );
147121 var cleanerMethod = directBufferClass .getDeclaredMethod ("cleaner" );
148122 var cleanerHandle = MethodHandles .explicitCastArguments (
149123 lookup .unreflect (cleanerMethod ),
150124 MethodType .methodType (cleanerMethod .getReturnType (), ByteBuffer .class ));
151125
152- // get the method handle to invoke the clean method on the Cleaner class (type: (Cleaner): void)
126+ // get the method handle to invoke the clean method on the Cleaner class (type: (Cleaner):void)
153127 var cleanerClass = Class .forName ("jdk.internal.ref.Cleaner" );
154128 var cleanMethod = cleanerClass .getDeclaredMethod ("clean" );
155129 var cleanHandle = lookup .unreflect (cleanMethod );
156130
157- // adapt the clean() method handle by pre-processing it with the result of the cleaner retrieval method handle
158- // this results in a chained invocation like clean(buffer.cleaner())
159- var cleanBufferHandle = MethodHandles .filterArguments (cleanHandle , 0 , cleanerHandle );
131+ // adapt the clean() method handle by pre-processing it with the result of the cleaner retrieval
132+ // method handle, this results in a chained invocation (type: (ByteBuffer):void)
133+ var cleanBufferHandle = MethodHandles .filterReturnValue (cleanerHandle , cleanHandle );
134+
135+ // get a method handle to check if a buffer has an attachment (type: (ByteBuffer):boolean)
136+ var attachementMethod = directBufferClass .getMethod ("attachment" );
137+ var attachmentHandle = MethodHandles .explicitCastArguments (
138+ lookup .unreflect (attachementMethod ),
139+ MethodType .methodType (attachementMethod .getReturnType (), ByteBuffer .class ));
140+ var isNullHandle = lookup .findStatic (
141+ Objects .class ,
142+ "isNull" ,
143+ MethodType .methodType (boolean .class , Object .class ));
144+ var isAttachmentNullHandle = MethodHandles .collectArguments (isNullHandle , 0 , attachmentHandle );
145+
146+ // get a method handle that throws an IAE with the message 'duplicate or slice' (type: ():void)
147+ // this handle needs to then be adapted to add an extra, ignored ByteBuffer param (type: (ByteBuffer):void)
148+ var iaeMessageCtrHandle = lookup .findConstructor (
149+ IllegalArgumentException .class ,
150+ MethodType .methodType (void .class , String .class ));
151+ var dupOrSliceIaeCtrHandle = MethodHandles .insertArguments (iaeMessageCtrHandle , 0 , "duplicate or slice" );
152+ var throwIaeHandle = MethodHandles .throwException (void .class , IllegalArgumentException .class );
153+ var throwDupOrSliceHandle = MethodHandles .collectArguments (throwIaeHandle , 0 , dupOrSliceIaeCtrHandle );
154+ var throwDupOrSliceHandleWithBBArg = MethodHandles .dropArguments (throwDupOrSliceHandle , 0 , ByteBuffer .class );
155+
156+ // construct a method handle that conditionally invokes 'bb.cleaner().clean()' or throws an
157+ // IAE depending on the fact if the provided ByteBuffer has an attachment or not
158+ var cleanIfNotAttachmentHandle = MethodHandles .guardWithTest (
159+ isAttachmentNullHandle ,
160+ cleanBufferHandle ,
161+ throwDupOrSliceHandleWithBBArg );
160162 return buffer -> {
161163 try {
162- cleanBufferHandle .invokeExact (buffer );
164+ cleanIfNotAttachmentHandle .invokeExact (buffer );
165+ } catch (IllegalArgumentException exception ) {
166+ throw exception ;
163167 } catch (Throwable _) {
164168 }
165169 };
166170 } catch (Throwable throwable ) {
167171 UnsafeLogUtil .debug ("Unable to access byte buffer cleaning methods; falling back to no cleaning" , throwable );
168- return _ -> { // unable to clean direct buffers
169- };
172+ return BB_CLEANER_NOOP ; // unable to clean direct buffers
170173 }
171174 });
172175 }
@@ -1275,12 +1278,6 @@ public static void unsafeInvokeCleaner(ByteBuffer buffer) {
12751278 throw new IllegalArgumentException ("buffer is non-direct" ); // mimics current behavior
12761279 }
12771280
1278- var attachmentGetter = BB_ATTACHMENT_GETTER .get ();
1279- var attachment = attachmentGetter .apply (buffer );
1280- if (attachment != null ) {
1281- throw new IllegalArgumentException ("duplicate or slice" ); // mimics current behavior
1282- }
1283-
12841281 var cleanerInvoker = BB_CLEANER .get ();
12851282 cleanerInvoker .accept (buffer );
12861283 }
0 commit comments