From dbbee86aaa1e5754438f0d3547b89bf43a218a01 Mon Sep 17 00:00:00 2001 From: Ao Li Date: Mon, 25 Mar 2024 18:22:05 -0400 Subject: [PATCH] Add semaphore test. --- .../cmu/pasta/sfuzz/core/GlobalContext.kt | 25 +++- .../cmu/pasta/sfuzz/core/RuntimeDelegate.kt | 63 +++++++++ .../core/concurrency/locks/LockManager.kt | 12 +- ...Manager.kt => ReferencedContextManager.kt} | 13 +- .../concurrency/locks/SemaphoreContext.kt | 44 ++++++ .../concurrency/locks/SemaphoreManager.kt | 34 +++++ .../sfuzz/instrumentation/JDKInstrumenter.kt | 3 +- .../visitors/ClassloaderInstrumenter.kt | 3 +- .../visitors/ConditionInstrumenter.kt | 8 +- .../visitors/LockInstrumenter.kt | 10 +- .../visitors/LockSupportInstrumenter.kt | 26 +--- .../visitors/MethodEnterVisitor.kt | 10 +- .../visitors/MethodExitVisitor.kt | 10 +- .../visitors/ObjectInstrumenter.kt | 5 +- .../ReentrantReadWriteLockInstrumenter.kt | 2 +- .../visitors/SemaphoreInstrumenter.kt | 46 +++++++ .../visitors/ThreadInstrumenter.kt | 11 +- .../visitors/UnsafeInstrumenter.kt | 2 +- .../sfuzz/instrumentation/visitors/Utils.kt | 10 +- .../src/test/kotlin/visitors/UtilsTest.kt | 9 +- .../pasta/sfuzz/it/core/SemaphoreTest.java | 31 +++++ .../sfuzz/it/core/ThreadInterruptTest.java | 4 +- .../src/test/java/example/SemaphoreTest.java | 127 ++++++++++++++++++ .../cmu/pasta/sfuzz/runtime/Delegate.java | 28 ++++ .../java/cmu/pasta/sfuzz/runtime/Runtime.java | 45 +++++++ 25 files changed, 512 insertions(+), 69 deletions(-) rename core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/{LockContextManager.kt => ReferencedContextManager.kt} (75%) create mode 100644 core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/SemaphoreContext.kt create mode 100644 core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/SemaphoreManager.kt create mode 100644 instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/SemaphoreInstrumenter.kt create mode 100644 integration-tests/src/test/java/cmu/pasta/sfuzz/it/core/SemaphoreTest.java create mode 100644 integration-tests/src/test/java/example/SemaphoreTest.java diff --git a/core/src/main/kotlin/cmu/pasta/sfuzz/core/GlobalContext.kt b/core/src/main/kotlin/cmu/pasta/sfuzz/core/GlobalContext.kt index 05f3f2f6..9040ce56 100644 --- a/core/src/main/kotlin/cmu/pasta/sfuzz/core/GlobalContext.kt +++ b/core/src/main/kotlin/cmu/pasta/sfuzz/core/GlobalContext.kt @@ -3,6 +3,7 @@ package cmu.pasta.sfuzz.core import cmu.pasta.sfuzz.core.concurrency.locks.LockManager import cmu.pasta.sfuzz.core.concurrency.SFuzzThread import cmu.pasta.sfuzz.core.concurrency.SynchronizationManager +import cmu.pasta.sfuzz.core.concurrency.locks.SemaphoreManager import cmu.pasta.sfuzz.core.concurrency.operations.* import cmu.pasta.sfuzz.core.logger.LoggerBase import cmu.pasta.sfuzz.core.runtime.AnalysisResult @@ -15,6 +16,7 @@ import cmu.pasta.sfuzz.runtime.MemoryOpType import cmu.pasta.sfuzz.runtime.Runtime import cmu.pasta.sfuzz.runtime.TargetTerminateException import java.util.concurrent.Executors +import java.util.concurrent.Semaphore import java.util.concurrent.locks.Condition import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock @@ -29,6 +31,7 @@ object GlobalContext { var scheduler: Scheduler = FifoScheduler() var config: Configuration? = null private val lockManager = LockManager() + private val semaphoreManager = SemaphoreManager() private val volatileManager = VolatileManager() val syncManager = SynchronizationManager() val loggers = mutableListOf() @@ -393,7 +396,7 @@ object GlobalContext { fun log(format: String, vararg args: Any) { val tid = Thread.currentThread().id val context = registeredThreads[tid]!! - val data = "[${context.index}]: ${String.format(format, args)}" + val data = "[${context.index}]: ${String.format(format, args)}\n" for (logger in loggers) { logger.applicationEvent(data) } @@ -449,6 +452,26 @@ object GlobalContext { lockManager.registerNewCondition(condition, lock) } + fun semaphoreInit(sem: Semaphore) { + semaphoreManager.init(sem) + } + + fun semaphoreAcquire(sem: Semaphore, permits: Int, shouldBlock: Boolean): Boolean { + return semaphoreManager.acquire(sem, permits, shouldBlock) + } + + fun semaphoreRelease(sem: Semaphore, permits: Int) { + semaphoreManager.release(sem, permits) + } + + fun semaphoreDrainPermits(sem: Semaphore): Int { + return semaphoreManager.drainPermits(sem) + } + + fun semaphoreReducePermits(sem: Semaphore, permits: Int) { + semaphoreManager.reducePermits(sem, permits) + } + fun fieldOperation(obj: Any?, owner: String, name: String, type: MemoryOpType) { if (!volatileManager.isVolatile(owner, name)) return val objIds = mutableListOf() diff --git a/core/src/main/kotlin/cmu/pasta/sfuzz/core/RuntimeDelegate.kt b/core/src/main/kotlin/cmu/pasta/sfuzz/core/RuntimeDelegate.kt index 1481e4d0..413b4a8f 100644 --- a/core/src/main/kotlin/cmu/pasta/sfuzz/core/RuntimeDelegate.kt +++ b/core/src/main/kotlin/cmu/pasta/sfuzz/core/RuntimeDelegate.kt @@ -4,6 +4,7 @@ import cmu.pasta.sfuzz.core.concurrency.SFuzzThread import cmu.pasta.sfuzz.runtime.Delegate import cmu.pasta.sfuzz.runtime.MemoryOpType import cmu.pasta.sfuzz.runtime.TargetTerminateException +import java.util.concurrent.Semaphore import java.util.concurrent.locks.Condition import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantReadWriteLock @@ -278,6 +279,68 @@ class RuntimeDelegate: Delegate() { entered.set(false) } + override fun onSemaphoreInit(sem: Semaphore) { + if (checkEntered()) return + GlobalContext.semaphoreInit(sem) + entered.set(false) + } + + override fun onSemaphoreAcquire(sem: Semaphore, permits: Int) { + if (checkEntered()) { + skipFunctionEntered.set(1 + skipFunctionEntered.get()) + return + } + GlobalContext.semaphoreAcquire(sem, permits, true) + entered.set(false) + skipFunctionEntered.set(skipFunctionEntered.get() + 1) + } + + override fun onSemaphoreAcquireDone() { + skipFunctionEntered.set(skipFunctionEntered.get() - 1) + } + + override fun onSemaphoreRelease(sem: Semaphore, permits: Int) { + if (checkEntered()) { + skipFunctionEntered.set(1 + skipFunctionEntered.get()) + return + } + GlobalContext.semaphoreRelease(sem, permits) + entered.set(false) + skipFunctionEntered.set(skipFunctionEntered.get() + 1) + } + + override fun onSemaphoreReleaseDone() { + skipFunctionEntered.set(skipFunctionEntered.get() - 1) + } + + override fun onSemaphoreDrainPermits(sem: Semaphore) { + if (checkEntered()) { + skipFunctionEntered.set(1 + skipFunctionEntered.get()) + return + } + GlobalContext.semaphoreDrainPermits(sem) + entered.set(false) + skipFunctionEntered.set(skipFunctionEntered.get() + 1) + } + + override fun onSemaphoreDrainPermitsDone() { + skipFunctionEntered.set(skipFunctionEntered.get() - 1) + } + + override fun onSemaphoreReducePermits(sem: Semaphore, permits: Int) { + if (checkEntered()) { + skipFunctionEntered.set(1 + skipFunctionEntered.get()) + return + } + GlobalContext.semaphoreReducePermits(sem, permits) + entered.set(false) + skipFunctionEntered.set(skipFunctionEntered.get() + 1) + } + + override fun onSemaphoreReducePermitsDone() { + skipFunctionEntered.set(skipFunctionEntered.get() - 1) + } + override fun start() { // For the first thread, it is not registered. // Therefor we cannot call `checkEntered` here. diff --git a/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/LockManager.kt b/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/LockManager.kt index d5c31e4f..96fafb8a 100644 --- a/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/LockManager.kt +++ b/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/LockManager.kt @@ -9,10 +9,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock class LockManager { - val lockContextManager = LockContextManager() - + val lockContextManager = ReferencedContextManager { ReentrantLockContext() } val waitingThreads = mutableMapOf>() - val conditionToLock = mutableMapOf() val lockToConditions = mutableMapOf>() @@ -22,12 +20,10 @@ class LockManager { fun reentrantReadWriteLockInit(readLock: ReadLock, writeLock: WriteLock) { val context = ReentrantReadWriteLockContext() - lockContextManager.addLockContext(readLock, context) - lockContextManager.addLockContext(writeLock, context) + lockContextManager.addContext(readLock, context) + lockContextManager.addContext(writeLock, context) } -// fun addLockContext(lock) - /** * Return true if [lock] is acquired by the current thread. */ @@ -92,6 +88,6 @@ class LockManager { assert(waitingThreads.isEmpty()) conditionToLock.clear() lockToConditions.clear() -// lockContextManager.done() + lockContextManager.done() } } \ No newline at end of file diff --git a/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/LockContextManager.kt b/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/ReferencedContextManager.kt similarity index 75% rename from core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/LockContextManager.kt rename to core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/ReferencedContextManager.kt index 95873166..39b829af 100644 --- a/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/LockContextManager.kt +++ b/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/ReferencedContextManager.kt @@ -7,20 +7,20 @@ import java.lang.ref.ReferenceQueue class IdentityPhantomReference(referent: T, queue: ReferenceQueue) : PhantomReference(referent, queue) { val id = System.identityHashCode(referent) } -class LockContextManager { +class ReferencedContextManager(val contextProducer: (Any) -> T) { val queue = ReferenceQueue() - val lockMap = mutableMapOf() - fun getLockContext(lock: Any): LockContext { + val lockMap = mutableMapOf() + fun getLockContext(lock: Any): T { val id = System.identityHashCode(lock) if (!lockMap.containsKey(id)) { - lockMap[id] = ReentrantLockContext() + lockMap[id] = contextProducer(lock) IdentityPhantomReference(lock, queue) gc() } return lockMap[id]!! } - fun addLockContext(lock: Any, context: LockContext) { + fun addContext(lock: Any, context: T) { val id = System.identityHashCode(lock) lockMap[id] = context IdentityPhantomReference(lock, queue) @@ -28,14 +28,13 @@ class LockContextManager { } fun done() { - lockMap.clear() + gc() } fun gc() { var ref = queue.poll() while (ref != null) { val id = (ref as IdentityPhantomReference<*>).id - assert(lockMap[id]!!.isEmpty()) lockMap.remove(id) ref = queue.poll() } diff --git a/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/SemaphoreContext.kt b/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/SemaphoreContext.kt new file mode 100644 index 00000000..a571a33d --- /dev/null +++ b/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/SemaphoreContext.kt @@ -0,0 +1,44 @@ +package cmu.pasta.sfuzz.core.concurrency.locks + +import cmu.pasta.sfuzz.core.GlobalContext +import cmu.pasta.sfuzz.core.ThreadState +import cmu.pasta.sfuzz.core.concurrency.operations.ThreadResumeOperation + +class SemaphoreContext(var totalPermits: Int) { + private val lockWaiters = mutableMapOf() + + fun acquire(permits: Int, shouldBlock: Boolean): Boolean { + if (totalPermits > permits) { + totalPermits -= permits + return true + } else if (shouldBlock) { + lockWaiters[Thread.currentThread().id] = permits + } + return false + } + + fun drainPermits(): Int { + val permits = totalPermits + totalPermits = 0 + return permits + } + + fun release(permits: Int) { + totalPermits += permits + if (totalPermits > 0) { + val it = lockWaiters.iterator() + while (it.hasNext()) { + val (tid, p) = it.next() + if (totalPermits >= p) { + GlobalContext.registeredThreads[tid]!!.pendingOperation = ThreadResumeOperation() + GlobalContext.registeredThreads[tid]!!.state = ThreadState.Enabled + lockWaiters.remove(tid) + } + } + } + } + + fun reducePermits(permits: Int) { + totalPermits -= permits + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/SemaphoreManager.kt b/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/SemaphoreManager.kt new file mode 100644 index 00000000..09e1bcfb --- /dev/null +++ b/core/src/main/kotlin/cmu/pasta/sfuzz/core/concurrency/locks/SemaphoreManager.kt @@ -0,0 +1,34 @@ +package cmu.pasta.sfuzz.core.concurrency.locks + +import java.util.concurrent.Semaphore + +class SemaphoreManager { + val lockContextManager = ReferencedContextManager {it -> + if (it is Semaphore) { + SemaphoreContext(it.availablePermits()) + } else { + throw IllegalArgumentException("SemaphoreManager can only manage Semaphore objects") + } + } + + fun init(sem: Semaphore) { + val context = SemaphoreContext(sem.availablePermits()) + lockContextManager.addContext(sem, context) + } + + fun acquire(sem: Semaphore, permits: Int, shouldBlock: Boolean): Boolean { + return lockContextManager.getLockContext(sem).acquire(permits, shouldBlock) + } + + fun release(sem: Semaphore, permits: Int) { + lockContextManager.getLockContext(sem).release(permits) + } + + fun drainPermits(sem: Semaphore): Int { + return lockContextManager.getLockContext(sem).drainPermits() + } + + fun reducePermits(sem: Semaphore, permits: Int) { + lockContextManager.getLockContext(sem).reducePermits(permits) + } +} \ No newline at end of file diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/JDKInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/JDKInstrumenter.kt index a8b5fa3e..84fde5bc 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/JDKInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/JDKInstrumenter.kt @@ -31,6 +31,7 @@ fun instrumentClass(path:String, inputStream: InputStream): ByteArray { cv = UnsafeInstrumenter(cv) cv = ClassloaderInstrumenter(cv) cv = ObjectInstrumenter(cv) + cv = SemaphoreInstrumenter(cv) // MonitorInstrumenter should come second because ObjectInstrumenter will insert more // monitors. cv = MonitorInstrumenter(cv) @@ -38,7 +39,7 @@ fun instrumentClass(path:String, inputStream: InputStream): ByteArray { // it inlines monitors for synchronized methods. // cv = SynchronizedMethodInstrumenter(cv) classReader.accept(cv, ClassReader.EXPAND_FRAMES) - var out = classWriter.toByteArray() + val out = classWriter.toByteArray() File("/tmp/out/jdk/${path.replace("/", ".").removePrefix(".")}").writeBytes(out) // File("/tmp/${path.replace("/", ".").removePrefix(".")}").writeBytes(out) return out diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ClassloaderInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ClassloaderInstrumenter.kt index ba833313..e55e8f26 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ClassloaderInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ClassloaderInstrumenter.kt @@ -30,7 +30,8 @@ class ClassloaderInstrumenter(cv: ClassVisitor): ClassVisitor(ASM9, cv) { val mv = super.visitMethod(access, name, descriptor, signature, exceptions) if ((name == "loadClass" && descriptor == "(Ljava/lang/String;)Ljava/lang/Class;" && className == "java/lang/ClassLoader") || (name == "makeImpl" && className == "java/lang/invoke/MethodType")) { - return MethodExitVisitor(MethodEnterVisitor(mv, Runtime::onLoadClass, false), Runtime::onLoadClassDone, access, name, descriptor, false) + val eMv = MethodEnterVisitor(mv, Runtime::onLoadClass, access, name, descriptor, false, false) + return MethodExitVisitor(eMv, Runtime::onLoadClassDone, access, name, descriptor, false, false) } return mv } diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ConditionInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ConditionInstrumenter.kt index 02dffc10..8a98b397 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ConditionInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ConditionInstrumenter.kt @@ -16,14 +16,14 @@ class ConditionInstrumenter(cv:ClassVisitor): ClassVisitorBase(cv, ConditionObje exceptions: Array? ): MethodVisitor { if (name.startsWith("await")) { - return MethodExitVisitor(MethodEnterVisitor(mv, Runtime::onConditionAwait, true), - Runtime::onConditionAwaitDone, access, name, descriptor, true) + val eMv = MethodEnterVisitor(mv, Runtime::onConditionAwait, access, name, descriptor, true, false) + return MethodExitVisitor(eMv, Runtime::onConditionAwaitDone, access, name, descriptor, true, false) } if (name == "signal") { - return MethodEnterVisitor(mv, Runtime::onConditionSignal, true) + return MethodEnterVisitor(mv, Runtime::onConditionSignal, access, name, descriptor, true, false) } if (name == "signalAll") { - return MethodEnterVisitor(mv, Runtime::onConditionSignalAll, true) + return MethodEnterVisitor(mv, Runtime::onConditionSignalAll, access, name, descriptor, true, false) } return mv } diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/LockInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/LockInstrumenter.kt index 29f6d34e..aa71f7c8 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/LockInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/LockInstrumenter.kt @@ -22,15 +22,15 @@ class LockInstrumenter(cv:ClassVisitor): ClassVisitorBase(cv, ReentrantLock::cla exceptions: Array? ): MethodVisitor { if (name == "tryLock") { - return MethodEnterVisitor(mv, Runtime::onLockTryLock, true) + return MethodEnterVisitor(mv, Runtime::onLockTryLock, access, name, descriptor, true, false) } if (name == "lock" || name == "lockInterruptibly") { - return MethodExitVisitor(MethodEnterVisitor(mv, Runtime::onLockLock, true), - Runtime::onLockLockDone, access, name, descriptor, true) + val eMv = MethodEnterVisitor(mv, Runtime::onLockLock, access, name, descriptor, true, false) + return MethodExitVisitor(eMv, Runtime::onLockLockDone, access, name, descriptor, true, false) } if (name == "unlock") { - return MethodExitVisitor(MethodEnterVisitor(mv, Runtime::onLockUnlock, true), - Runtime::onLockUnlockDone, access, name, descriptor, true) + val eMv = MethodEnterVisitor(mv, Runtime::onLockUnlock, access, name, descriptor, true, false) + return MethodExitVisitor(eMv, Runtime::onLockUnlockDone, access, name, descriptor, true, false) } if (name == "newCondition") { return NewConditionVisitor(mv, Runtime::onLockNewCondition, access, name, descriptor) diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/LockSupportInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/LockSupportInstrumenter.kt index b0e7ae2d..764084bc 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/LockSupportInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/LockSupportInstrumenter.kt @@ -17,30 +17,12 @@ class LockSupportInstrumenter(cv: ClassVisitor): ClassVisitorBase(cv, LockSuppor ): MethodVisitor { var mv = super.instrumentMethod(mv, access, name, descriptor, signature, exceptions) if (name.startsWith("park")) { - return MethodExitVisitor(MethodEnterVisitor(mv, Runtime::onThreadPark, false), Runtime::onThreadParkDone, access, name, descriptor, false) + val eMv = MethodEnterVisitor(mv, Runtime::onThreadPark, access, name, descriptor, false, false) + return MethodExitVisitor(eMv, Runtime::onThreadParkDone, access, name, descriptor, false, false) } if (name.startsWith("unpark")) { - return object: AdviceAdapter(ASM9, mv, access, name, descriptor) { - override fun onMethodEnter() { - loadArg(0) - mv.visitMethodInsn(INVOKESTATIC, - Runtime::class.java.name.replace('.', '/'), - Runtime::onThreadUnpark.name, - Utils.kFunctionToJvmMethodDescriptor(Runtime::onThreadUnpark), - false) - super.onMethodEnter() - } - - override fun onMethodExit(opcode: Int) { - loadArg(0) - mv.visitMethodInsn(INVOKESTATIC, - Runtime::class.java.name.replace('.', '/'), - Runtime::onThreadUnparkDone.name, - Utils.kFunctionToJvmMethodDescriptor(Runtime::onThreadUnparkDone), - false) - super.onMethodExit(opcode) - } - } + val eMv = MethodEnterVisitor(mv, Runtime::onThreadUnpark, access, name, descriptor, false, true) + return MethodExitVisitor(eMv, Runtime::onThreadUnparkDone, access, name, descriptor, false, true) } return mv } diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/MethodEnterVisitor.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/MethodEnterVisitor.kt index 07719b71..fb65eb73 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/MethodEnterVisitor.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/MethodEnterVisitor.kt @@ -4,13 +4,19 @@ import cmu.pasta.sfuzz.runtime.Runtime import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes.ASM9 +import org.objectweb.asm.Type +import org.objectweb.asm.commons.AdviceAdapter import kotlin.reflect.KFunction -class MethodEnterVisitor(mv: MethodVisitor, val method: KFunction<*>, val loadThis: Boolean): MethodVisitor(ASM9, mv) { +class MethodEnterVisitor(mv: MethodVisitor, val method: KFunction<*>, access: Int, name: String, descriptor: String, + val loadThis: Boolean, val loadArgs: Boolean): AdviceAdapter(ASM9, mv, access, name, descriptor) { override fun visitCode() { super.visitCode() if (loadThis) { - visitVarInsn(Opcodes.ALOAD, 0) + loadThis() + } + if (loadArgs) { + loadArgs() } visitMethodInsn(Opcodes.INVOKESTATIC, Runtime::class.java.name.replace(".", "/"), method.name, diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/MethodExitVisitor.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/MethodExitVisitor.kt index 36465473..ae43c338 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/MethodExitVisitor.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/MethodExitVisitor.kt @@ -4,12 +4,10 @@ import cmu.pasta.sfuzz.runtime.Runtime import org.objectweb.asm.Label import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes -import org.objectweb.asm.Opcodes.ASM9 -import org.objectweb.asm.Opcodes.ATHROW import org.objectweb.asm.commons.AdviceAdapter import kotlin.reflect.KFunction -class MethodExitVisitor(mv: MethodVisitor, val method: KFunction<*>, access: Int, name: String, descriptor: String, val loadThis: Boolean): AdviceAdapter(ASM9, mv, access, name, descriptor) { +class MethodExitVisitor(mv: MethodVisitor, val method: KFunction<*>, access: Int, name: String, descriptor: String, val loadThis: Boolean, val loadArgs: Boolean): AdviceAdapter(ASM9, mv, access, name, descriptor) { val methodEnterLabel = Label() val methodExitLabel = Label() override fun onMethodEnter() { @@ -22,6 +20,9 @@ class MethodExitVisitor(mv: MethodVisitor, val method: KFunction<*>, access: Int if (loadThis) { loadThis() } + if (loadArgs) { + loadArgs() + } visitMethodInsn(Opcodes.INVOKESTATIC, Runtime::class.java.name.replace(".", "/"), method.name, Utils.kFunctionToJvmMethodDescriptor(method), false) @@ -35,6 +36,9 @@ class MethodExitVisitor(mv: MethodVisitor, val method: KFunction<*>, access: Int if (loadThis) { loadThis() } + if (loadArgs) { + loadArgs() + } visitMethodInsn(Opcodes.INVOKESTATIC, Runtime::class.java.name.replace(".", "/"), method.name, Utils.kFunctionToJvmMethodDescriptor(method), false) diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ObjectInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ObjectInstrumenter.kt index b5b07c97..35561efc 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ObjectInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ObjectInstrumenter.kt @@ -17,7 +17,10 @@ class ObjectInstrumenter(cv: ClassVisitor): ClassVisitorBase(cv, Object::class.j exceptions: Array? ): MethodVisitor { if (name == "wait" && descriptor == "(J)V") { - return object: AdviceAdapter(ASM9, MethodEnterVisitor(mv, Runtime::onObjectWait, true), access, name, descriptor) { + val eMv = MethodEnterVisitor(mv, Runtime::onObjectWait, access, name, descriptor, true, false) + // We cannot use MethodExitVisitor here because `onObjectWaitDone` may throw an `InterruptedException` + // So we cannot catch that exception twice. + return object: AdviceAdapter(ASM9, eMv, access, name, descriptor) { val methodEnterLabel = Label() val methodExitLabel = Label() override fun onMethodEnter() { diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ReentrantReadWriteLockInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ReentrantReadWriteLockInstrumenter.kt index 7176488d..a6df59a5 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ReentrantReadWriteLockInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ReentrantReadWriteLockInstrumenter.kt @@ -15,7 +15,7 @@ class ReentrantReadWriteLockInstrumenter(cv: ClassVisitor): ClassVisitorBase(cv, exceptions: Array? ): MethodVisitor { if (name == "" && descriptor == "(Z)V") { - return MethodExitVisitor(mv, Runtime::onReentrantReadWriteLockInit, access, name, descriptor, true) + return MethodExitVisitor(mv, Runtime::onReentrantReadWriteLockInit, access, name, descriptor, true, false) } return mv } diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/SemaphoreInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/SemaphoreInstrumenter.kt new file mode 100644 index 00000000..fa30909d --- /dev/null +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/SemaphoreInstrumenter.kt @@ -0,0 +1,46 @@ +package cmu.pasta.sfuzz.instrumentation.visitors + +import cmu.pasta.sfuzz.runtime.Runtime +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import java.util.concurrent.Semaphore + +class SemaphoreInstrumenter(cv: ClassVisitor): ClassVisitorBase(cv, Semaphore::class.java.name) { + override fun instrumentMethod( + mv: MethodVisitor, + access: Int, + name: String, + descriptor: String, + signature: String?, + exceptions: Array? + ): MethodVisitor { + if (name == "") { + return MethodExitVisitor(mv, Runtime::onSemaphoreInit, access, name, descriptor, true, false) + } + if ((name == "acquire" || name == "acquireUninterruptibly") && descriptor == "()V") { + val eMv = MethodEnterVisitor(mv, Runtime::onSemaphoreAcquire, access, name, descriptor, true, true) + return MethodExitVisitor(eMv, Runtime::onSemaphoreAcquireDone, access, name, descriptor, false, false) + } + if ((name == "acquire" || name == "acquireUninterruptibly") && descriptor == "(I)V") { + val eMv = MethodEnterVisitor(mv, Runtime::onSemaphoreAcquirePermits, access, name, descriptor, true, true) + return MethodExitVisitor(eMv, Runtime::onSemaphoreAcquireDone, access, name, descriptor, false, false) + } + if (name == "release" && descriptor == "()V") { + val eMv = MethodEnterVisitor(mv, Runtime::onSemaphoreRelease, access, name, descriptor, true, true) + return MethodExitVisitor(eMv, Runtime::onSemaphoreReleaseDone, access, name, descriptor, false, false) + } + if (name == "release" && descriptor == "(I)V") { + val eMv = MethodEnterVisitor(mv, Runtime::onSemaphoreReleasePermits, access, name, descriptor, true, true) + return MethodExitVisitor(eMv, Runtime::onSemaphoreReleaseDone, access, name, descriptor, false, false) + } + if (name == "drainPermits") { + val eMv = MethodEnterVisitor(mv, Runtime::onSemaphoreDrainPermits, access, name, descriptor, true, false) + return MethodExitVisitor(eMv, Runtime::onSemaphoreDrainPermitsDone, access, name, descriptor, false, false) + } + if (name == "reducePermits") { + val eMv = MethodEnterVisitor(mv, Runtime::onSemaphoreReducePermits, access, name, descriptor, true, true) + return MethodExitVisitor(eMv, Runtime::onSemaphoreReducePermitsDone, access, name, descriptor, false, false) + } + return mv + } +} \ No newline at end of file diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ThreadInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ThreadInstrumenter.kt index 82f734f9..f623f2f7 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ThreadInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/ThreadInstrumenter.kt @@ -17,19 +17,20 @@ class ThreadInstrumenter(cv: ClassVisitor): ClassVisitorBase(cv, Thread::class.j exceptions: Array? ): MethodVisitor { if (name == "start") { - return MethodEnterVisitor(MethodExitVisitor(mv, Runtime::onThreadStartDone, access, name, descriptor, true), Runtime::onThreadStart, true) + val eMv = MethodExitVisitor(mv, Runtime::onThreadStartDone, access, name, descriptor, true, false) + return MethodEnterVisitor(eMv, Runtime::onThreadStart, access, name, descriptor, true, false) } if (name == "yield") { - return MethodEnterVisitor(mv, Runtime::onYield, false) + return MethodEnterVisitor(mv, Runtime::onYield, access, name, descriptor, false, false) } if (name == "getAndClearInterrupt") { - return MethodExitVisitor(mv, Runtime::onThreadGetAndClearInterrupt, access, name, descriptor, true) + return MethodExitVisitor(mv, Runtime::onThreadGetAndClearInterrupt, access, name, descriptor, true, false) } if (name == "clearInterrupt") { - return MethodExitVisitor(mv, Runtime::onThreadClearInterrupt, access, name, descriptor, true) + return MethodExitVisitor(mv, Runtime::onThreadClearInterrupt, access, name, descriptor, true, false) } if (name == "setInterrupt" || name == "interrupt") { - return MethodEnterVisitor(mv, Runtime::onThreadInterrupt, true) + return MethodEnterVisitor(mv, Runtime::onThreadInterrupt, access, name, descriptor, true, false) } return mv } diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/UnsafeInstrumenter.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/UnsafeInstrumenter.kt index cc150631..8bb3ec8b 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/UnsafeInstrumenter.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/UnsafeInstrumenter.kt @@ -15,7 +15,7 @@ class UnsafeInstrumenter(cv: ClassVisitor): ClassVisitorBase(cv, "sun.misc.Unsaf ): MethodVisitor { // TODO(aoli): consider passing more information (memory location, ect) if (unsafeMethodNames.contains(name)) { - return MethodEnterVisitor(mv, Runtime::onUnsafeOperation, false) + return MethodEnterVisitor(mv, Runtime::onUnsafeOperation, access, name, descriptor, false, false) } return mv } diff --git a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/Utils.kt b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/Utils.kt index df4235fb..fd1db358 100644 --- a/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/Utils.kt +++ b/instrumentation/src/main/kotlin/cmu/pasta/sfuzz/instrumentation/visitors/Utils.kt @@ -18,8 +18,16 @@ object Utils { Double::class -> "D" Void::class -> "V" Unit::class -> "V" + IntArray::class -> "[I" + LongArray::class -> "[J" + BooleanArray::class -> "[Z" + ByteArray::class -> "[B" + CharArray::class -> "[C" + ShortArray::class -> "[S" + FloatArray::class -> "[F" + DoubleArray::class -> "[D" else -> { - var className = kType.javaType.typeName; + val className = kType.javaType.typeName; "L${className.replace('.', '/')};" } } diff --git a/instrumentation/src/test/kotlin/visitors/UtilsTest.kt b/instrumentation/src/test/kotlin/visitors/UtilsTest.kt index 69f17820..67bc9b35 100644 --- a/instrumentation/src/test/kotlin/visitors/UtilsTest.kt +++ b/instrumentation/src/test/kotlin/visitors/UtilsTest.kt @@ -1,6 +1,7 @@ package visitors import cmu.pasta.sfuzz.instrumentation.visitors.Utils +import cmu.pasta.sfuzz.runtime.Runtime import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -12,8 +13,8 @@ class UtilsTest { assertEquals("(Ljava/lang/String;I)J", Utils.kFunctionToJvmMethodDescriptor(::exampleMethod)) } -// @Test -// fun kFunctionToJvmMethodDescriptorWithStaticMethod() { -// assertEquals("()V", Utils.kFunctionToJvmMethodDescriptor(Runtime::onThreadRun)) -// } + @Test + fun kFunctionToJvmMethodDescriptorWithStaticMethod() { + assertEquals("(Ljava/util/concurrent/Semaphore;[I)V", Utils.kFunctionToJvmMethodDescriptor(Runtime::onSemaphoreAcquire)) + } } \ No newline at end of file diff --git a/integration-tests/src/test/java/cmu/pasta/sfuzz/it/core/SemaphoreTest.java b/integration-tests/src/test/java/cmu/pasta/sfuzz/it/core/SemaphoreTest.java new file mode 100644 index 00000000..13d5097f --- /dev/null +++ b/integration-tests/src/test/java/cmu/pasta/sfuzz/it/core/SemaphoreTest.java @@ -0,0 +1,31 @@ +package cmu.pasta.sfuzz.it.core; + +import cmu.pasta.sfuzz.it.IntegrationTestRunner; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +public class SemaphoreTest extends IntegrationTestRunner { + @Test + public void testSemaphore() { + String event = "[1]: Starting A\n" + + "[1]: A is waiting for a permit.\n" + + "[1]: A gets a permit.\n" + + "[1]: A: 1\n" + + "[1]: A: 2\n" + + "[1]: A: 3\n" + + "[1]: A: 4\n" + + "[1]: A: 5\n" + + "[1]: A releases the permit.\n" + + "[2]: Starting B\n" + + "[2]: B is waiting for a permit.\n" + + "[2]: B gets a permit.\n" + + "[2]: B: 4\n" + + "[2]: B: 3\n" + + "[2]: B: 2\n" + + "[2]: B: 1\n" + + "[2]: B: 0\n" + + "[2]: B releases the permit.\n" + + "[0]: count: 0\n"; + assertEquals(event, runTest("testSemaphore")); + } +} diff --git a/integration-tests/src/test/java/cmu/pasta/sfuzz/it/core/ThreadInterruptTest.java b/integration-tests/src/test/java/cmu/pasta/sfuzz/it/core/ThreadInterruptTest.java index 47672e58..772ff679 100644 --- a/integration-tests/src/test/java/cmu/pasta/sfuzz/it/core/ThreadInterruptTest.java +++ b/integration-tests/src/test/java/cmu/pasta/sfuzz/it/core/ThreadInterruptTest.java @@ -9,12 +9,12 @@ public class ThreadInterruptTest extends IntegrationTestRunner { @Test public void testInterruptBeforeWait() { - assertEquals("[1]: Interrupted", runTest("testInterruptBeforeWait")); + assertEquals("[1]: Interrupted\n", runTest("testInterruptBeforeWait")); } @Test public void testInterruptDuringWait() { - assertEquals("[1]: Interrupted", runTest("testInterruptDuringWait")); + assertEquals("[1]: Interrupted\n", runTest("testInterruptDuringWait")); } @Test diff --git a/integration-tests/src/test/java/example/SemaphoreTest.java b/integration-tests/src/test/java/example/SemaphoreTest.java new file mode 100644 index 00000000..5fb29c03 --- /dev/null +++ b/integration-tests/src/test/java/example/SemaphoreTest.java @@ -0,0 +1,127 @@ +package example; + +// java program to demonstrate +// use of semaphores Locks +import jdk.jshell.execution.Util; + +import java.util.concurrent.*; + +//A shared resource/class. +class Shared +{ + static int count = 0; +} + +class MyThread extends Thread +{ + Semaphore sem; + String threadName; + public MyThread(Semaphore sem, String threadName) + { + super(threadName); + this.sem = sem; + this.threadName = threadName; + } + + @Override + public void run() { + + // run by thread A + if(this.getName().equals("A")) + { + Utils.log("Starting " + threadName); + try + { + // First, get a permit. + Utils.log(threadName + " is waiting for a permit."); + + // acquiring the lock + sem.acquire(); + + Utils.log(threadName + " gets a permit."); + + // Now, accessing the shared resource. + // other waiting threads will wait, until this + // thread release the lock + for(int i=0; i < 5; i++) + { + Shared.count++; + Utils.log(threadName + ": " + Shared.count); + + // Now, allowing a context switch -- if possible. + // for thread B to execute + Thread.sleep(10); + } + } catch (InterruptedException exc) { + Utils.log(exc.toString()); + } + + // Release the permit. + Utils.log(threadName + " releases the permit."); + sem.release(); + } + + // run by thread B + else + { + Utils.log("Starting " + threadName); + try + { + // First, get a permit. + Utils.log(threadName + " is waiting for a permit."); + + // acquiring the lock + sem.acquire(); + + Utils.log(threadName + " gets a permit."); + + // Now, accessing the shared resource. + // other waiting threads will wait, until this + // thread release the lock + for(int i=0; i < 5; i++) + { + Shared.count--; + Utils.log(threadName + ": " + Shared.count); + + // Now, allowing a context switch -- if possible. + // for thread A to execute + Thread.sleep(10); + } + } catch (InterruptedException exc) { + Utils.log(exc.toString()); + } + // Release the permit. + Utils.log(threadName + " releases the permit."); + sem.release(); + } + } +} + +// Driver class +public class SemaphoreTest +{ + public static void testSemaphore() throws InterruptedException + { + // creating a Semaphore object + // with number of permits 1 + Semaphore sem = new Semaphore(1); + + // creating two threads with name A and B + // Note that thread A will increment the count + // and thread B will decrement the count + MyThread mt1 = new MyThread(sem, "A"); + MyThread mt2 = new MyThread(sem, "B"); + + // stating threads A and B + mt1.start(); + mt2.start(); + + // waiting for threads A and B + mt1.join(); + mt2.join(); + + // count will always remain 0 after + // both threads will complete their execution + Utils.log("count: " + Shared.count); + } +} diff --git a/runtime/src/main/java/cmu/pasta/sfuzz/runtime/Delegate.java b/runtime/src/main/java/cmu/pasta/sfuzz/runtime/Delegate.java index b0385217..00a6876a 100644 --- a/runtime/src/main/java/cmu/pasta/sfuzz/runtime/Delegate.java +++ b/runtime/src/main/java/cmu/pasta/sfuzz/runtime/Delegate.java @@ -1,5 +1,6 @@ package cmu.pasta.sfuzz.runtime; +import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -125,6 +126,33 @@ public void onThreadInterrupt(Thread t) { public void onReentrantReadWriteLockInit(ReentrantReadWriteLock lock) { } + public void onSemaphoreInit(Semaphore sem) { + } + + public void onSemaphoreAcquire(Semaphore sem, int permits) { + } + + public void onSemaphoreAcquireDone() { + } + + public void onSemaphoreDrainPermits(Semaphore sem) { + } + + public void onSemaphoreDrainPermitsDone() { + } + + public void onSemaphoreRelease(Semaphore sem, int permits) { + } + + public void onSemaphoreReleaseDone() { + } + + public void onSemaphoreReducePermits(Semaphore sem, int permits) { + } + + public void onSemaphoreReducePermitsDone() { + } + public boolean onThreadClearInterrupt(Boolean originValue, Thread t) { return originValue; } diff --git a/runtime/src/main/java/cmu/pasta/sfuzz/runtime/Runtime.java b/runtime/src/main/java/cmu/pasta/sfuzz/runtime/Runtime.java index aec6935c..1dc994e7 100644 --- a/runtime/src/main/java/cmu/pasta/sfuzz/runtime/Runtime.java +++ b/runtime/src/main/java/cmu/pasta/sfuzz/runtime/Runtime.java @@ -1,5 +1,6 @@ package cmu.pasta.sfuzz.runtime; +import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -173,4 +174,48 @@ public static void onThreadClearInterrupt(Thread t) { public static void onReentrantReadWriteLockInit(ReentrantReadWriteLock lock) { DELEGATE.onReentrantReadWriteLockInit(lock); } + + public static void onSemaphoreInit(Semaphore sem) { + DELEGATE.onSemaphoreInit(sem); + } + + public static void onSemaphoreAcquirePermits(Semaphore sem, int permits) { + DELEGATE.onSemaphoreAcquire(sem, permits); + } + + public static void onSemaphoreAcquire(Semaphore sem) { + DELEGATE.onSemaphoreAcquire(sem, 1); + } + + public static void onSemaphoreAcquireDone() { + DELEGATE.onSemaphoreAcquireDone(); + } + + public static void onSemaphoreReleasePermits(Semaphore sem, int permits) { + DELEGATE.onSemaphoreRelease(sem, permits); + } + + public static void onSemaphoreRelease(Semaphore sem) { + DELEGATE.onSemaphoreRelease(sem, 1); + } + + public static void onSemaphoreReleaseDone() { + DELEGATE.onSemaphoreReleaseDone(); + } + + public static void onSemaphoreDrainPermitsDone() { + DELEGATE.onSemaphoreDrainPermitsDone(); + } + + public static void onSemaphoreReducePermits(Semaphore sem, int permits) { + DELEGATE.onSemaphoreReducePermits(sem, permits); + } + + public static void onSemaphoreReducePermitsDone() { + DELEGATE.onSemaphoreReducePermitsDone(); + } + + public static void onSemaphoreDrainPermits(Semaphore sem) { + DELEGATE.onSemaphoreDrainPermits(sem); + } }