2929import com .google .common .util .concurrent .internal .InternalFutures ;
3030import com .google .errorprone .annotations .CanIgnoreReturnValue ;
3131import com .google .errorprone .annotations .ForOverride ;
32+ import com .google .j2objc .annotations .J2ObjCIncompatible ;
3233import com .google .j2objc .annotations .ReflectionSupport ;
3334import com .google .j2objc .annotations .RetainedLocalRef ;
35+ import java .lang .invoke .MethodHandles ;
36+ import java .lang .invoke .VarHandle ;
3437import java .lang .reflect .Field ;
3538import java .security .AccessController ;
3639import java .security .PrivilegedActionException ;
@@ -150,35 +153,60 @@ public final boolean cancel(boolean mayInterruptIfRunning) {
150153
151154 private static final AtomicHelper ATOMIC_HELPER ;
152155
156+ /**
157+ * Returns the result of calling {@link MethodHandles#lookup} from inside {@link AbstractFuture}.
158+ * By virtue of being created there, it has access to the private fields of {@link
159+ * AbstractFuture}, so that access is available to anyone who calls this method—specifically, to
160+ * {@link VarHandleAtomicHelper}.
161+ *
162+ * <p>This "shouldn't" be necessary: {@link VarHandleAtomicHelper} and {@link AbstractFuture}
163+ * "should" be nestmates, so a call to {@link MethodHandles#lookup} inside {@link
164+ * VarHandleAtomicHelper} "should" have access to each other's private fields. However, our
165+ * open-source build uses {@code -source 8 -target 8}, so the class files from that build can't
166+ * express nestmates. Thus, when those class files are used from Java 9 or higher (i.e., high
167+ * enough to trigger the {@link VarHandle} code path), such a lookup would fail with an {@link
168+ * IllegalAccessException}.
169+ *
170+ * <p>Note that we do not have a similar problem with the fields in {@link Waiter} because those
171+ * fields are not private. (We could solve the problem with {@link AbstractFuture} fields in the
172+ * same way if we wanted.)
173+ */
174+ private static MethodHandles .Lookup methodHandlesLookupFromWithinAbstractFuture () {
175+ return MethodHandles .lookup ();
176+ }
177+
153178 static {
154179 AtomicHelper helper ;
155180 Throwable thrownUnsafeFailure = null ;
156181 Throwable thrownAtomicReferenceFieldUpdaterFailure = null ;
157182
158- try {
159- helper = new UnsafeAtomicHelper ();
160- } catch (Exception | Error unsafeFailure ) { // sneaky checked exception
161- thrownUnsafeFailure = unsafeFailure ;
162- // catch absolutely everything and fall through to our
163- // 'AtomicReferenceFieldUpdaterAtomicHelper' The access control checks that ARFU does means
164- // the caller class has to be AbstractFuture instead of
165- // AtomicReferenceFieldUpdaterAtomicHelper, so we annoyingly define these here
183+ helper = VarHandleAtomicHelperMaker .INSTANCE .tryMakeVarHandleAtomicHelper ();
184+ if (helper == null ) {
166185 try {
167- helper =
168- new AtomicReferenceFieldUpdaterAtomicHelper (
169- newUpdater (Waiter .class , Thread .class , "thread" ),
170- newUpdater (Waiter .class , Waiter .class , "next" ),
171- newUpdater (AbstractFuture .class , Waiter .class , "waiters" ),
172- newUpdater (AbstractFuture .class , Listener .class , "listeners" ),
173- newUpdater (AbstractFuture .class , Object .class , "value" ));
174- } catch (Exception // sneaky checked exception
175- | Error atomicReferenceFieldUpdaterFailure ) {
176- // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause
177- // getDeclaredField to throw a NoSuchFieldException when the field is definitely there.
178- // For these users fallback to a suboptimal implementation, based on synchronized. This will
179- // be a definite performance hit to those users.
180- thrownAtomicReferenceFieldUpdaterFailure = atomicReferenceFieldUpdaterFailure ;
181- helper = new SynchronizedHelper ();
186+ helper = new UnsafeAtomicHelper ();
187+ } catch (Exception | Error unsafeFailure ) { // sneaky checked exception
188+ thrownUnsafeFailure = unsafeFailure ;
189+ // catch absolutely everything and fall through to our
190+ // 'AtomicReferenceFieldUpdaterAtomicHelper' The access control checks that ARFU does means
191+ // the caller class has to be AbstractFuture instead of
192+ // AtomicReferenceFieldUpdaterAtomicHelper, so we annoyingly define these here
193+ try {
194+ helper =
195+ new AtomicReferenceFieldUpdaterAtomicHelper (
196+ newUpdater (Waiter .class , Thread .class , "thread" ),
197+ newUpdater (Waiter .class , Waiter .class , "next" ),
198+ newUpdater (AbstractFuture .class , Waiter .class , "waiters" ),
199+ newUpdater (AbstractFuture .class , Listener .class , "listeners" ),
200+ newUpdater (AbstractFuture .class , Object .class , "value" ));
201+ } catch (Exception // sneaky checked exception
202+ | Error atomicReferenceFieldUpdaterFailure ) {
203+ // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause
204+ // getDeclaredField to throw a NoSuchFieldException when the field is definitely there.
205+ // For these users fallback to a suboptimal implementation, based on synchronized. This
206+ // will be a definite performance hit to those users.
207+ thrownAtomicReferenceFieldUpdaterFailure = atomicReferenceFieldUpdaterFailure ;
208+ helper = new SynchronizedHelper ();
209+ }
182210 }
183211 }
184212 ATOMIC_HELPER = helper ;
@@ -200,6 +228,40 @@ public final boolean cancel(boolean mayInterruptIfRunning) {
200228 }
201229 }
202230
231+ private enum VarHandleAtomicHelperMaker {
232+ INSTANCE {
233+ /**
234+ * Implementation used by non-J2ObjC environments (aside, of course, from those that have
235+ * supersource for the entirety of {@link AbstractFuture}).
236+ */
237+ @ Override
238+ @ J2ObjCIncompatible
239+ @ Nullable AtomicHelper tryMakeVarHandleAtomicHelper () {
240+ try {
241+ /*
242+ * We first use reflection to check whether VarHandle exists. If we instead just tried to
243+ * load our class directly (which would trigger non-reflective loading of VarHandle) from
244+ * within a `try` block, then an error might be thrown even before we enter the `try`
245+ * block: https://github.com/google/truth/issues/333#issuecomment-765652454
246+ *
247+ * Also, it's nice that this approach should let us catch *only* ClassNotFoundException
248+ * instead of having to catch more broadly (potentially even including, say, a
249+ * StackOverflowError).
250+ */
251+ Class .forName ("java.lang.invoke.VarHandle" );
252+ } catch (ClassNotFoundException beforeJava9 ) {
253+ return null ;
254+ }
255+ return new VarHandleAtomicHelper ();
256+ }
257+ };
258+
259+ /** Implementation used by J2ObjC environments, overridden for other environments. */
260+ @ Nullable AtomicHelper tryMakeVarHandleAtomicHelper () {
261+ return null ;
262+ }
263+ }
264+
203265 /** Waiter links form a Treiber stack, in the {@link #waiters} field. */
204266 private static final class Waiter {
205267 static final Waiter TOMBSTONE = new Waiter (false /* ignored param */ );
@@ -1339,6 +1401,72 @@ abstract boolean casListeners(
13391401 abstract boolean casValue (AbstractFuture <?> future , @ Nullable Object expect , Object update );
13401402 }
13411403
1404+ /** {@link AtomicHelper} based on {@link VarHandle}. */
1405+ @ J2ObjCIncompatible
1406+ // We use this class only after confirming that VarHandle is available at runtime.
1407+ @ SuppressWarnings ("Java8ApiChecker" )
1408+ @ IgnoreJRERequirement
1409+ private static final class VarHandleAtomicHelper extends AtomicHelper {
1410+ static final VarHandle waiterThreadUpdater ;
1411+ static final VarHandle waiterNextUpdater ;
1412+ static final VarHandle waitersUpdater ;
1413+ static final VarHandle listenersUpdater ;
1414+ static final VarHandle valueUpdater ;
1415+
1416+ static {
1417+ MethodHandles .Lookup lookup = methodHandlesLookupFromWithinAbstractFuture ();
1418+ try {
1419+ waiterThreadUpdater = lookup .findVarHandle (Waiter .class , "thread" , Thread .class );
1420+ waiterNextUpdater = lookup .findVarHandle (Waiter .class , "next" , Waiter .class );
1421+ waitersUpdater = lookup .findVarHandle (AbstractFuture .class , "waiters" , Waiter .class );
1422+ listenersUpdater = lookup .findVarHandle (AbstractFuture .class , "listeners" , Listener .class );
1423+ valueUpdater = lookup .findVarHandle (AbstractFuture .class , "value" , Object .class );
1424+ } catch (ReflectiveOperationException e ) {
1425+ // Those fields exist.
1426+ throw newLinkageError (e );
1427+ }
1428+ }
1429+
1430+ @ Override
1431+ void putThread (Waiter waiter , Thread newValue ) {
1432+ waiterThreadUpdater .setRelease (waiter , newValue );
1433+ }
1434+
1435+ @ Override
1436+ void putNext (Waiter waiter , @ Nullable Waiter newValue ) {
1437+ waiterNextUpdater .setRelease (waiter , newValue );
1438+ }
1439+
1440+ @ Override
1441+ boolean casWaiters (AbstractFuture <?> future , @ Nullable Waiter expect , @ Nullable Waiter update ) {
1442+ return waitersUpdater .compareAndSet (future , expect , update );
1443+ }
1444+
1445+ @ Override
1446+ boolean casListeners (AbstractFuture <?> future , @ Nullable Listener expect , Listener update ) {
1447+ return listenersUpdater .compareAndSet (future , expect , update );
1448+ }
1449+
1450+ @ Override
1451+ Listener gasListeners (AbstractFuture <?> future , Listener update ) {
1452+ return (Listener ) listenersUpdater .getAndSet (future , update );
1453+ }
1454+
1455+ @ Override
1456+ Waiter gasWaiters (AbstractFuture <?> future , Waiter update ) {
1457+ return (Waiter ) waitersUpdater .getAndSet (future , update );
1458+ }
1459+
1460+ @ Override
1461+ boolean casValue (AbstractFuture <?> future , @ Nullable Object expect , Object update ) {
1462+ return valueUpdater .compareAndSet (future , expect , update );
1463+ }
1464+
1465+ private static LinkageError newLinkageError (Throwable cause ) {
1466+ return new LinkageError (cause .toString (), cause );
1467+ }
1468+ }
1469+
13421470 /**
13431471 * {@link AtomicHelper} based on {@link sun.misc.Unsafe}.
13441472 *
0 commit comments