| /* |
| * Copyright (C) 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.VarHandle; |
| import java.lang.reflect.Field; |
| import java.util.concurrent.atomic.AtomicReference; |
| import sun.misc.Unsafe; |
| |
| class Main { |
| public static void main(String args[]) throws Exception { |
| // Stress-test read-modify-write operations in adjacent memory locations. |
| // This is intended to uncover bugs triggered by spurious CAS failures on |
| // architectures where such spurious failures can happen. Bug: 218453177 |
| $noinline$testVarHandleBytes(); |
| $noinline$testVarHandleInts(); |
| $noinline$testVarHandleLongs(); |
| $noinline$testVarHandleReferences(); |
| $noinline$testUnsafeInts(); |
| $noinline$testUnsafeLongs(); |
| $noinline$testUnsafeReferences(); |
| |
| // Stress-test read-modify-write operations on the same memory locations. |
| // This is intended to uncover bugs with false-positive comparison in CAS. |
| $noinline$testAtomicReference(); |
| } |
| |
| public static void $noinline$testVarHandleBytes() throws Exception { |
| // Prepare `VarHandle` objects. |
| VarHandle[] vhs = new VarHandle[] { |
| MethodHandles.lookup().findVarHandle(FourBytes.class, "b1", byte.class), |
| MethodHandles.lookup().findVarHandle(FourBytes.class, "b2", byte.class), |
| MethodHandles.lookup().findVarHandle(FourBytes.class, "b3", byte.class), |
| MethodHandles.lookup().findVarHandle(FourBytes.class, "b4", byte.class) |
| }; |
| // Prepare threads. |
| final FourBytes fourBytes = new FourBytes(); |
| final StopFlag stopFlag = new StopFlag(); |
| Thread[] threads = new Thread[4]; |
| for (int i = 0; i != 4; ++i) { |
| final VarHandle vh = vhs[i]; |
| threads[i] = new Thread() { |
| public void run() { |
| byte value = 0; |
| while (!stopFlag.stop) { |
| byte nextValue = (byte) (value + 1); |
| boolean success = vh.compareAndSet(fourBytes, value, nextValue); |
| assertTrue(success); |
| value = nextValue; |
| } |
| } |
| }; |
| } |
| // Start threads. |
| for (int i = 0; i != 4; ++i) { |
| threads[i].start(); |
| } |
| // Let the threads run for 5s. |
| Thread.sleep(5000); |
| // Stop threads. |
| stopFlag.stop = true; |
| for (int i = 0; i != 4; ++i) { |
| threads[i].join(); |
| } |
| } |
| |
| public static void $noinline$testVarHandleInts() throws Exception { |
| // Prepare `VarHandle` objects. |
| VarHandle[] vhs = new VarHandle[] { |
| MethodHandles.lookup().findVarHandle(FourInts.class, "i1", int.class), |
| MethodHandles.lookup().findVarHandle(FourInts.class, "i2", int.class), |
| MethodHandles.lookup().findVarHandle(FourInts.class, "i3", int.class), |
| MethodHandles.lookup().findVarHandle(FourInts.class, "i4", int.class) |
| }; |
| // Prepare threads. |
| final FourInts fourInts = new FourInts(); |
| final StopFlag stopFlag = new StopFlag(); |
| Thread[] threads = new Thread[4]; |
| for (int i = 0; i != 4; ++i) { |
| final VarHandle vh = vhs[i]; |
| threads[i] = new Thread() { |
| public void run() { |
| int value = 0; |
| while (!stopFlag.stop) { |
| int nextValue = value + 1; |
| boolean success = vh.compareAndSet(fourInts, value, nextValue); |
| assertTrue(success); |
| value = nextValue; |
| } |
| } |
| }; |
| } |
| // Start threads. |
| for (int i = 0; i != 4; ++i) { |
| threads[i].start(); |
| } |
| // Let the threads run for 5s. |
| Thread.sleep(5000); |
| // Stop threads. |
| stopFlag.stop = true; |
| for (int i = 0; i != 4; ++i) { |
| threads[i].join(); |
| } |
| } |
| |
| public static void $noinline$testVarHandleLongs() throws Exception { |
| // Prepare `VarHandle` objects. |
| VarHandle[] vhs = new VarHandle[] { |
| MethodHandles.lookup().findVarHandle(FourLongs.class, "l1", long.class), |
| MethodHandles.lookup().findVarHandle(FourLongs.class, "l2", long.class), |
| MethodHandles.lookup().findVarHandle(FourLongs.class, "l3", long.class), |
| MethodHandles.lookup().findVarHandle(FourLongs.class, "l4", long.class) |
| }; |
| // Prepare threads. |
| final FourLongs fourLongs = new FourLongs(); |
| final StopFlag stopFlag = new StopFlag(); |
| Thread[] threads = new Thread[4]; |
| for (int i = 0; i != 4; ++i) { |
| final VarHandle vh = vhs[i]; |
| threads[i] = new Thread() { |
| public void run() { |
| long value = 0; |
| while (!stopFlag.stop) { |
| long nextValue = value + 1L; |
| boolean success = vh.compareAndSet(fourLongs, value, nextValue); |
| assertTrue(success); |
| value = nextValue; |
| } |
| } |
| }; |
| } |
| // Start threads. |
| for (int i = 0; i != 4; ++i) { |
| threads[i].start(); |
| } |
| // Let the threads run for 5s. |
| Thread.sleep(5000); |
| // Stop threads. |
| stopFlag.stop = true; |
| for (int i = 0; i != 4; ++i) { |
| threads[i].join(); |
| } |
| } |
| |
| public static void $noinline$testVarHandleReferences() throws Exception { |
| // Prepare `VarHandle` objects. |
| VarHandle[] vhs = new VarHandle[] { |
| MethodHandles.lookup().findVarHandle(FourReferences.class, "r1", Object.class), |
| MethodHandles.lookup().findVarHandle(FourReferences.class, "r2", Object.class), |
| MethodHandles.lookup().findVarHandle(FourReferences.class, "r3", Object.class), |
| MethodHandles.lookup().findVarHandle(FourReferences.class, "r4", Object.class) |
| }; |
| // Prepare threads. |
| final FourReferences fourReferences = new FourReferences(); |
| Object[] values = new Object[] { |
| null, |
| new Object(), |
| new Object(), |
| new Object() |
| }; |
| final StopFlag stopFlag = new StopFlag(); |
| Thread[] threads = new Thread[4]; |
| for (int i = 0; i != 4; ++i) { |
| final VarHandle vh = vhs[i]; |
| threads[i] = new Thread() { |
| public void run() { |
| int index = 0; |
| while (!stopFlag.stop) { |
| Object value = values[index]; |
| index = (index + 1) & 3; |
| Object nextValue = values[index]; |
| boolean success = vh.compareAndSet(fourReferences, value, nextValue); |
| assertTrue(success); |
| } |
| } |
| }; |
| } |
| // Start threads. |
| for (int i = 0; i != 4; ++i) { |
| threads[i].start(); |
| } |
| // Allocate memory to trigger some GCs |
| for (int i = 0; i != 640 * 1024; ++i) { |
| $noinline$allocateAtLeast1KiB(); |
| } |
| // Stop threads. |
| stopFlag.stop = true; |
| for (int i = 0; i != 4; ++i) { |
| threads[i].join(); |
| } |
| } |
| |
| public static void $noinline$testUnsafeInts() throws Exception { |
| // Prepare Unsafe offsets. |
| final Unsafe unsafe = getUnsafe(); |
| long[] offsets = new long[] { |
| unsafe.objectFieldOffset(FourInts.class.getField("i1")), |
| unsafe.objectFieldOffset(FourInts.class.getField("i2")), |
| unsafe.objectFieldOffset(FourInts.class.getField("i3")), |
| unsafe.objectFieldOffset(FourInts.class.getField("i4")) |
| }; |
| // Prepare threads. |
| final FourInts fourInts = new FourInts(); |
| final StopFlag stopFlag = new StopFlag(); |
| Thread[] threads = new Thread[4]; |
| for (int i = 0; i != 4; ++i) { |
| final long offset = offsets[i]; |
| threads[i] = new Thread() { |
| public void run() { |
| int value = 0; |
| while (!stopFlag.stop) { |
| int nextValue = value + 1; |
| boolean success = unsafe.compareAndSwapInt( |
| fourInts, offset, value, nextValue); |
| assertTrue(success); |
| value = nextValue; |
| } |
| } |
| }; |
| } |
| // Start threads. |
| for (int i = 0; i != 4; ++i) { |
| threads[i].start(); |
| } |
| // Let the threads run for 5s. |
| Thread.sleep(5000); |
| // Stop threads. |
| stopFlag.stop = true; |
| for (int i = 0; i != 4; ++i) { |
| threads[i].join(); |
| } |
| } |
| |
| public static void $noinline$testUnsafeLongs() throws Exception { |
| // Prepare Unsafe offsets. |
| final Unsafe unsafe = getUnsafe(); |
| long[] offsets = new long[] { |
| unsafe.objectFieldOffset(FourLongs.class.getField("l1")), |
| unsafe.objectFieldOffset(FourLongs.class.getField("l2")), |
| unsafe.objectFieldOffset(FourLongs.class.getField("l3")), |
| unsafe.objectFieldOffset(FourLongs.class.getField("l4")) |
| }; |
| // Prepare threads. |
| final FourLongs fourLongs = new FourLongs(); |
| final StopFlag stopFlag = new StopFlag(); |
| Thread[] threads = new Thread[4]; |
| for (int i = 0; i != 4; ++i) { |
| final long offset = offsets[i]; |
| threads[i] = new Thread() { |
| public void run() { |
| long value = 0; |
| while (!stopFlag.stop) { |
| long nextValue = value + 1L; |
| boolean success = unsafe.compareAndSwapLong( |
| fourLongs, offset, value, nextValue); |
| assertTrue(success); |
| value = nextValue; |
| } |
| } |
| }; |
| } |
| // Start threads. |
| for (int i = 0; i != 4; ++i) { |
| threads[i].start(); |
| } |
| // Let the threads run for 5s. |
| Thread.sleep(5000); |
| // Stop threads. |
| stopFlag.stop = true; |
| for (int i = 0; i != 4; ++i) { |
| threads[i].join(); |
| } |
| } |
| |
| public static void $noinline$testUnsafeReferences() throws Exception { |
| // Prepare Unsafe offsets. |
| // D8 rewrites the bytecode with a workaround for CAS bug. To test the raw |
| // `Unsafe.compareAndSwapObject()` call, we implement the call in smali |
| // and wrap it in an indirect call. |
| final UnsafeDispatch unsafeDispatch = |
| (UnsafeDispatch) Class.forName("UnsafeWrapper").newInstance(); |
| final Unsafe unsafe = getUnsafe(); |
| long[] offsets = new long[] { |
| unsafe.objectFieldOffset(FourReferences.class.getField("r1")), |
| unsafe.objectFieldOffset(FourReferences.class.getField("r2")), |
| unsafe.objectFieldOffset(FourReferences.class.getField("r3")), |
| unsafe.objectFieldOffset(FourReferences.class.getField("r4")) |
| }; |
| // Prepare threads. |
| final FourReferences fourReferences = new FourReferences(); |
| Object[] values = new Object[] { |
| null, |
| new Object(), |
| new Object(), |
| new Object() |
| }; |
| final StopFlag stopFlag = new StopFlag(); |
| Thread[] threads = new Thread[4]; |
| for (int i = 0; i != 4; ++i) { |
| final long offset = offsets[i]; |
| threads[i] = new Thread() { |
| public void run() { |
| int index = 0; |
| while (!stopFlag.stop) { |
| Object value = values[index]; |
| index = (index + 1) & 3; |
| Object nextValue = values[index]; |
| boolean success = unsafeDispatch.compareAndSwapObject( |
| unsafe, fourReferences, offset, value, nextValue); |
| assertTrue(success); |
| } |
| } |
| }; |
| } |
| // Start threads. |
| for (int i = 0; i != 4; ++i) { |
| threads[i].start(); |
| } |
| // Allocate memory to trigger some GCs |
| for (int i = 0; i != 640 * 1024; ++i) { |
| $noinline$allocateAtLeast1KiB(); |
| } |
| // Stop threads. |
| stopFlag.stop = true; |
| for (int i = 0; i != 4; ++i) { |
| threads[i].join(); |
| } |
| } |
| |
| // Instead of using a `VarHandle` directly, this test uses `AtomicReference` which is |
| // implemented using a `VarHandle`. This is because the normal `VarHandle` checks are |
| // done without read barrier which makes them likely to fail and take the slow-path to |
| // the runtime while the GC is marking (which is the case we're most interested in). |
| // The `AtomicReference` uses a boot-image `VarHandle` which is optimized to avoid |
| // those checks, making it more likely to hit bugs in the raw RMW operation. |
| public static void $noinline$testAtomicReference() throws Exception { |
| // Prepare `AtomicReference` object. |
| // D8 rewrites the bytecode with a workaround for CAS bug. To test the raw |
| // `AtomicReference.compareAndSet()` call, we implement the call in smali |
| // and wrap it in an indirect call. |
| final AtomicReferenceDispatch atomicReferenceDispatch = |
| (AtomicReferenceDispatch) Class.forName("AtomicReferenceWrapper").newInstance(); |
| final AtomicReference aref = new AtomicReference(null); |
| // Prepare threads. |
| final Object[] objects = new Object[] { |
| null, |
| new Object(), |
| new Object(), |
| new Object() |
| }; |
| final StopFlag stopFlag = new StopFlag(); |
| Thread[] threads = new Thread[4]; |
| for (int i = 0; i != 4; ++i) { |
| if (i == 0) { |
| threads[i] = new Thread() { |
| public void run() { |
| int index = 0; |
| Object value = objects[index]; |
| while (!stopFlag.stop) { |
| index = (index + 1) & 3; |
| Object nextValue = objects[index]; |
| boolean success = atomicReferenceDispatch.compareAndSet( |
| aref, value, nextValue); |
| assertTrue(success); |
| value = nextValue; |
| } |
| } |
| }; |
| } else { |
| final Object value = objects[i]; |
| assertTrue(value != null); |
| threads[i] = new Thread() { |
| public void run() { |
| // This thread is trying to overwrite a value with the same value. |
| // For a false-positive in CAS compare, it would actually change |
| // the value and cause the thread `threads[0]` to fail. |
| assertTrue(value != null); |
| while (!stopFlag.stop) { |
| // Do not check the return value. |
| atomicReferenceDispatch.compareAndSet(aref, value, value); |
| } |
| } |
| }; |
| } |
| }; |
| // Start threads. |
| for (int i = 0; i != 4; ++i) { |
| threads[i].start(); |
| } |
| // Allocate memory to trigger some GCs |
| for (int i = 0; i != 640 * 1024; ++i) { |
| $noinline$allocateAtLeast1KiB(); |
| } |
| // Stop threads. |
| stopFlag.stop = true; |
| for (int i = 0; i != 4; ++i) { |
| threads[i].join(); |
| } |
| } |
| |
| public static void assertTrue(boolean value) { |
| if (!value) { |
| throw new Error("Assertion failed!"); |
| } |
| } |
| |
| public static Unsafe getUnsafe() throws Exception { |
| Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); |
| Field f = unsafeClass.getDeclaredField("theUnsafe"); |
| f.setAccessible(true); |
| return (Unsafe) f.get(null); |
| } |
| |
| public static void $noinline$allocateAtLeast1KiB() { |
| // Give GC more work by allocating Object arrays. |
| memory[allocationIndex] = new Object[1024 / 4]; |
| ++allocationIndex; |
| if (allocationIndex == memory.length) { |
| allocationIndex = 0; |
| } |
| } |
| |
| // We shall retain some allocated memory and release old allocations |
| // so that the GC has something to do. |
| public static Object[] memory = new Object[1024]; |
| public static int allocationIndex = 0; |
| } |
| |
| class StopFlag { |
| public volatile boolean stop = false; |
| } |
| |
| class FourBytes { |
| public byte b1 = (byte) 0; |
| public byte b2 = (byte) 0; |
| public byte b3 = (byte) 0; |
| public byte b4 = (byte) 0; |
| } |
| |
| class FourInts { |
| public int i1 = 0; |
| public int i2 = 0; |
| public int i3 = 0; |
| public int i4 = 0; |
| } |
| |
| class FourLongs { |
| public long l1 = 0L; |
| public long l2 = 0L; |
| public long l3 = 0L; |
| public long l4 = 0L; |
| } |
| |
| class FourReferences { |
| public Object r1 = null; |
| public Object r2 = null; |
| public Object r3 = null; |
| public Object r4 = null; |
| } |
| |
| abstract class UnsafeDispatch { |
| public abstract boolean compareAndSwapObject( |
| Unsafe unsafe, Object obj, long offset, Object expected, Object new_value); |
| } |
| |
| abstract class AtomicReferenceDispatch { |
| public abstract boolean compareAndSet(AtomicReference aref, Object expected, Object new_value); |
| } |