From 6dfb131736d787ea7150909dd4264d4683212d86 Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Mon, 21 Nov 2022 16:44:26 +0800 Subject: [PATCH 01/12] Added wrappers for Thread and ThreadGroup --- .github/workflows/framework-tests-matrix.json | 2 +- .../threads/ThreadStartExampleTest.kt | 25 ++ .../engine/overrides/strings/UtString.java | 2 +- .../engine/overrides/threads/UtThread.java | 338 ++++++++++++++++++ .../overrides/threads/UtThreadGroup.java | 253 +++++++++++++ .../main/kotlin/org/utbot/engine/Memory.kt | 14 + .../kotlin/org/utbot/engine/ObjectWrappers.kt | 14 +- .../kotlin/org/utbot/engine/ThreadWrappers.kt | 101 ++++++ .../main/kotlin/org/utbot/engine/Traverser.kt | 31 +- .../MemoryUpdateSimplificator.kt | 6 + .../org/utbot/engine/types/TypeResolver.kt | 4 + .../org/utbot/framework/util/SootUtils.kt | 5 +- .../examples/threads/ThreadStartExample.java | 9 + 13 files changed, 793 insertions(+), 11 deletions(-) create mode 100644 utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadStartExampleTest.kt create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java create mode 100644 utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt create mode 100644 utbot-sample/src/main/java/org/utbot/examples/threads/ThreadStartExample.java diff --git a/.github/workflows/framework-tests-matrix.json b/.github/workflows/framework-tests-matrix.json index 300a6e187c..dbf7cd89dd 100644 --- a/.github/workflows/framework-tests-matrix.json +++ b/.github/workflows/framework-tests-matrix.json @@ -22,7 +22,7 @@ }, { "PART_NAME": "examples-part2", - "TESTS_TO_RUN": "--tests \"org.utbot.examples.exceptions.*\" --tests \"org.utbot.examples.invokes.*\" --tests \"org.utbot.examples.lambda.*\" --tests \"org.utbot.examples.make.symbolic.*\" --tests \"org.utbot.examples.math.*\" --tests \"org.utbot.examples.mixed.*\" --tests \"org.utbot.examples.mock.*\" --tests \"org.utbot.examples.models.*\" --tests \"org.utbot.examples.natives.*\" --tests \"org.utbot.examples.objects.*\" --tests \"org.utbot.examples.reflection.*\"" + "TESTS_TO_RUN": "--tests \"org.utbot.examples.exceptions.*\" --tests \"org.utbot.examples.invokes.*\" --tests \"org.utbot.examples.lambda.*\" --tests \"org.utbot.examples.make.symbolic.*\" --tests \"org.utbot.examples.math.*\" --tests \"org.utbot.examples.mixed.*\" --tests \"org.utbot.examples.mock.*\" --tests \"org.utbot.examples.models.*\" --tests \"org.utbot.examples.natives.*\" --tests \"org.utbot.examples.objects.*\" --tests \"org.utbot.examples.reflection.*\" --tests org.utbot.examples.threads.*\"" }, { "PART_NAME": "examples-part3", diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadStartExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadStartExampleTest.kt new file mode 100644 index 0000000000..5734e05bc2 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadStartExampleTest.kt @@ -0,0 +1,25 @@ +package org.utbot.examples.threads + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.withoutConcrete +import org.utbot.tests.infrastructure.UtValueTestCaseChecker +import org.utbot.tests.infrastructure.ignoreExecutionsNumber +import org.utbot.tests.infrastructure.isException + +class ThreadStartExampleTest : UtValueTestCaseChecker(testClass = ThreadStartExample::class) { + @Test + // TODO minimization does not work + fun testExceptionInStart() { + // TODO concrete execution does not discover an exception - looks like it cannot find an exception in another thread + withoutConcrete { + // TODO an exception in another thread is not captured by assertThrows, we should find another way to support exceptions in different threads + withEnabledTestingCodeGeneration(false) { + checkWithException( + ThreadStartExample::explicitExceptionInStart, + ignoreExecutionsNumber, + { r -> r.isException() } + ) + } + } + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtString.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtString.java index 1cd5024061..84d5a4fe6e 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtString.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtString.java @@ -13,7 +13,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; import static java.lang.Math.min; -@SuppressWarnings({"ConstantConditions", "unused"}) +@SuppressWarnings("unused") public class UtString implements java.io.Serializable, Comparable, CharSequence { char[] value; int length; diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java new file mode 100644 index 0000000000..0cca696167 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java @@ -0,0 +1,338 @@ +package org.utbot.engine.overrides.threads; + +import org.utbot.api.mock.UtMock; + +import java.security.AccessControlContext; +import java.util.Map; + +@SuppressWarnings("unused") +public class UtThread { + private String name; + private int priority; + + private boolean daemon; + + private final Runnable target; + + private final ThreadGroup group; + + /* The context ClassLoader for this thread */ + private ClassLoader contextClassLoader; + + /* For autonumbering anonymous threads. */ + private static int threadInitNumber = 0; + + private static int nextThreadNum() { + return threadInitNumber++; + } + + /* + * Thread ID + */ + private final long tid; + + /* For generating thread ID */ + private static long threadSeqNumber; + + private static long nextThreadID() { + return ++threadSeqNumber; + } + + private boolean isInterrupted; + + /** + * The minimum priority that a thread can have. + */ + public static final int MIN_PRIORITY = 1; + + /** + * The maximum priority that a thread can have. + */ + public static final int MAX_PRIORITY = 10; + + // null unless explicitly set + private volatile Thread.UncaughtExceptionHandler uncaughtExceptionHandler; + + // null unless explicitly set + private static volatile Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler; + + public static UtThread currentThread() { + return UtMock.makeSymbolic(); + } + + public static void yield() { + // Do nothing + } + + @SuppressWarnings("RedundantThrows") + public static void sleep(long ignoredMillis) throws InterruptedException { + // Do nothing + } + + @SuppressWarnings("RedundantThrows") + public static void sleep(long millis, int nanos) throws InterruptedException { + if (millis < 0) { + throw new IllegalArgumentException(); + } + + if (nanos < 0 || nanos > 999999) { + throw new IllegalArgumentException(); + } + } + + public UtThread() { + this(null, null, UtMock.makeSymbolic(), 0); + } + + public UtThread(Runnable target) { + this(null, target, UtMock.makeSymbolic(), 0); + } + + public UtThread(ThreadGroup group, Runnable target) { + this(group, target, UtMock.makeSymbolic(), 0); + } + + public UtThread(String name) { + this(null, null, name, 0); + } + + public UtThread(ThreadGroup group, String name) { + this(group, null, name, 0); + } + + public UtThread(Runnable target, String name) { + this(null, target, name, 0); + } + + public UtThread(ThreadGroup group, Runnable target, String name) { + this(group, target, name, 0); + } + + public UtThread(ThreadGroup group, Runnable target, String name, + long stackSize) { + this(group, target, name, stackSize, null, true); + } + + public UtThread(ThreadGroup group, Runnable target, String name, + long stackSize, boolean inheritUtThreadLocals) { + this(group, target, name, stackSize, null, inheritUtThreadLocals); + } + + private UtThread(ThreadGroup g, Runnable target, String name, + long stackSize, AccessControlContext ignoredAcc, + boolean ignoredInheritUtThreadLocals) { + if (name == null) { + throw new NullPointerException(); + } + + this.name = name; + + if (g == null) { + g = UtMock.makeSymbolic(); + } + + this.group = g; + this.daemon = UtMock.makeSymbolic(); + + final Integer priority = UtMock.makeSymbolic(); + UtMock.assume(priority >= MIN_PRIORITY); + UtMock.assume(priority <= MIN_PRIORITY); + this.priority = priority; + setPriority(this.priority); + + this.contextClassLoader = UtMock.makeSymbolic(); + this.target = target; + + this.tid = nextThreadID(); + } + + public void start() { + run(); + } + + public void run() { + if (target != null) { + target.run(); + } + } + + public final void stop() { + // DO nothing + } + + public void interrupt() { + // Set interrupted status + isInterrupted = true; + } + + public static boolean interrupted() { + return UtMock.makeSymbolic(); + } + + public boolean isInterrupted() { + return isInterrupted; + } + + public final boolean isAlive() { + return UtMock.makeSymbolic(); + } + + public final void suspend() { + // Do nothing + } + + public final void resume() { + // Do nothing + } + + public final void setPriority(int newPriority) { + if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { + throw new IllegalArgumentException(); + } + + if (group != null) { + if (newPriority > group.getMaxPriority()) { + newPriority = group.getMaxPriority(); + } + + priority = newPriority; + } + } + + public final int getPriority() { + return priority; + } + + public final synchronized void setName(String name) { + if (name == null) { + throw new NullPointerException(); + } + + this.name = name; + } + + public final String getName() { + return name; + } + + public final ThreadGroup getThreadGroup() { + return group; + } + + public static int activeCount() { + final Integer result = UtMock.makeSymbolic(); + UtMock.assume(result >= 0); + + return result; + } + + public static int enumerate(UtThread[] tarray) { + Integer length = UtMock.makeSymbolic(); + UtMock.assume(length >= 0); + UtMock.assume(length <= tarray.length); + + for (int i = 0; i < length; i++) { + tarray[i] = UtMock.makeSymbolic(); + } + + return length; + } + + public int countStackFrames() { + return UtMock.makeSymbolic(); + } + + public final void join(long millis) throws InterruptedException { + if (millis < 0) { + throw new IllegalArgumentException(); + } + + // Do nothing + } + + public final void join(long millis, int nanos) throws InterruptedException { + if (millis < 0) { + throw new IllegalArgumentException(); + } + + if (nanos < 0 || nanos > 999999) { + throw new IllegalArgumentException(); + } + + // Do nothing + } + + public final void join() throws InterruptedException { + join(0); + } + + public static void dumpStack() { + // Do nothing + } + + public final void setDaemon(boolean on) { + daemon = on; + } + + public final boolean isDaemon() { + return daemon; + } + + public String toString() { + if (group != null) { + return "Thread[" + getName() + "," + getPriority() + "," + + group.getName() + "]"; + } else { + return "Thread[" + getName() + "," + getPriority() + "," + + "" + "]"; + } + } + + public ClassLoader getContextClassLoader() { + return contextClassLoader; + } + + public void setContextClassLoader(ClassLoader cl) { + contextClassLoader = cl; + } + + public static boolean holdsLock(Object obj) { + if (obj == null) { + throw new NullPointerException(); + } + + return UtMock.makeSymbolic(); + } + + public StackTraceElement[] getStackTrace() { + return UtMock.makeSymbolic(); + } + + public static Map getAllStackTraces() { + return UtMock.makeSymbolic(); + } + + public long getId() { + return tid; + } + + public Thread.State getState() { + return UtMock.makeSymbolic(); + } + + public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) { + defaultUncaughtExceptionHandler = eh; + } + + public static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() { + return defaultUncaughtExceptionHandler; + } + + public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() { + return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; + } + + public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) { + uncaughtExceptionHandler = eh; + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java new file mode 100644 index 0000000000..22a0e7c0d1 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java @@ -0,0 +1,253 @@ +package org.utbot.engine.overrides.threads; + +import org.utbot.api.mock.UtMock; + +import java.util.Arrays; + +@SuppressWarnings("unused") +public class UtThreadGroup implements Thread.UncaughtExceptionHandler { + private final ThreadGroup parent; + String name; + int maxPriority; + boolean destroyed; + boolean daemon; + + int nUnstartedThreads = 0; + int nthreads; + Thread[] threads; + + int ngroups; + ThreadGroup[] groups; + + public UtThreadGroup(String name) { + this(UtThread.currentThread().getThreadGroup(), name); + } + + public UtThreadGroup(ThreadGroup parent, String name) { + this.name = name; + this.maxPriority = UtMock.makeSymbolic(); + this.daemon = UtMock.makeSymbolic(); + this.parent = parent; + } + + public final String getName() { + return name; + } + + public final ThreadGroup getParent() { + return parent; + } + + public final int getMaxPriority() { + return maxPriority; + } + + public final boolean isDaemon() { + return daemon; + } + + public synchronized boolean isDestroyed() { + return destroyed; + } + + public final void setDaemon(boolean daemon) { + this.daemon = daemon; + } + + public final void setMaxPriority(int pri) { + if (pri < UtThread.MIN_PRIORITY || pri > UtThread.MAX_PRIORITY) { + return; + } + + for (int i = 0 ; i < ngroups ; i++) { + groups[i].setMaxPriority(pri); + } + } + + public final boolean parentOf(ThreadGroup g) { + return UtMock.makeSymbolic(); + } + + public final void checkAccess() { + // Do nothing + } + + public int activeCount() { + if (destroyed) { + return 0; + } + + final Integer result = UtMock.makeSymbolic(); + UtMock.assume(result >= 0); + return result; + } + + public int enumerate(Thread[] list) { + return enumerate(list, 0, true); + } + + public int enumerate(Thread[] list, boolean recurse) { + return enumerate(list, 0, recurse); + } + + @SuppressWarnings({"SameParameterValue", "ParameterCanBeLocal"}) + private int enumerate(Thread[] list, int ignoredN, boolean ignoredRecurse) { + if (destroyed) { + return 0; + } + + list = UtMock.makeSymbolic(); + + final Integer result = UtMock.makeSymbolic(); + UtMock.assume(result <= list.length); + UtMock.assume(result >= 0); + + return result; + } + + public int activeGroupCount() { + if (destroyed) { + return 0; + } + + final Integer result = UtMock.makeSymbolic(); + UtMock.assume(result >= 0); + return result; + } + + public int enumerate(ThreadGroup[] list) { + return enumerate(list, 0, true); + } + + public int enumerate(ThreadGroup[] list, boolean recurse) { + return enumerate(list, 0, recurse); + } + + @SuppressWarnings({"SameParameterValue", "ParameterCanBeLocal"}) + private int enumerate(ThreadGroup[] list, int ignoredN, boolean ignoredRecurse) { + if (destroyed) { + return 0; + } + + list = UtMock.makeSymbolic(); + + final Integer result = UtMock.makeSymbolic(); + UtMock.assume(result <= list.length); + UtMock.assume(result >= 0); + + return result; + } + + public final void stop() { + // Do nothing + } + + public final void interrupt() { + for (int i = 0 ; i < nthreads ; i++) { + threads[i].interrupt(); + } + + for (int i = 0 ; i < ngroups ; i++) { + groups[i].interrupt(); + } + } + + public final void suspend() { + // Do nothing + } + + public final void resume() { + // Do nothing + } + + public final void destroy() { + if (destroyed || nthreads > 0) { + throw new IllegalThreadStateException(); + } + + if (parent != null) { + destroyed = true; + ngroups = 0; + groups = null; + nthreads = 0; + threads = null; + } + for (int i = 0 ; i < ngroups ; i += 1) { + groups[i].destroy(); + } + } + + private void add(ThreadGroup g){ + if (destroyed) { + throw new IllegalThreadStateException(); + } + + if (groups == null) { + groups = new ThreadGroup[4]; + } else if (ngroups == groups.length) { + groups = Arrays.copyOf(groups, ngroups * 2); + } + groups[ngroups] = g; + + ngroups++; + } + + private void remove(ThreadGroup g) { + if (destroyed) { + return; + } + + for (int i = 0 ; i < ngroups ; i++) { + if (groups[i] == g) { + ngroups -= 1; + System.arraycopy(groups, i + 1, groups, i, ngroups - i); + groups[ngroups] = null; + break; + } + } + + if (daemon && nthreads == 0 && nUnstartedThreads == 0 && ngroups == 0) { + destroy(); + } + } + + void addUnstarted() { + if (destroyed) { + throw new IllegalThreadStateException(); + } + + nUnstartedThreads++; + } + + void add(Thread t) { + if (destroyed) { + throw new IllegalThreadStateException(); + } + + if (threads == null) { + threads = new Thread[4]; + } else if (nthreads == threads.length) { + threads = Arrays.copyOf(threads, nthreads * 2); + } + threads[nthreads] = t; + + nthreads++; + nUnstartedThreads--; + } + + public void list() { + // Do nothing + } + + public void uncaughtException(Thread t, Throwable e) { + // Do nothing + } + + public boolean allowThreadSuspension(boolean b) { + return true; + } + + public String toString() { + return getClass().getName() + "[name=" + getName() + ",maxpri=" + maxPriority + "]"; + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index 361b9c2373..3a36fe177d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -45,6 +45,7 @@ import soot.CharType import soot.IntType import soot.RefLikeType import soot.Scene +import soot.SootField import soot.Type @@ -64,6 +65,8 @@ import soot.Type * * Note: [staticInitial] contains mapping from [FieldId] to the memory state at the moment of the field initialization. * + * [fieldValues] stores symbolic values for specified fields of the specified object instances. + * * @see memoryForNestedMethod * @see FieldStates */ @@ -77,6 +80,7 @@ data class Memory( // TODO: split purely symbolic memory and information about s private val initializedStaticFields: PersistentSet = persistentHashSetOf(), private val staticFieldsStates: PersistentMap = persistentHashMapOf(), private val meaningfulStaticFields: PersistentSet = persistentHashSetOf(), + private val fieldValues: PersistentMap> = persistentHashMapOf(), private val addrToArrayType: PersistentMap = persistentHashMapOf(), private val addrToMockInfo: PersistentMap = persistentHashMapOf(), private val updates: MemoryUpdate = MemoryUpdate(), // TODO: refactor this later. Now we use it only for statics substitution @@ -110,6 +114,13 @@ data class Memory( // TODO: split purely symbolic memory and information about s fun staticFields(): Map = staticFieldsStates.filterKeys { it in meaningfulStaticFields } + /** + * Returns a symbolic value, associated with the specified [field] of the object with the specified [instanceAddr], + * if present, and null otherwise. + */ + fun fieldValue(field: SootField, instanceAddr: UtAddrExpression): SymbolicValue? = + fieldValues[field]?.get(instanceAddr) + /** * Construct the mapping from addresses to sets of fields whose values are read during the code execution * and therefore should be initialized in a constructed model. @@ -243,6 +254,7 @@ data class Memory( // TODO: split purely symbolic memory and information about s initializedStaticFields = initializedStaticFields.addAll(update.initializedStaticFields), staticFieldsStates = previousMemoryStates.toPersistentMap().putAll(updatedStaticFields), meaningfulStaticFields = meaningfulStaticFields.addAll(update.meaningfulStaticFields), + fieldValues = fieldValues.putAll(update.fieldValues), addrToArrayType = addrToArrayType.putAll(update.addrToArrayType), addrToMockInfo = addrToMockInfo.putAll(update.addrToMockInfo), updates = updates + update, @@ -342,6 +354,7 @@ data class MemoryUpdate( val initializedStaticFields: PersistentSet = persistentHashSetOf(), val staticFieldsUpdates: PersistentList = persistentListOf(), val meaningfulStaticFields: PersistentSet = persistentHashSetOf(), + val fieldValues: PersistentMap> = persistentHashMapOf(), val addrToArrayType: PersistentMap = persistentHashMapOf(), val addrToMockInfo: PersistentMap = persistentHashMapOf(), val visitedValues: PersistentList = persistentListOf(), @@ -361,6 +374,7 @@ data class MemoryUpdate( initializedStaticFields = initializedStaticFields.addAll(other.initializedStaticFields), staticFieldsUpdates = staticFieldsUpdates.addAll(other.staticFieldsUpdates), meaningfulStaticFields = meaningfulStaticFields.addAll(other.meaningfulStaticFields), + fieldValues = fieldValues.putAll(other.fieldValues), addrToArrayType = addrToArrayType.putAll(other.addrToArrayType), addrToMockInfo = addrToMockInfo.putAll(other.addrToMockInfo), visitedValues = visitedValues.addAll(other.visitedValues), diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt index 15e54fe332..a31c5c8395 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt @@ -26,7 +26,6 @@ import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.constructorId import org.utbot.framework.plugin.api.util.id @@ -42,7 +41,6 @@ import java.util.OptionalInt import java.util.OptionalLong import java.util.concurrent.CopyOnWriteArrayList import kotlin.reflect.KClass -import kotlin.reflect.KFunction4 typealias TypeToBeWrapped = RefType typealias WrapperType = RefType @@ -65,6 +63,10 @@ val classToWrapper: MutableMap = putSootClass(OptionalLong::class, UT_OPTIONAL_LONG.className) putSootClass(OptionalDouble::class, UT_OPTIONAL_DOUBLE.className) + // threads + putSootClass(java.lang.Thread::class, utThreadClass) + putSootClass(java.lang.ThreadGroup::class, utThreadGroupClass) + putSootClass(RangeModifiableUnlimitedArray::class, RangeModifiableUnlimitedArrayWrapper::class) putSootClass(AssociativeArray::class, AssociativeArrayWrapper::class) @@ -146,6 +148,10 @@ private val wrappers = mapOf( wrap(OptionalLong::class) { type, addr -> objectValue(type, addr, OptionalWrapper(UT_OPTIONAL_LONG)) }, wrap(OptionalDouble::class) { type, addr -> objectValue(type, addr, OptionalWrapper(UT_OPTIONAL_DOUBLE)) }, + // threads + wrap(Thread::class) { type, addr -> objectValue(type, addr, ThreadWrapper()) }, + wrap(ThreadGroup::class) { type, addr -> objectValue(type, addr, ThreadGroupWrapper()) }, + wrap(RangeModifiableUnlimitedArray::class) { type, addr -> objectValue(type, addr, RangeModifiableUnlimitedArrayWrapper()) }, @@ -250,14 +256,14 @@ interface WrapperInterface { * value of `select` operation. For example, for arrays and lists it's zero, * for associative array it's one. */ - open val selectOperationTypeIndex: Int + val selectOperationTypeIndex: Int get() = 0 /** * Similar to [selectOperationTypeIndex], it is responsible for type index * of the returning value from `get` operation */ - open val getOperationTypeIndex: Int + val getOperationTypeIndex: Int get() = 0 } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt new file mode 100644 index 0000000000..296569cddd --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt @@ -0,0 +1,101 @@ +package org.utbot.engine + +import org.utbot.engine.overrides.threads.UtThread +import org.utbot.engine.overrides.threads.UtThreadGroup +import org.utbot.engine.types.STRING_TYPE +import org.utbot.engine.types.THREAD_GROUP_TYPE +import org.utbot.engine.types.THREAD_TYPE +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.constructorId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.util.nextModelName +import soot.RefType +import soot.Scene +import soot.SootClass +import soot.SootField +import soot.SootMethod + +val utThreadClass: SootClass + get() = Scene.v().getSootClass(UtThread::class.qualifiedName) +val utThreadGroupClass: SootClass + get() = Scene.v().getSootClass(UtThreadGroup::class.qualifiedName) + +class ThreadWrapper : BaseOverriddenWrapper(utThreadClass.name) { + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = null + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = + resolver.run { + val classId = THREAD_TYPE.id + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName("thread") + + val targetField = targetField.fieldId + val values = collectFieldModels(wrapper.addr, overriddenClass.type) + val targetModel = values[targetField] as? UtLambdaModel + + val (constructor, params) = if (targetModel == null) { + constructorId(classId) to emptyList() + } else { + constructorId(classId, runnableType.id) to listOf(targetModel) + } + + val instantiationCall = UtExecutableCallModel( + instance = null, + constructor, + params + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + +// val targetFieldChunkId = resolver.hierarchy.chunkIdForField(utThreadClass.type, targetField) +// val targetFieldChunkDescriptor = MemoryChunkDescriptor(targetFieldChunkId, wrapper.type, runnableType) +// val targetAddr = resolver.findArray(targetFieldChunkDescriptor, state).select(wrapper.addr) + } + + companion object { + private val runnableType: RefType = Scene.v().getSootClass(Runnable::class.qualifiedName).type!! + private val targetField: SootField + get() = utThreadClass.getField("target", runnableType) + } +} + +class ThreadGroupWrapper : BaseOverriddenWrapper(utThreadGroupClass.name) { + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = null + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = + resolver.run { + val classId = THREAD_GROUP_TYPE.id + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName("threadGroup") + + val nameField = nameField.fieldId + val values = collectFieldModels(wrapper.addr, overriddenClass.type) + val nameModel = values[nameField] ?: UtNullModel(stringClassId) + + val instantiationCall = UtExecutableCallModel( + instance = null, + constructorId(classId, stringClassId), + listOf(nameModel) + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + private val nameField: SootField + get() = utThreadGroupClass.getField("name", STRING_TYPE) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 59da8bf29d..9a27ba4393 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -961,7 +961,12 @@ class Traverser( } } - SymbolicStateUpdate(memoryUpdates = objectUpdate) + // We need to associate this field with the created symbolic value to not lose an information about its type. + // For example, if field's declared type is Runnable but at the current state it is a specific lambda, + // we have to save this lambda as a type of this field to be able to retrieve it in the future. + val fieldValuesUpdate = fieldUpdate(left.field, instanceForField.addr, value) + + SymbolicStateUpdate(memoryUpdates = objectUpdate + fieldValuesUpdate) } is JimpleLocal -> SymbolicStateUpdate(localMemoryUpdates = localMemoryUpdate(left.variable to value)) is InvokeExpr -> TODO("Not implemented: $left") @@ -1941,7 +1946,7 @@ class Traverser( /** * For now the field is `meaningful` if it is safe to set, that is, it is not an internal system field nor a - * synthetic field. This filter is needed to prohibit changing internal fields, which can break up our own + * synthetic field or a wrapper's field. This filter is needed to prohibit changing internal fields, which can break up our own * code and which are useless for the user. * * @return `true` if the field is meaningful, `false` otherwise. @@ -1950,7 +1955,9 @@ class Traverser( !Modifier.isSynthetic(field.modifiers) && // we don't want to set fields that cannot be set via reflection anyway !field.fieldId.isInaccessibleViaReflection && - // we should not manually set enum constants + // we should not set static fields from wrappers + !field.declaringClass.isOverridden && + // we should not manually set enum constants !(field.declaringClass.isEnum && field.isEnumConstant) && // we don't want to set fields from library classes !workaround(IGNORE_STATICS_FROM_TRUSTED_LIBRARIES) { @@ -2063,6 +2070,10 @@ class Traverser( field: SootField, mockInfoGenerator: UtMockInfoGenerator? ): SymbolicValue { + memory.fieldValue(field, addr)?.let { + return it + } + val chunkId = hierarchy.chunkIdForField(objectType, field) val createdField = createField(objectType, addr, field.type, chunkId, mockInfoGenerator) @@ -2236,6 +2247,20 @@ class Traverser( return MemoryUpdate(persistentListOf(namedStore(descriptor, instance.addr, value))) } + /** + * Creates a [MemoryUpdate] with [MemoryUpdate.fieldValues] containing [fieldValue] associated with the non-staitc [field] + * of the object instance with the specified [instanceAddr]. + */ + private fun fieldUpdate( + field: SootField, + instanceAddr: UtAddrExpression, + fieldValue: SymbolicValue + ): MemoryUpdate { + val fieldValuesUpdate = persistentHashMapOf(field to persistentHashMapOf(instanceAddr to fieldValue)) + + return MemoryUpdate(fieldValues = fieldValuesUpdate) + } + fun arrayUpdateWithValue( addr: UtAddrExpression, type: ArrayType, diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/MemoryUpdateSimplificator.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/MemoryUpdateSimplificator.kt index eb1520ccaa..a613ab8817 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/MemoryUpdateSimplificator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/MemoryUpdateSimplificator.kt @@ -13,6 +13,7 @@ import org.utbot.engine.MemoryUpdate import org.utbot.engine.MockInfoEnriched import org.utbot.engine.ObjectValue import org.utbot.engine.StaticFieldMemoryUpdateInfo +import org.utbot.engine.SymbolicValue import org.utbot.engine.UtMockInfo import org.utbot.engine.UtNamedStore import org.utbot.engine.pc.Simplificator @@ -20,6 +21,7 @@ import org.utbot.engine.pc.UtAddrExpression import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.FieldId import soot.ArrayType +import soot.SootField typealias StoresType = PersistentList typealias TouchedChunkDescriptorsType = PersistentSet @@ -29,6 +31,7 @@ typealias StaticInstanceStorageType = PersistentMap typealias InitializedStaticFieldsType = PersistentSet typealias StaticFieldsUpdatesType = PersistentList typealias MeaningfulStaticFieldsType = PersistentSet +typealias FieldValuesType = PersistentMap> typealias AddrToArrayTypeType = PersistentMap typealias AddrToMockInfoType = PersistentMap typealias VisitedValuesType = PersistentList @@ -50,6 +53,7 @@ class MemoryUpdateSimplificator( val initializedStaticFields = simplifyInitializedStaticFields(initializedStaticFields) val staticFieldsUpdates = simplifyStaticFieldsUpdates(staticFieldsUpdates) val meaningfulStaticFields = simplifyMeaningfulStaticFields(meaningfulStaticFields) + val fieldValues = simplifyFieldValues(fieldValues) val addrToArrayType = simplifyAddrToArrayType(addrToArrayType) val addrToMockInfo = simplifyAddrToMockInfo(addrToMockInfo) val visitedValues = simplifyVisitedValues(visitedValues) @@ -68,6 +72,7 @@ class MemoryUpdateSimplificator( initializedStaticFields, staticFieldsUpdates, meaningfulStaticFields, + fieldValues, addrToArrayType, addrToMockInfo, visitedValues, @@ -124,6 +129,7 @@ class MemoryUpdateSimplificator( private fun simplifyMeaningfulStaticFields(meaningfulStaticFields: MeaningfulStaticFieldsType): MeaningfulStaticFieldsType = meaningfulStaticFields + private fun simplifyFieldValues(fieldValues: FieldValuesType): FieldValuesType = fieldValues private fun simplifyAddrToArrayType(addrToArrayType: AddrToArrayTypeType): AddrToArrayTypeType = addrToArrayType diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt index 84b43861c0..e0d8b11157 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt @@ -354,6 +354,10 @@ internal val STRING_TYPE: RefType get() = Scene.v().getSootClass(String::class.java.canonicalName).type internal val CLASS_REF_TYPE: RefType get() = CLASS_REF_SOOT_CLASS.type +internal val THREAD_TYPE: RefType + get() = Scene.v().getSootClass(Thread::class.java.canonicalName).type +internal val THREAD_GROUP_TYPE: RefType + get() = Scene.v().getSootClass(ThreadGroup::class.java.canonicalName).type internal val NEW_INSTANCE_SIGNATURE: String = CLASS_REF_SOOT_CLASS.getMethodByName("newInstance").subSignature diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt index 0fc195d704..e04ad78315 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt @@ -5,7 +5,6 @@ import org.utbot.engine.jimpleBody import org.utbot.engine.pureJavaSignature import org.utbot.framework.UtSettings import org.utbot.framework.plugin.api.ExecutableId -import org.utbot.framework.plugin.api.visible.UtStreamConsumingException import org.utbot.framework.plugin.services.JdkInfo import soot.G import soot.PackManager @@ -181,11 +180,13 @@ private val classesToLoad = arrayOf( org.utbot.engine.overrides.strings.UtString::class, org.utbot.engine.overrides.strings.UtStringBuilder::class, org.utbot.engine.overrides.strings.UtStringBuffer::class, + org.utbot.engine.overrides.threads.UtThread::class, + org.utbot.engine.overrides.threads.UtThreadGroup::class, org.utbot.engine.overrides.stream.Stream::class, org.utbot.engine.overrides.stream.Arrays::class, org.utbot.engine.overrides.collections.Collection::class, org.utbot.engine.overrides.collections.List::class, - UtStreamConsumingException::class, + org.utbot.framework.plugin.api.visible.UtStreamConsumingException::class, org.utbot.engine.overrides.stream.UtStream::class, org.utbot.engine.overrides.stream.UtIntStream::class, org.utbot.engine.overrides.stream.UtLongStream::class, diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadStartExample.java b/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadStartExample.java new file mode 100644 index 0000000000..846c93fc80 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadStartExample.java @@ -0,0 +1,9 @@ +package org.utbot.examples.threads; + +public class ThreadStartExample { + public void explicitExceptionInStart() { + new Thread(() -> { + throw new IllegalStateException(); + }).start(); + } +} From bbd6d3958ae75d1347fc1f40afc8b397038cb5fd Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Wed, 23 Nov 2022 15:00:53 +0800 Subject: [PATCH 02/12] Added a wrapper for CompletableFuture --- .../examples/threads/FutureExamplesTest.kt | 20 + .../overrides/threads/CompletableFuture.java | 19 + .../threads/UtCompletableFuture.java | 449 ++++++++++++++++++ .../kotlin/org/utbot/engine/ObjectWrappers.kt | 7 + .../kotlin/org/utbot/engine/ThreadWrappers.kt | 87 +++- .../org/utbot/engine/types/TypeResolver.kt | 3 + .../org/utbot/framework/util/SootUtils.kt | 2 + .../examples/threads/FutureExamples.java | 14 + 8 files changed, 588 insertions(+), 13 deletions(-) create mode 100644 utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt new file mode 100644 index 0000000000..d3fcaca04e --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt @@ -0,0 +1,20 @@ +package org.utbot.examples.threads + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.tests.infrastructure.AtLeast +import org.utbot.tests.infrastructure.UtValueTestCaseChecker +import org.utbot.tests.infrastructure.isException +import java.util.concurrent.ExecutionException + +class FutureExamplesTest : UtValueTestCaseChecker(testClass = FutureExamples::class) { + @Test + fun testThrowingRunnable() { + checkWithException( + FutureExamples::throwingRunnableExample, + eq(1), + { r -> r.isException() }, + coverage = AtLeast(71) + ) + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java new file mode 100644 index 0000000000..85df79fe3f --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java @@ -0,0 +1,19 @@ +package org.utbot.engine.overrides.threads; + +import org.utbot.api.annotation.UtClassMock; + +import java.util.concurrent.Executor; + +@UtClassMock(target = java.util.concurrent.CompletableFuture.class, internalUsage = true) +public class CompletableFuture { + public static java.util.concurrent.CompletableFuture runAsync(Runnable runnable) { + java.util.concurrent.CompletableFuture future = new java.util.concurrent.CompletableFuture<>(); + + return future.thenRun(runnable); + } + + @SuppressWarnings("unused") + public static java.util.concurrent.CompletableFuture runAsync(Runnable runnable, Executor ignoredExecutor) { + return runAsync(runnable); + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java new file mode 100644 index 0000000000..af4a478048 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java @@ -0,0 +1,449 @@ +package org.utbot.engine.overrides.threads; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + +public class UtCompletableFuture implements Future, CompletionStage { + T result; + + Throwable exception; + + public UtCompletableFuture(T result) { + visit(this); + this.result = result; + } + + public UtCompletableFuture() { + visit(this); + } + + public UtCompletableFuture(Throwable exception) { + visit(this); + this.exception = exception; + } + + public UtCompletableFuture(UtCompletableFuture future) { + visit(this); + + result = future.result; + exception = future.exception; + } + + public void eqGenericType(T ignoredValue) { + // Will be processed symbolically + } + + public void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + eqGenericType(result); + + visit(this); + } + + @Override + public CompletableFuture thenApply(Function fn) { + preconditionCheck(); + + final U nextResult; + try { + nextResult = fn.apply(result); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture<>(nextResult).toCompletableFuture(); + } + + @Override + public CompletableFuture thenApplyAsync(Function fn) { + return thenApply(fn); + } + + @Override + public CompletableFuture thenApplyAsync(Function fn, Executor executor) { + return thenApply(fn); + } + + @Override + public CompletableFuture thenAccept(Consumer action) { + preconditionCheck(); + + try { + action.accept(result); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture thenAcceptAsync(Consumer action) { + return thenAccept(action); + } + + @Override + public CompletableFuture thenAcceptAsync(Consumer action, Executor executor) { + return thenAccept(action); + } + + @Override + public CompletableFuture thenRun(Runnable action) { + preconditionCheck(); + + try { + action.run(); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture thenRunAsync(Runnable action) { + return thenRun(action); + } + + @Override + public CompletableFuture thenRunAsync(Runnable action, Executor executor) { + return thenRun(action); + } + + @Override + public CompletableFuture thenCombine(CompletionStage other, BiFunction fn) { + preconditionCheck(); + + final CompletableFuture completableFuture = other.toCompletableFuture(); + if (fn == null || completableFuture == null) { + throw new NullPointerException(); + } + + U otherResult; + try { + otherResult = completableFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + final V nextResult; + try { + nextResult = fn.apply(result, otherResult); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture<>(nextResult).toCompletableFuture(); + } + + @Override + public CompletableFuture thenCombineAsync(CompletionStage other, BiFunction fn) { + return thenCombine(other, fn); + } + + @Override + public CompletableFuture thenCombineAsync(CompletionStage other, BiFunction fn, Executor executor) { + return thenCombine(other, fn); + } + + @Override + public CompletableFuture thenAcceptBoth(CompletionStage other, BiConsumer action) { + preconditionCheck(); + + final CompletableFuture completableFuture = other.toCompletableFuture(); + if (action == null || completableFuture == null) { + throw new NullPointerException(); + } + + U otherResult; + try { + otherResult = completableFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + try { + action.accept(result, otherResult); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture thenAcceptBothAsync(CompletionStage other, BiConsumer action) { + return thenAcceptBoth(other, action); + } + + @Override + public CompletableFuture thenAcceptBothAsync(CompletionStage other, BiConsumer action, Executor executor) { + return thenAcceptBoth(other, action); + } + + @Override + public CompletableFuture runAfterBoth(CompletionStage other, Runnable action) { + preconditionCheck(); + + final CompletableFuture completableFuture = other.toCompletableFuture(); + if (action == null || completableFuture == null) { + throw new NullPointerException(); + } + + try { + action.run(); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture runAfterBothAsync(CompletionStage other, Runnable action) { + return runAfterBoth(other, action); + } + + @Override + public CompletableFuture runAfterBothAsync(CompletionStage other, Runnable action, Executor executor) { + return runAfterBoth(other, action); + } + + @Override + public CompletableFuture applyToEither(CompletionStage other, Function fn) { + preconditionCheck(); + + final CompletableFuture completableFuture = other.toCompletableFuture(); + if (fn == null || completableFuture == null) { + throw new NullPointerException(); + } + + final T eitherResult; + try { + eitherResult = (result != null) ? result : completableFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + final U newResult; + try { + newResult = fn.apply(eitherResult); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture<>(newResult).toCompletableFuture(); + } + + @Override + public CompletableFuture applyToEitherAsync(CompletionStage other, Function fn) { + return applyToEither(other, fn); + } + + @Override + public CompletableFuture applyToEitherAsync(CompletionStage other, Function fn, Executor executor) { + return applyToEither(other, fn); + } + + @Override + public CompletableFuture acceptEither(CompletionStage other, Consumer action) { + preconditionCheck(); + + final CompletableFuture completableFuture = other.toCompletableFuture(); + if (action == null || completableFuture == null) { + throw new NullPointerException(); + } + + final T eitherResult; + try { + eitherResult = (result != null) ? result : completableFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + try { + action.accept(eitherResult); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture acceptEitherAsync(CompletionStage other, Consumer action) { + return acceptEither(other, action); + } + + @Override + public CompletableFuture acceptEitherAsync(CompletionStage other, Consumer action, Executor executor) { + return acceptEither(other, action); + } + + @Override + public CompletableFuture runAfterEither(CompletionStage other, Runnable action) { + preconditionCheck(); + + try { + action.run(); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture runAfterEitherAsync(CompletionStage other, Runnable action) { + return runAfterEither(other, action); + } + + @Override + public CompletableFuture runAfterEitherAsync(CompletionStage other, Runnable action, Executor executor) { + return runAfterEither(other, action); + } + + @Override + public CompletableFuture thenCompose(Function> fn) { + preconditionCheck(); + + return fn.apply(result).toCompletableFuture(); + } + + @Override + public CompletableFuture thenComposeAsync(Function> fn) { + return thenCompose(fn); + } + + @Override + public CompletableFuture thenComposeAsync(Function> fn, Executor executor) { + return thenCompose(fn); + } + + @Override + public CompletableFuture handle(BiFunction fn) { + preconditionCheck(); + + U newResult; + try { + newResult = fn.apply(result, exception); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture<>(newResult).toCompletableFuture(); + } + + @Override + public CompletableFuture handleAsync(BiFunction fn) { + return handle(fn); + } + + @Override + public CompletableFuture handleAsync(BiFunction fn, Executor executor) { + return handle(fn); + } + + @Override + public CompletableFuture whenComplete(BiConsumer action) { + preconditionCheck(); + + final UtCompletableFuture next = new UtCompletableFuture<>(this); + try { + action.accept(next.result, next.exception); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return next.toCompletableFuture(); + } + + @Override + public CompletableFuture whenCompleteAsync(BiConsumer action) { + return whenComplete(action); + } + + @Override + public CompletableFuture whenCompleteAsync(BiConsumer action, Executor executor) { + return whenComplete(action); + } + + @Override + public CompletableFuture exceptionally(Function fn) { + preconditionCheck(); + + if (fn == null) { + throw new NullPointerException(); + } + + if (exception != null) { + try { + final T exceptionalResult = fn.apply(exception); + return new UtCompletableFuture<>(exceptionalResult).toCompletableFuture(); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + } + + return new UtCompletableFuture<>(result).toCompletableFuture(); + } + + @Override + public CompletableFuture toCompletableFuture() { + // Will be processed symbolically + return null; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + preconditionCheck(); + // Tasks could not be canceled since they are supposed to be executed immediately + return false; + } + + @Override + public boolean isCancelled() { + preconditionCheck(); + // Tasks could not be canceled since they are supposed to be executed immediately + return false; + } + + @Override + public boolean isDone() { + preconditionCheck(); + + return true; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + preconditionCheck(); + + if (exception != null) { + throw new ExecutionException(exception); + } + + return result; + } + + @Override + public T get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return get(); + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt index a31c5c8395..78bf8bbfb7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt @@ -21,6 +21,7 @@ import org.utbot.engine.overrides.security.UtSecurityManager import org.utbot.engine.overrides.strings.UtString import org.utbot.engine.overrides.strings.UtStringBuffer import org.utbot.engine.overrides.strings.UtStringBuilder +import org.utbot.engine.overrides.threads.UtCompletableFuture import org.utbot.engine.pc.UtAddrExpression import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel @@ -39,6 +40,8 @@ import java.util.Optional import java.util.OptionalDouble import java.util.OptionalInt import java.util.OptionalLong +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage import java.util.concurrent.CopyOnWriteArrayList import kotlin.reflect.KClass @@ -151,6 +154,10 @@ private val wrappers = mapOf( // threads wrap(Thread::class) { type, addr -> objectValue(type, addr, ThreadWrapper()) }, wrap(ThreadGroup::class) { type, addr -> objectValue(type, addr, ThreadGroupWrapper()) }, + wrap(CompletableFuture::class) { type, addr -> objectValue(type, addr, CompletableFutureWrapper()) }, + wrap(CompletionStage::class) { type, addr -> objectValue(type, addr, CompletableFutureWrapper()) }, + // A hack to be able to create UtCompletableFuture in its methods as a wrapper + wrap(UtCompletableFuture::class) { type, addr -> objectValue(type, addr, CompletableFutureWrapper()) }, wrap(RangeModifiableUnlimitedArray::class) { type, addr -> objectValue(type, addr, RangeModifiableUnlimitedArrayWrapper()) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt index 296569cddd..191acf19a6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt @@ -1,10 +1,15 @@ package org.utbot.engine +import org.utbot.engine.overrides.threads.UtCompletableFuture import org.utbot.engine.overrides.threads.UtThread import org.utbot.engine.overrides.threads.UtThreadGroup +import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.types.COMPLETABLE_FUTURE_TYPE +import org.utbot.engine.types.OBJECT_TYPE import org.utbot.engine.types.STRING_TYPE import org.utbot.engine.types.THREAD_GROUP_TYPE import org.utbot.engine.types.THREAD_TYPE +import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtLambdaModel @@ -12,18 +17,20 @@ import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.constructorId +import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.util.nextModelName import soot.RefType import soot.Scene import soot.SootClass -import soot.SootField import soot.SootMethod val utThreadClass: SootClass get() = Scene.v().getSootClass(UtThread::class.qualifiedName) val utThreadGroupClass: SootClass get() = Scene.v().getSootClass(UtThreadGroup::class.qualifiedName) +val utCompletableFutureClass: SootClass + get() = Scene.v().getSootClass(UtCompletableFuture::class.qualifiedName) class ThreadWrapper : BaseOverriddenWrapper(utThreadClass.name) { override fun Traverser.overrideInvoke( @@ -38,9 +45,8 @@ class ThreadWrapper : BaseOverriddenWrapper(utThreadClass.name) { val addr = holder.concreteAddr(wrapper.addr) val modelName = nextModelName("thread") - val targetField = targetField.fieldId val values = collectFieldModels(wrapper.addr, overriddenClass.type) - val targetModel = values[targetField] as? UtLambdaModel + val targetModel = values[targetFieldId] as? UtLambdaModel val (constructor, params) = if (targetModel == null) { constructorId(classId) to emptyList() @@ -55,16 +61,12 @@ class ThreadWrapper : BaseOverriddenWrapper(utThreadClass.name) { ) return UtAssembleModel(addr, classId, modelName, instantiationCall) - -// val targetFieldChunkId = resolver.hierarchy.chunkIdForField(utThreadClass.type, targetField) -// val targetFieldChunkDescriptor = MemoryChunkDescriptor(targetFieldChunkId, wrapper.type, runnableType) -// val targetAddr = resolver.findArray(targetFieldChunkDescriptor, state).select(wrapper.addr) } companion object { private val runnableType: RefType = Scene.v().getSootClass(Runnable::class.qualifiedName).type!! - private val targetField: SootField - get() = utThreadClass.getField("target", runnableType) + private val targetFieldId: FieldId + get() = utThreadClass.getField("target", runnableType).fieldId } } @@ -81,9 +83,8 @@ class ThreadGroupWrapper : BaseOverriddenWrapper(utThreadGroupClass.name) { val addr = holder.concreteAddr(wrapper.addr) val modelName = nextModelName("threadGroup") - val nameField = nameField.fieldId val values = collectFieldModels(wrapper.addr, overriddenClass.type) - val nameModel = values[nameField] ?: UtNullModel(stringClassId) + val nameModel = values[nameFieldId] ?: UtNullModel(stringClassId) val instantiationCall = UtExecutableCallModel( instance = null, @@ -95,7 +96,67 @@ class ThreadGroupWrapper : BaseOverriddenWrapper(utThreadGroupClass.name) { } companion object { - private val nameField: SootField - get() = utThreadGroupClass.getField("name", STRING_TYPE) + private val nameFieldId: FieldId + get() = utThreadGroupClass.getField("name", STRING_TYPE).fieldId + } +} + +private val TO_COMPLETABLE_FUTURE_SIGNATURE: String + get() = utCompletableFutureClass.getMethodByName(UtCompletableFuture<*>::toCompletableFuture.name).signature +private val UT_COMPLETABLE_FUTURE_EQ_GENERIC_TYPE_SIGNATURE: String + get() = utCompletableFutureClass.getMethodByName(UtCompletableFuture<*>::eqGenericType.name).signature + +class CompletableFutureWrapper : BaseOverriddenWrapper(utCompletableFutureClass.name) { + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = + when (method.signature) { + TO_COMPLETABLE_FUTURE_SIGNATURE -> { + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(method.returnType) + val resultingWrapper = wrapper.copy(typeStorage = typeStorage) + val methodResult = MethodResult(resultingWrapper) + + listOf(methodResult) + } + UT_COMPLETABLE_FUTURE_EQ_GENERIC_TYPE_SIGNATURE -> { + val firstParameter = parameters.single() + val genericTypeParameterTypeConstraint = typeRegistry.typeConstraintToGenericTypeParameter( + firstParameter.addr, + wrapper.addr, + i = 0 + ) + val methodResult = MethodResult( + firstParameter, + genericTypeParameterTypeConstraint.asHardConstraint() + ) + + listOf(methodResult) + } + else -> null + } + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = + resolver.run { + val classId = COMPLETABLE_FUTURE_TYPE.id + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName("completableFuture") + + val values = collectFieldModels(wrapper.addr, overriddenClass.type) + val resultModel = values[resultFieldId] ?: UtNullModel(objectClassId) + + val instantiationCall = UtExecutableCallModel( + instance = null, + constructorId(classId, objectClassId), + listOf(resultModel) + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + private val resultFieldId: FieldId + get() = utCompletableFutureClass.getField("result", OBJECT_TYPE).fieldId } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt index e0d8b11157..af55ffbb2d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt @@ -43,6 +43,7 @@ import soot.SootClass import soot.SootField import soot.Type import soot.VoidType +import java.util.concurrent.CompletableFuture class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy: Hierarchy) { @@ -358,6 +359,8 @@ internal val THREAD_TYPE: RefType get() = Scene.v().getSootClass(Thread::class.java.canonicalName).type internal val THREAD_GROUP_TYPE: RefType get() = Scene.v().getSootClass(ThreadGroup::class.java.canonicalName).type +internal val COMPLETABLE_FUTURE_TYPE: RefType + get() = Scene.v().getSootClass(CompletableFuture::class.java.canonicalName).type internal val NEW_INSTANCE_SIGNATURE: String = CLASS_REF_SOOT_CLASS.getMethodByName("newInstance").subSignature diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt index e04ad78315..38da9b3b93 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt @@ -182,6 +182,8 @@ private val classesToLoad = arrayOf( org.utbot.engine.overrides.strings.UtStringBuffer::class, org.utbot.engine.overrides.threads.UtThread::class, org.utbot.engine.overrides.threads.UtThreadGroup::class, + org.utbot.engine.overrides.threads.UtCompletableFuture::class, + org.utbot.engine.overrides.threads.CompletableFuture::class, org.utbot.engine.overrides.stream.Stream::class, org.utbot.engine.overrides.stream.Arrays::class, org.utbot.engine.overrides.collections.Collection::class, diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java new file mode 100644 index 0000000000..d1ab152275 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java @@ -0,0 +1,14 @@ +package org.utbot.examples.threads; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class FutureExamples { + void throwingRunnableExample() throws ExecutionException, InterruptedException { + final CompletableFuture future = CompletableFuture.runAsync(() -> { + throw new IllegalStateException(); + }); + + future.get(); + } +} From 69fac2e74fb4c09b20bab6ee4507861f0da3fea0 Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Wed, 23 Nov 2022 15:26:10 +0800 Subject: [PATCH 03/12] Added preconditionCheck for wrappers for Thread and ThreadGroup --- .../engine/overrides/threads/UtThread.java | 86 ++++++++++++++++-- .../overrides/threads/UtThreadGroup.java | 90 +++++++++++++++++-- 2 files changed, 162 insertions(+), 14 deletions(-) diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java index 0cca696167..720bdd2365 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java @@ -5,6 +5,10 @@ import java.security.AccessControlContext; import java.util.Map; +import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + @SuppressWarnings("unused") public class UtThread { private String name; @@ -121,6 +125,8 @@ public UtThread(ThreadGroup group, Runnable target, String name, private UtThread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext ignoredAcc, boolean ignoredInheritUtThreadLocals) { + visit(this); + if (name == null) { throw new NullPointerException(); } @@ -135,8 +141,8 @@ private UtThread(ThreadGroup g, Runnable target, String name, this.daemon = UtMock.makeSymbolic(); final Integer priority = UtMock.makeSymbolic(); - UtMock.assume(priority >= MIN_PRIORITY); - UtMock.assume(priority <= MIN_PRIORITY); + assume(priority >= MIN_PRIORITY); + assume(priority <= MIN_PRIORITY); this.priority = priority; setPriority(this.priority); @@ -146,21 +152,45 @@ private UtThread(ThreadGroup g, Runnable target, String name, this.tid = nextThreadID(); } + public void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + + assume(name != null); + + assume(priority >= MIN_PRIORITY); + assume(priority <= MIN_PRIORITY); + + assume(group != null); + assume(contextClassLoader != null); + + assume(threadInitNumber >= 0); + assume(tid >= 0); + assume(threadSeqNumber >= 0); + + visit(this); + } + public void start() { run(); } public void run() { + preconditionCheck(); + if (target != null) { target.run(); } } public final void stop() { + preconditionCheck(); // DO nothing } public void interrupt() { + preconditionCheck(); // Set interrupted status isInterrupted = true; } @@ -170,22 +200,30 @@ public static boolean interrupted() { } public boolean isInterrupted() { + preconditionCheck(); + return isInterrupted; } public final boolean isAlive() { + preconditionCheck(); + return UtMock.makeSymbolic(); } public final void suspend() { + preconditionCheck(); // Do nothing } public final void resume() { + preconditionCheck(); // Do nothing } public final void setPriority(int newPriority) { + preconditionCheck(); + if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } @@ -200,10 +238,14 @@ public final void setPriority(int newPriority) { } public final int getPriority() { + preconditionCheck(); + return priority; } - public final synchronized void setName(String name) { + public final void setName(String name) { + preconditionCheck(); + if (name == null) { throw new NullPointerException(); } @@ -212,24 +254,28 @@ public final synchronized void setName(String name) { } public final String getName() { + preconditionCheck(); + return name; } public final ThreadGroup getThreadGroup() { + preconditionCheck(); + return group; } public static int activeCount() { final Integer result = UtMock.makeSymbolic(); - UtMock.assume(result >= 0); + assume(result >= 0); return result; } public static int enumerate(UtThread[] tarray) { Integer length = UtMock.makeSymbolic(); - UtMock.assume(length >= 0); - UtMock.assume(length <= tarray.length); + assume(length >= 0); + assume(length <= tarray.length); for (int i = 0; i < length; i++) { tarray[i] = UtMock.makeSymbolic(); @@ -239,10 +285,14 @@ public static int enumerate(UtThread[] tarray) { } public int countStackFrames() { + preconditionCheck(); + return UtMock.makeSymbolic(); } public final void join(long millis) throws InterruptedException { + preconditionCheck(); + if (millis < 0) { throw new IllegalArgumentException(); } @@ -251,6 +301,8 @@ public final void join(long millis) throws InterruptedException { } public final void join(long millis, int nanos) throws InterruptedException { + preconditionCheck(); + if (millis < 0) { throw new IllegalArgumentException(); } @@ -263,6 +315,8 @@ public final void join(long millis, int nanos) throws InterruptedException { } public final void join() throws InterruptedException { + preconditionCheck(); + join(0); } @@ -271,14 +325,20 @@ public static void dumpStack() { } public final void setDaemon(boolean on) { + preconditionCheck(); + daemon = on; } public final boolean isDaemon() { + preconditionCheck(); + return daemon; } public String toString() { + preconditionCheck(); + if (group != null) { return "Thread[" + getName() + "," + getPriority() + "," + group.getName() + "]"; @@ -289,10 +349,14 @@ public String toString() { } public ClassLoader getContextClassLoader() { + preconditionCheck(); + return contextClassLoader; } public void setContextClassLoader(ClassLoader cl) { + preconditionCheck(); + contextClassLoader = cl; } @@ -305,6 +369,8 @@ public static boolean holdsLock(Object obj) { } public StackTraceElement[] getStackTrace() { + preconditionCheck(); + return UtMock.makeSymbolic(); } @@ -313,10 +379,14 @@ public static Map getAllStackTraces() { } public long getId() { + preconditionCheck(); + return tid; } public Thread.State getState() { + preconditionCheck(); + return UtMock.makeSymbolic(); } @@ -329,10 +399,14 @@ public static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler } public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() { + preconditionCheck(); + return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; } public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) { + preconditionCheck(); + uncaughtExceptionHandler = eh; } } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java index 22a0e7c0d1..54452eb876 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java @@ -4,6 +4,10 @@ import java.util.Arrays; +import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + @SuppressWarnings("unused") public class UtThreadGroup implements Thread.UncaughtExceptionHandler { private final ThreadGroup parent; @@ -24,37 +28,75 @@ public UtThreadGroup(String name) { } public UtThreadGroup(ThreadGroup parent, String name) { + visit(this); + this.name = name; - this.maxPriority = UtMock.makeSymbolic(); + + final Integer maxPriority = UtMock.makeSymbolic(); + assume(maxPriority >= UtThread.MIN_PRIORITY); + assume(maxPriority <= UtThread.MAX_PRIORITY); + this.maxPriority = maxPriority; + this.daemon = UtMock.makeSymbolic(); this.parent = parent; } + public void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + + assume(parent != null); + assume(name != null); + + assume(maxPriority >= UtThread.MIN_PRIORITY); + assume(maxPriority <= UtThread.MAX_PRIORITY); + + assume(nUnstartedThreads >= 0); + assume(ngroups >= 0); + + visit(this); + } + public final String getName() { + preconditionCheck(); + return name; } public final ThreadGroup getParent() { + preconditionCheck(); + return parent; } public final int getMaxPriority() { + preconditionCheck(); + return maxPriority; } public final boolean isDaemon() { + preconditionCheck(); + return daemon; } - public synchronized boolean isDestroyed() { + public boolean isDestroyed() { + preconditionCheck(); + return destroyed; } public final void setDaemon(boolean daemon) { + preconditionCheck(); + this.daemon = daemon; } public final void setMaxPriority(int pri) { + preconditionCheck(); + if (pri < UtThread.MIN_PRIORITY || pri > UtThread.MAX_PRIORITY) { return; } @@ -65,20 +107,25 @@ public final void setMaxPriority(int pri) { } public final boolean parentOf(ThreadGroup g) { + preconditionCheck(); + return UtMock.makeSymbolic(); } public final void checkAccess() { + preconditionCheck(); // Do nothing } public int activeCount() { + preconditionCheck(); + if (destroyed) { return 0; } final Integer result = UtMock.makeSymbolic(); - UtMock.assume(result >= 0); + assume(result >= 0); return result; } @@ -92,6 +139,8 @@ public int enumerate(Thread[] list, boolean recurse) { @SuppressWarnings({"SameParameterValue", "ParameterCanBeLocal"}) private int enumerate(Thread[] list, int ignoredN, boolean ignoredRecurse) { + preconditionCheck(); + if (destroyed) { return 0; } @@ -99,19 +148,21 @@ private int enumerate(Thread[] list, int ignoredN, boolean ignoredRecurse) { list = UtMock.makeSymbolic(); final Integer result = UtMock.makeSymbolic(); - UtMock.assume(result <= list.length); - UtMock.assume(result >= 0); + assume(result <= list.length); + assume(result >= 0); return result; } public int activeGroupCount() { + preconditionCheck(); + if (destroyed) { return 0; } final Integer result = UtMock.makeSymbolic(); - UtMock.assume(result >= 0); + assume(result >= 0); return result; } @@ -125,6 +176,8 @@ public int enumerate(ThreadGroup[] list, boolean recurse) { @SuppressWarnings({"SameParameterValue", "ParameterCanBeLocal"}) private int enumerate(ThreadGroup[] list, int ignoredN, boolean ignoredRecurse) { + preconditionCheck(); + if (destroyed) { return 0; } @@ -132,17 +185,20 @@ private int enumerate(ThreadGroup[] list, int ignoredN, boolean ignoredRecurse) list = UtMock.makeSymbolic(); final Integer result = UtMock.makeSymbolic(); - UtMock.assume(result <= list.length); - UtMock.assume(result >= 0); + assume(result <= list.length); + assume(result >= 0); return result; } public final void stop() { + preconditionCheck(); // Do nothing } public final void interrupt() { + preconditionCheck(); + for (int i = 0 ; i < nthreads ; i++) { threads[i].interrupt(); } @@ -153,14 +209,18 @@ public final void interrupt() { } public final void suspend() { + preconditionCheck(); // Do nothing } public final void resume() { + preconditionCheck(); // Do nothing } public final void destroy() { + preconditionCheck(); + if (destroyed || nthreads > 0) { throw new IllegalThreadStateException(); } @@ -178,6 +238,8 @@ public final void destroy() { } private void add(ThreadGroup g){ + preconditionCheck(); + if (destroyed) { throw new IllegalThreadStateException(); } @@ -193,6 +255,8 @@ private void add(ThreadGroup g){ } private void remove(ThreadGroup g) { + preconditionCheck(); + if (destroyed) { return; } @@ -212,6 +276,8 @@ private void remove(ThreadGroup g) { } void addUnstarted() { + preconditionCheck(); + if (destroyed) { throw new IllegalThreadStateException(); } @@ -220,6 +286,8 @@ void addUnstarted() { } void add(Thread t) { + preconditionCheck(); + if (destroyed) { throw new IllegalThreadStateException(); } @@ -236,18 +304,24 @@ void add(Thread t) { } public void list() { + preconditionCheck(); // Do nothing } public void uncaughtException(Thread t, Throwable e) { + preconditionCheck(); // Do nothing } public boolean allowThreadSuspension(boolean b) { + preconditionCheck(); + return true; } public String toString() { + preconditionCheck(); + return getClass().getName() + "[name=" + getName() + ",maxpri=" + maxPriority + "]"; } } From a2c6751df8d149551850edf23095216610f6553c Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Wed, 23 Nov 2022 17:37:50 +0800 Subject: [PATCH 04/12] Added a wrapper for ExecutorService --- .../engine/overrides/threads/Executors.java | 120 +++++++++++ .../overrides/threads/ThreadFactory.java | 12 ++ .../threads/UtCompletableFuture.java | 20 +- .../overrides/threads/UtExecutorService.java | 202 ++++++++++++++++++ .../kotlin/org/utbot/engine/ObjectWrappers.kt | 8 + .../kotlin/org/utbot/engine/ThreadWrappers.kt | 34 +++ .../org/utbot/engine/types/TypeResolver.kt | 3 + .../org/utbot/framework/util/SootUtils.kt | 2 + 8 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/threads/Executors.java create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/threads/ThreadFactory.java create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/Executors.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/Executors.java new file mode 100644 index 0000000000..f04c0661e5 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/Executors.java @@ -0,0 +1,120 @@ +package org.utbot.engine.overrides.threads; + +import org.utbot.api.annotation.UtClassMock; +import org.utbot.api.mock.UtMock; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; + +@UtClassMock(target = java.util.concurrent.Executors.class, internalUsage = true) +public class Executors { + public static ExecutorService newFixedThreadPool(int nThreads) { + if (nThreads <= 0) { + throw new IllegalArgumentException(); + } + + return new UtExecutorService(); + } + + public static ExecutorService newWorkStealingPool() { + return new UtExecutorService(); + } + + public static ExecutorService newWorkStealingPool(int parallelism) { + if (parallelism <= 0) { + throw new IllegalArgumentException(); + } + + return new UtExecutorService(); + } + + public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { + if (threadFactory == null) { + throw new NullPointerException(); + } + + return newFixedThreadPool(nThreads); + } + + public static ExecutorService newSingleThreadExecutor() { + return new UtExecutorService(); + } + + public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { + if (threadFactory == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ExecutorService newCachedThreadPool() { + return new UtExecutorService(); + } + + public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { + if (threadFactory == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ScheduledExecutorService newSingleThreadScheduledExecutor() { + return new UtExecutorService(); + } + + public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { + if (threadFactory == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { + if (corePoolSize < 0) { + throw new IllegalArgumentException(); + } + + return new UtExecutorService(); + } + + public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) { + if (corePoolSize < 0) { + throw new IllegalArgumentException(); + } + + if (threadFactory == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ExecutorService unconfigurableExecutorService(ExecutorService executor) { + if (executor == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor) { + if (executor == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ThreadFactory defaultThreadFactory() { + // TODO make a wrapper? + return UtMock.makeSymbolic(); + } + + public static ThreadFactory privilegedThreadFactory() { + return defaultThreadFactory(); + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/ThreadFactory.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/ThreadFactory.java new file mode 100644 index 0000000000..f143c7be49 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/ThreadFactory.java @@ -0,0 +1,12 @@ +package org.utbot.engine.overrides.threads; + +import org.jetbrains.annotations.NotNull; +import org.utbot.api.annotation.UtClassMock; + +@UtClassMock(target = ThreadFactory.class, internalUsage = true) +public class ThreadFactory implements java.util.concurrent.ThreadFactory { + @Override + public Thread newThread(@NotNull Runnable r) { + return new Thread(r); + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java index af4a478048..119e22e9fe 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java @@ -4,9 +4,11 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; @@ -14,10 +16,11 @@ import java.util.function.Consumer; import java.util.function.Function; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; import static org.utbot.engine.overrides.UtOverrideMock.visit; -public class UtCompletableFuture implements Future, CompletionStage { +public class UtCompletableFuture implements Future, ScheduledFuture, CompletionStage { T result; Throwable exception; @@ -446,4 +449,19 @@ public T get() throws InterruptedException, ExecutionException { public T get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return get(); } + + @Override + public long getDelay(@NotNull TimeUnit unit) { + return 0; + } + + @Override + public int compareTo(@NotNull Delayed o) { + if (o == this) { // compare zero if same object{ + return 0; + } + + long diff = getDelay(NANOSECONDS) - o.getDelay(NANOSECONDS); + return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; + } } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java new file mode 100644 index 0000000000..d9a64f390f --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java @@ -0,0 +1,202 @@ +package org.utbot.engine.overrides.threads; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + +public class UtExecutorService implements ExecutorService, ScheduledExecutorService { + private boolean isShutdown; + private boolean isTerminated; + + public UtExecutorService() { + visit(this); + } + + private void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + + visit(this); + } + + @Override + public void shutdown() { + preconditionCheck(); + + isShutdown = true; + isTerminated = true; + } + + @NotNull + @Override + public List shutdownNow() { + preconditionCheck(); + + shutdown(); + // Since all tasks are processed immediately, there are no waiting tasks + return new ArrayList<>(); + } + + @Override + public boolean isShutdown() { + preconditionCheck(); + + return isShutdown; + } + + @Override + public boolean isTerminated() { + preconditionCheck(); + + return isTerminated; + } + + @Override + public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) { + // No need to wait tasks + return true; + } + + @NotNull + @Override + public Future submit(@NotNull Callable task) { + preconditionCheck(); + + try { + T result = task.call(); + return new UtCompletableFuture<>(result); + } catch (Exception e) { + return new UtCompletableFuture<>(e); + } + } + + @NotNull + @Override + public Future submit(@NotNull Runnable task, T result) { + preconditionCheck(); + + try { + task.run(); + return new UtCompletableFuture<>(result); + } catch (Exception e) { + return new UtCompletableFuture<>(e); + } + } + + @NotNull + @Override + public Future submit(@NotNull Runnable task) { + return submit(task, null); + } + + @NotNull + @Override + public List> invokeAll(@NotNull Collection> tasks) { + preconditionCheck(); + + List> results = new ArrayList<>(); + for (Callable task : tasks) { + results.add(submit(task)); + } + + return results; + } + + @NotNull + @Override + public List> invokeAll(@NotNull Collection> tasks, long timeout, @NotNull TimeUnit unit) { + return invokeAll(tasks); + } + + @NotNull + @Override + public T invokeAny(@NotNull Collection> tasks) throws ExecutionException { + preconditionCheck(); + + for (Callable task : tasks) { + try { + return task.call(); + } catch (Exception e) { + // Do nothing + } + } + + // ExecutionException no-parameters constructor is protected + throw new ExecutionException(new RuntimeException()); + } + + @Override + public T invokeAny(@NotNull Collection> tasks, long timeout, @NotNull TimeUnit unit) throws ExecutionException { + return invokeAny(tasks); + } + + @Override + public void execute(@NotNull Runnable command) { + preconditionCheck(); + + command.run(); + } + + @NotNull + @Override + public ScheduledFuture schedule(@NotNull Runnable command, long delay, @NotNull TimeUnit unit) { + preconditionCheck(); + + try { + command.run(); + return new UtCompletableFuture<>(); + } catch (Exception e) { + return new UtCompletableFuture<>(e); + } + } + + @NotNull + @Override + public ScheduledFuture schedule(@NotNull Callable callable, long delay, @NotNull TimeUnit unit) { + preconditionCheck(); + + try { + V result = callable.call(); + return new UtCompletableFuture<>(result); + } catch (Exception e) { + return new UtCompletableFuture<>(e); + } + } + + @NotNull + @Override + public ScheduledFuture scheduleAtFixedRate(@NotNull Runnable command, long initialDelay, long period, @NotNull TimeUnit unit) { + preconditionCheck(); + + if (period <= 0) { + throw new IllegalArgumentException(); + } + + return schedule(command, initialDelay, unit); + } + + @NotNull + @Override + public ScheduledFuture scheduleWithFixedDelay(@NotNull Runnable command, long initialDelay, long delay, @NotNull TimeUnit unit) { + preconditionCheck(); + + if (delay <= 0) { + throw new IllegalArgumentException(); + } + + return schedule(command, initialDelay, unit); + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt index 78bf8bbfb7..90b527923a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt @@ -43,6 +43,10 @@ import java.util.OptionalLong import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletionStage import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.ExecutorService +import java.util.concurrent.ForkJoinPool +import java.util.concurrent.ScheduledThreadPoolExecutor +import java.util.concurrent.ThreadPoolExecutor import kotlin.reflect.KClass typealias TypeToBeWrapped = RefType @@ -154,6 +158,10 @@ private val wrappers = mapOf( // threads wrap(Thread::class) { type, addr -> objectValue(type, addr, ThreadWrapper()) }, wrap(ThreadGroup::class) { type, addr -> objectValue(type, addr, ThreadGroupWrapper()) }, + wrap(ExecutorService::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, + wrap(ThreadPoolExecutor::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, + wrap(ForkJoinPool::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, + wrap(ScheduledThreadPoolExecutor::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, wrap(CompletableFuture::class) { type, addr -> objectValue(type, addr, CompletableFutureWrapper()) }, wrap(CompletionStage::class) { type, addr -> objectValue(type, addr, CompletableFutureWrapper()) }, // A hack to be able to create UtCompletableFuture in its methods as a wrapper diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt index 191acf19a6..8b1ed60989 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt @@ -1,14 +1,17 @@ package org.utbot.engine import org.utbot.engine.overrides.threads.UtCompletableFuture +import org.utbot.engine.overrides.threads.UtExecutorService import org.utbot.engine.overrides.threads.UtThread import org.utbot.engine.overrides.threads.UtThreadGroup import org.utbot.engine.symbolic.asHardConstraint import org.utbot.engine.types.COMPLETABLE_FUTURE_TYPE +import org.utbot.engine.types.EXECUTORS_TYPE import org.utbot.engine.types.OBJECT_TYPE import org.utbot.engine.types.STRING_TYPE import org.utbot.engine.types.THREAD_GROUP_TYPE import org.utbot.engine.types.THREAD_TYPE +import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel @@ -19,6 +22,7 @@ import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.constructorId import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.util.executableId import org.utbot.framework.util.nextModelName import soot.RefType import soot.Scene @@ -31,6 +35,8 @@ val utThreadGroupClass: SootClass get() = Scene.v().getSootClass(UtThreadGroup::class.qualifiedName) val utCompletableFutureClass: SootClass get() = Scene.v().getSootClass(UtCompletableFuture::class.qualifiedName) +val utExecutorServiceClass: SootClass + get() = Scene.v().getSootClass(UtExecutorService::class.qualifiedName) class ThreadWrapper : BaseOverriddenWrapper(utThreadClass.name) { override fun Traverser.overrideInvoke( @@ -160,3 +166,31 @@ class CompletableFutureWrapper : BaseOverriddenWrapper(utCompletableFutureClass. get() = utCompletableFutureClass.getField("result", OBJECT_TYPE).fieldId } } + +class ExecutorServiceWrapper : BaseOverriddenWrapper(utExecutorServiceClass.name) { + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = null + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = + resolver.run { + val classId = EXECUTORS_TYPE.id + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName("executorService") + + val instantiationCall = UtExecutableCallModel( + instance = null, + newSingleThreadExecutorMethod, + emptyList() + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + val newSingleThreadExecutorMethod: ExecutableId + get() = EXECUTORS_TYPE.sootClass.getMethodByName("newSingleThreadExecutor").executableId + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt index af55ffbb2d..3ce8ea61cc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt @@ -44,6 +44,7 @@ import soot.SootField import soot.Type import soot.VoidType import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executors class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy: Hierarchy) { @@ -361,6 +362,8 @@ internal val THREAD_GROUP_TYPE: RefType get() = Scene.v().getSootClass(ThreadGroup::class.java.canonicalName).type internal val COMPLETABLE_FUTURE_TYPE: RefType get() = Scene.v().getSootClass(CompletableFuture::class.java.canonicalName).type +internal val EXECUTORS_TYPE: RefType + get() = Scene.v().getSootClass(Executors::class.java.canonicalName).type internal val NEW_INSTANCE_SIGNATURE: String = CLASS_REF_SOOT_CLASS.getMethodByName("newInstance").subSignature diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt index 38da9b3b93..40c846711f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt @@ -184,6 +184,8 @@ private val classesToLoad = arrayOf( org.utbot.engine.overrides.threads.UtThreadGroup::class, org.utbot.engine.overrides.threads.UtCompletableFuture::class, org.utbot.engine.overrides.threads.CompletableFuture::class, + org.utbot.engine.overrides.threads.Executors::class, + org.utbot.engine.overrides.threads.UtExecutorService::class, org.utbot.engine.overrides.stream.Stream::class, org.utbot.engine.overrides.stream.Arrays::class, org.utbot.engine.overrides.collections.Collection::class, From 190656c8bbf5a5306afd09b1abe62cee0d740d32 Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Wed, 23 Nov 2022 19:09:00 +0800 Subject: [PATCH 05/12] Fixed wrappers for Thread and ExecutorService --- .../engine/overrides/security/UtSecurityManager.java | 6 ++++++ .../org/utbot/engine/overrides/threads/UtThread.java | 12 ++++++++++++ .../main/kotlin/org/utbot/engine/ThreadWrappers.kt | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/security/UtSecurityManager.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/security/UtSecurityManager.java index 67c635945a..c703789a7c 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/security/UtSecurityManager.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/security/UtSecurityManager.java @@ -1,5 +1,7 @@ package org.utbot.engine.overrides.security; +import org.utbot.api.mock.UtMock; + import java.security.Permission; /** @@ -13,4 +15,8 @@ public void checkPermission(Permission perm) { public void checkPackageAccess(String pkg) { // Do nothing to allow everything } + + public ThreadGroup getThreadGroup() { + return new ThreadGroup(UtMock.makeSymbolic()); + } } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java index 720bdd2365..0f85b93606 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java @@ -205,6 +205,18 @@ public boolean isInterrupted() { return isInterrupted; } + private boolean isInterrupted(boolean clearInterrupted) { + preconditionCheck(); + + boolean result = isInterrupted; + + if (clearInterrupted) { + isInterrupted = false; + } + + return result; + } + public final boolean isAlive() { preconditionCheck(); diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt index 8b1ed60989..40bab1de59 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt @@ -191,6 +191,6 @@ class ExecutorServiceWrapper : BaseOverriddenWrapper(utExecutorServiceClass.name companion object { val newSingleThreadExecutorMethod: ExecutableId - get() = EXECUTORS_TYPE.sootClass.getMethodByName("newSingleThreadExecutor").executableId + get() = EXECUTORS_TYPE.sootClass.getMethod("newSingleThreadExecutor", emptyList()).executableId } } From f99b5210ee762cb49e740fdf84c24e3dfc623a77 Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Thu, 24 Nov 2022 11:09:02 +0800 Subject: [PATCH 06/12] Added a wrapper for CountDownLatch --- .../overrides/threads/UtCountDownLatch.java | 63 +++++++++++++++++++ .../kotlin/org/utbot/engine/ObjectWrappers.kt | 2 + .../kotlin/org/utbot/engine/ThreadWrappers.kt | 38 +++++++++++ .../org/utbot/engine/types/TypeResolver.kt | 3 + .../org/utbot/framework/util/SootUtils.kt | 1 + 5 files changed, 107 insertions(+) create mode 100644 utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCountDownLatch.java diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCountDownLatch.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCountDownLatch.java new file mode 100644 index 0000000000..f46bea70f0 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCountDownLatch.java @@ -0,0 +1,63 @@ +package org.utbot.engine.overrides.threads; + +import java.util.concurrent.TimeUnit; + +import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + +public class UtCountDownLatch { + private int count; + + public UtCountDownLatch(int count) { + if (count < 0) { + throw new IllegalArgumentException(); + } + + visit(this); + this.count = count; + } + + void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + + assume(count >= 0); + + visit(this); + } + + public void await() { + preconditionCheck(); + // Do nothing + } + + public boolean await(long ignoredTimeout, TimeUnit ignoredUnit) { + preconditionCheck(); + + return count == 0; + } + + public void countDown() { + preconditionCheck(); + + if (count != 0) { + count--; + } + } + + public long getCount() { + preconditionCheck(); + + return count; + } + + @Override + public String toString() { + preconditionCheck(); + // Actually, the real string representation also contains some meta-information about this class, + // but it looks redundant for this wrapper + return String.valueOf(count); + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt index 90b527923a..6c27c40f55 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt @@ -43,6 +43,7 @@ import java.util.OptionalLong import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletionStage import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.CountDownLatch import java.util.concurrent.ExecutorService import java.util.concurrent.ForkJoinPool import java.util.concurrent.ScheduledThreadPoolExecutor @@ -162,6 +163,7 @@ private val wrappers = mapOf( wrap(ThreadPoolExecutor::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, wrap(ForkJoinPool::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, wrap(ScheduledThreadPoolExecutor::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, + wrap(CountDownLatch::class) { type, addr -> objectValue(type, addr, CountDownLatchWrapper()) }, wrap(CompletableFuture::class) { type, addr -> objectValue(type, addr, CompletableFutureWrapper()) }, wrap(CompletionStage::class) { type, addr -> objectValue(type, addr, CompletableFutureWrapper()) }, // A hack to be able to create UtCompletableFuture in its methods as a wrapper diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt index 40bab1de59..c36e0521cc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt @@ -1,11 +1,13 @@ package org.utbot.engine import org.utbot.engine.overrides.threads.UtCompletableFuture +import org.utbot.engine.overrides.threads.UtCountDownLatch import org.utbot.engine.overrides.threads.UtExecutorService import org.utbot.engine.overrides.threads.UtThread import org.utbot.engine.overrides.threads.UtThreadGroup import org.utbot.engine.symbolic.asHardConstraint import org.utbot.engine.types.COMPLETABLE_FUTURE_TYPE +import org.utbot.engine.types.COUNT_DOWN_LATCH_TYPE import org.utbot.engine.types.EXECUTORS_TYPE import org.utbot.engine.types.OBJECT_TYPE import org.utbot.engine.types.STRING_TYPE @@ -20,10 +22,13 @@ import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.constructorId +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.util.executableId import org.utbot.framework.util.nextModelName +import soot.IntType import soot.RefType import soot.Scene import soot.SootClass @@ -37,6 +42,8 @@ val utCompletableFutureClass: SootClass get() = Scene.v().getSootClass(UtCompletableFuture::class.qualifiedName) val utExecutorServiceClass: SootClass get() = Scene.v().getSootClass(UtExecutorService::class.qualifiedName) +val utCountDownLatchClass: SootClass + get() = Scene.v().getSootClass(UtCountDownLatch::class.qualifiedName) class ThreadWrapper : BaseOverriddenWrapper(utThreadClass.name) { override fun Traverser.overrideInvoke( @@ -194,3 +201,34 @@ class ExecutorServiceWrapper : BaseOverriddenWrapper(utExecutorServiceClass.name get() = EXECUTORS_TYPE.sootClass.getMethod("newSingleThreadExecutor", emptyList()).executableId } } + +class CountDownLatchWrapper : BaseOverriddenWrapper(utCountDownLatchClass.name) { + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = null + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = + resolver.run { + val classId = COUNT_DOWN_LATCH_TYPE.id + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName("countDownLatch") + + val values = collectFieldModels(wrapper.addr, overriddenClass.type) + val countModel = values[countFieldId] ?: intClassId.defaultValueModel() + + val instantiationCall = UtExecutableCallModel( + instance = null, + constructorId(classId, intClassId), + listOf(countModel) + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + private val countFieldId: FieldId + get() = utCountDownLatchClass.getField("count", IntType.v()).fieldId + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt index 3ce8ea61cc..c27b4f968e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt @@ -44,6 +44,7 @@ import soot.SootField import soot.Type import soot.VoidType import java.util.concurrent.CompletableFuture +import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy: Hierarchy) { @@ -364,6 +365,8 @@ internal val COMPLETABLE_FUTURE_TYPE: RefType get() = Scene.v().getSootClass(CompletableFuture::class.java.canonicalName).type internal val EXECUTORS_TYPE: RefType get() = Scene.v().getSootClass(Executors::class.java.canonicalName).type +internal val COUNT_DOWN_LATCH_TYPE: RefType + get() = Scene.v().getSootClass(CountDownLatch::class.java.canonicalName).type internal val NEW_INSTANCE_SIGNATURE: String = CLASS_REF_SOOT_CLASS.getMethodByName("newInstance").subSignature diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt index 40c846711f..0779f6de85 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt @@ -186,6 +186,7 @@ private val classesToLoad = arrayOf( org.utbot.engine.overrides.threads.CompletableFuture::class, org.utbot.engine.overrides.threads.Executors::class, org.utbot.engine.overrides.threads.UtExecutorService::class, + org.utbot.engine.overrides.threads.UtCountDownLatch::class, org.utbot.engine.overrides.stream.Stream::class, org.utbot.engine.overrides.stream.Arrays::class, org.utbot.engine.overrides.collections.Collection::class, From afac5490b3abfc1cb16d78ebff1d27f38989bcd2 Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Sat, 26 Nov 2022 12:23:15 +0800 Subject: [PATCH 07/12] Fixed hierarchy incompability with UtThread and added missed fields --- .../org/utbot/engine/overrides/threads/UtThread.java | 10 ++++++++++ .../src/main/kotlin/org/utbot/engine/Hierarchy.kt | 12 ++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java index 0f85b93606..8a8b3560c6 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java @@ -44,6 +44,12 @@ private static long nextThreadID() { private boolean isInterrupted; + // This field is required by ThreadLocal class. The real type is a package-private type ThreadLocal.ThreadLocalMap + Object threadLocals; + + // This field is required by InheritableThreadLocal class. The real type is a package-private type ThreadLocal.ThreadLocalMap + Object inheritableThreadLocals; + /** * The minimum priority that a thread can have. */ @@ -150,6 +156,10 @@ private UtThread(ThreadGroup g, Runnable target, String name, this.target = target; this.tid = nextThreadID(); + + // We need to make it possible to cast these fields to the ThreadLocal.ThreadLocalMap type + UtMock.disableClassCastExceptionCheck(threadLocals); + UtMock.disableClassCastExceptionCheck(inheritableThreadLocals); } public void preconditionCheck() { diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt index bfb471e400..e3c09f4b56 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt @@ -1,6 +1,7 @@ package org.utbot.engine -import org.utbot.engine.types.OBJECT_TYPE +import org.utbot.common.WorkaroundReason +import org.utbot.common.workaround import org.utbot.engine.types.TypeRegistry import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.id @@ -32,7 +33,14 @@ class Hierarchy(private val typeRegistry: TypeRegistry) { val realType = typeRegistry.findRealType(type) as RefType val realFieldDeclaringType = typeRegistry.findRealType(field.declaringClass.type) as RefType - if (realFieldDeclaringType.sootClass !in ancestors(realType.sootClass.id)) { + // java.lang.Thread class has package-private fields, that can be used outside the class. + // Since wrapper UtThread does not inherit java.lang.Thread, we cannot use this inheritance condition only + val realTypeHasFieldByName = workaround(WorkaroundReason.TAINT){ + realType.sootClass.getFieldByNameUnsafe(field.name) != null + } + val realTypeIsInheritor = realFieldDeclaringType.sootClass in ancestors(realType.sootClass.id) + + if (!realTypeIsInheritor && !realTypeHasFieldByName) { error("No such field ${field.subSignature} found in ${realType.sootClass.name}") } return ChunkId("$realFieldDeclaringType", field.name) From d48e1134a30f18a856ccb8113c3c54175bc8950f Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Fri, 16 Dec 2022 15:58:37 +0800 Subject: [PATCH 08/12] Added tests for other wrappers --- .../threads/CountDownLatchExamplesTest.kt | 23 ++++++++ .../threads/ExecutorServiceExamplesTest.kt | 39 ++++++++++++++ .../examples/threads/FutureExamplesTest.kt | 53 ++++++++++++++++--- .../examples/threads/ThreadExamplesTest.kt | 53 +++++++++++++++++++ .../threads/ThreadStartExampleTest.kt | 25 --------- .../main/kotlin/org/utbot/engine/Hierarchy.kt | 7 +-- .../threads/CountDownLatchExamples.java | 38 +++++++++++++ .../threads/ExecutorServiceExamples.java | 23 ++++++++ .../examples/threads/FutureExamples.java | 29 +++++++++- .../examples/threads/ThreadExamples.java | 29 ++++++++++ .../examples/threads/ThreadStartExample.java | 9 ---- 11 files changed, 281 insertions(+), 47 deletions(-) create mode 100644 utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/CountDownLatchExamplesTest.kt create mode 100644 utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt create mode 100644 utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt delete mode 100644 utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadStartExampleTest.kt create mode 100644 utbot-sample/src/main/java/org/utbot/examples/threads/CountDownLatchExamples.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/threads/ExecutorServiceExamples.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/threads/ThreadExamples.java delete mode 100644 utbot-sample/src/main/java/org/utbot/examples/threads/ThreadStartExample.java diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/CountDownLatchExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/CountDownLatchExamplesTest.kt new file mode 100644 index 0000000000..125251f73f --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/CountDownLatchExamplesTest.kt @@ -0,0 +1,23 @@ +package org.utbot.examples.threads + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.AtLeast +import org.utbot.testing.UtValueTestCaseChecker + +class CountDownLatchExamplesTest : UtValueTestCaseChecker(testClass = CountDownLatchExamples::class) { + @Test + fun testGetAndDown() { + check( + CountDownLatchExamples::getAndDown, + eq(2), + { countDownLatch, l -> countDownLatch.count == 0L && l == 0L }, + { countDownLatch, l -> + val firstCount = countDownLatch.count + + firstCount != 0L && l == firstCount - 1 + }, + coverage = AtLeast(83) + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt new file mode 100644 index 0000000000..36dec1e037 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt @@ -0,0 +1,39 @@ +package org.utbot.examples.threads + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.AtLeast +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +// IMPORTANT: most of the these tests test only the symbolic engine +// and should not be used for testing conrete or code generation since they are possibly flaky in the real execution +class ExecutorServiceExamplesTest : UtValueTestCaseChecker(testClass = ExecutorServiceExamples::class) { + @Test + fun testExceptionInExecute() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + checkWithException( + ExecutorServiceExamples::throwingInExecute, + ignoreExecutionsNumber, + { r -> r.isException() } + ) + } + } + } + + @Test + fun testChangingCollectionInExecute() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + check( + ExecutorServiceExamples::changingCollectionInExecute, + ignoreExecutionsNumber, + { r -> r == 42 }, + coverage = AtLeast(78) + ) + } + } + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt index d3fcaca04e..37056224a9 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt @@ -2,19 +2,58 @@ package org.utbot.examples.threads import org.junit.jupiter.api.Test import org.utbot.testcheckers.eq -import org.utbot.tests.infrastructure.AtLeast -import org.utbot.tests.infrastructure.UtValueTestCaseChecker -import org.utbot.tests.infrastructure.isException +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.AtLeast +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException import java.util.concurrent.ExecutionException +// IMPORTANT: most of the these tests test only the symbolic engine +// and should not be used for testing conrete or code generation since they are possibly flaky in the real execution class FutureExamplesTest : UtValueTestCaseChecker(testClass = FutureExamples::class) { @Test fun testThrowingRunnable() { - checkWithException( - FutureExamples::throwingRunnableExample, + withoutConcrete { + checkWithException( + FutureExamples::throwingRunnableExample, + eq(1), + { r -> r.isException() }, + coverage = AtLeast(71) + ) + } + } + + @Test + fun testResultFromGet() { + check( + FutureExamples::resultFromGet, eq(1), - { r -> r.isException() }, - coverage = AtLeast(71) + { r -> r == 42 }, ) } + + @Test + fun testChangingCollectionInFuture() { + withEnabledTestingCodeGeneration(false) { + check( + FutureExamples::changingCollectionInFuture, + eq(1), + { r -> r == 42 }, + ) + } + } + + @Test + fun testChangingCollectionInFutureWithoutGet() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + check( + FutureExamples::changingCollectionInFutureWithoutGet, + eq(1), + { r -> r == 42 }, + coverage = AtLeast(78) + ) + } + } + } } diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt new file mode 100644 index 0000000000..454e5128dc --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt @@ -0,0 +1,53 @@ +package org.utbot.examples.threads + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.AtLeast +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +// IMPORTANT: most of the these tests test only the symbolic engine +// and should not be used for testing conrete or code generation since they are possibly flaky in the real execution +class ThreadExamplesTest : UtValueTestCaseChecker(testClass = ThreadExamples::class) { + @Test + fun testExceptionInStart() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + checkWithException( + ThreadExamples::explicitExceptionInStart, + ignoreExecutionsNumber, + { r -> r.isException() } + ) + } + } + } + + @Test + fun testChangingCollectionInThread() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + check( + ThreadExamples::changingCollectionInThread, + ignoreExecutionsNumber, + { r -> r == 42 }, + coverage = AtLeast(81) + ) + } + } + } + + @Test + fun testChangingCollectionInThreadWithoutStart() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + checkWithException( + ThreadExamples::changingCollectionInThreadWithoutStart, + ignoreExecutionsNumber, + { r -> r.isException() }, + coverage = AtLeast(81) + ) + } + } + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadStartExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadStartExampleTest.kt deleted file mode 100644 index 5734e05bc2..0000000000 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadStartExampleTest.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.utbot.examples.threads - -import org.junit.jupiter.api.Test -import org.utbot.testcheckers.withoutConcrete -import org.utbot.tests.infrastructure.UtValueTestCaseChecker -import org.utbot.tests.infrastructure.ignoreExecutionsNumber -import org.utbot.tests.infrastructure.isException - -class ThreadStartExampleTest : UtValueTestCaseChecker(testClass = ThreadStartExample::class) { - @Test - // TODO minimization does not work - fun testExceptionInStart() { - // TODO concrete execution does not discover an exception - looks like it cannot find an exception in another thread - withoutConcrete { - // TODO an exception in another thread is not captured by assertThrows, we should find another way to support exceptions in different threads - withEnabledTestingCodeGeneration(false) { - checkWithException( - ThreadStartExample::explicitExceptionInStart, - ignoreExecutionsNumber, - { r -> r.isException() } - ) - } - } - } -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt index e3c09f4b56..be167ac2ba 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt @@ -1,7 +1,6 @@ package org.utbot.engine -import org.utbot.common.WorkaroundReason -import org.utbot.common.workaround +import org.utbot.engine.types.OBJECT_TYPE import org.utbot.engine.types.TypeRegistry import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.id @@ -35,9 +34,7 @@ class Hierarchy(private val typeRegistry: TypeRegistry) { // java.lang.Thread class has package-private fields, that can be used outside the class. // Since wrapper UtThread does not inherit java.lang.Thread, we cannot use this inheritance condition only - val realTypeHasFieldByName = workaround(WorkaroundReason.TAINT){ - realType.sootClass.getFieldByNameUnsafe(field.name) != null - } + val realTypeHasFieldByName = realType.sootClass.getFieldByNameUnsafe(field.name) != null val realTypeIsInheritor = realFieldDeclaringType.sootClass in ancestors(realType.sootClass.id) if (!realTypeIsInheritor && !realTypeHasFieldByName) { diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/CountDownLatchExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/CountDownLatchExamples.java new file mode 100644 index 0000000000..b1ff95af84 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/CountDownLatchExamples.java @@ -0,0 +1,38 @@ +package org.utbot.examples.threads; + +import org.utbot.api.mock.UtMock; + +import java.util.concurrent.CountDownLatch; + +public class CountDownLatchExamples { + long getAndDown(CountDownLatch countDownLatch) { + UtMock.assume(countDownLatch != null); + + final long count = countDownLatch.getCount(); + + if (count < 0) { + // Unreachable + return -1; + } + + countDownLatch.countDown(); + final long nextCount = countDownLatch.getCount(); + + if (nextCount < 0) { + // Unreachable + return -2; + } + + if (count == 0) { + // Could not differs from 0 too + return nextCount; + } + + if (count - nextCount != 1) { + // Unreachable + return -3; + } + + return nextCount; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/ExecutorServiceExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/ExecutorServiceExamples.java new file mode 100644 index 0000000000..c55005343e --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/ExecutorServiceExamples.java @@ -0,0 +1,23 @@ +package org.utbot.examples.threads; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; + +public class ExecutorServiceExamples { + public void throwingInExecute() { + Executors.newSingleThreadExecutor().execute(() -> { + throw new IllegalStateException(); + }); + } + + public int changingCollectionInExecute() { + List list = new ArrayList<>(); + + Executors.newSingleThreadExecutor().execute(() -> { + list.add(42); + }); + + return list.get(0); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java index d1ab152275..48a2652adc 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java @@ -1,14 +1,41 @@ package org.utbot.examples.threads; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class FutureExamples { - void throwingRunnableExample() throws ExecutionException, InterruptedException { + public void throwingRunnableExample() throws ExecutionException, InterruptedException { final CompletableFuture future = CompletableFuture.runAsync(() -> { throw new IllegalStateException(); }); future.get(); } + + public int resultFromGet() throws ExecutionException, InterruptedException { + final CompletableFuture future = CompletableFuture.supplyAsync(() -> 42); + + return future.get(); + } + + public int changingCollectionInFuture() throws ExecutionException, InterruptedException { + List values = new ArrayList<>(); + + final CompletableFuture future = CompletableFuture.runAsync(() -> values.add(42)); + + future.get(); + + return values.get(0); + } + + @SuppressWarnings("unused") + public int changingCollectionInFutureWithoutGet() { + List values = new ArrayList<>(); + + final CompletableFuture future = CompletableFuture.runAsync(() -> values.add(42)); + + return values.get(0); + } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadExamples.java new file mode 100644 index 0000000000..21cc1bbfe1 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadExamples.java @@ -0,0 +1,29 @@ +package org.utbot.examples.threads; + +import java.util.ArrayList; +import java.util.List; + +public class ThreadExamples { + public void explicitExceptionInStart() { + new Thread(() -> { + throw new IllegalStateException(); + }).start(); + } + + public int changingCollectionInThread() { + List values = new ArrayList<>(); + + new Thread(() -> values.add(42)).start(); + + return values.get(0); + } + + @SuppressWarnings("unused") + public int changingCollectionInThreadWithoutStart() { + List values = new ArrayList<>(); + + final Thread thread = new Thread(() -> values.add(42)); + + return values.get(0); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadStartExample.java b/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadStartExample.java deleted file mode 100644 index 846c93fc80..0000000000 --- a/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadStartExample.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.utbot.examples.threads; - -public class ThreadStartExample { - public void explicitExceptionInStart() { - new Thread(() -> { - throw new IllegalStateException(); - }).start(); - } -} From dd38cb5177b17892ab665461e1bbaf193ac9437a Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Fri, 23 Dec 2022 13:45:30 +0800 Subject: [PATCH 09/12] Added missing quotes --- .github/workflows/framework-tests-matrix.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/framework-tests-matrix.json b/.github/workflows/framework-tests-matrix.json index dbf7cd89dd..68c8fb27ff 100644 --- a/.github/workflows/framework-tests-matrix.json +++ b/.github/workflows/framework-tests-matrix.json @@ -22,7 +22,7 @@ }, { "PART_NAME": "examples-part2", - "TESTS_TO_RUN": "--tests \"org.utbot.examples.exceptions.*\" --tests \"org.utbot.examples.invokes.*\" --tests \"org.utbot.examples.lambda.*\" --tests \"org.utbot.examples.make.symbolic.*\" --tests \"org.utbot.examples.math.*\" --tests \"org.utbot.examples.mixed.*\" --tests \"org.utbot.examples.mock.*\" --tests \"org.utbot.examples.models.*\" --tests \"org.utbot.examples.natives.*\" --tests \"org.utbot.examples.objects.*\" --tests \"org.utbot.examples.reflection.*\" --tests org.utbot.examples.threads.*\"" + "TESTS_TO_RUN": "--tests \"org.utbot.examples.exceptions.*\" --tests \"org.utbot.examples.invokes.*\" --tests \"org.utbot.examples.lambda.*\" --tests \"org.utbot.examples.make.symbolic.*\" --tests \"org.utbot.examples.math.*\" --tests \"org.utbot.examples.mixed.*\" --tests \"org.utbot.examples.mock.*\" --tests \"org.utbot.examples.models.*\" --tests \"org.utbot.examples.natives.*\" --tests \"org.utbot.examples.objects.*\" --tests \"org.utbot.examples.reflection.*\" --tests \"org.utbot.examples.threads.*\"" }, { "PART_NAME": "examples-part3", From fe0c3e6b4bb37824cb2a242ebddccc6b450f69a4 Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Wed, 28 Dec 2022 12:27:34 +0800 Subject: [PATCH 10/12] Fixed a substitution static fields with unbounded variables --- .../examples/annotations/NotNullAnnotationTest.kt | 4 +++- .../src/main/kotlin/org/utbot/engine/Memory.kt | 11 +++++++++-- .../src/main/kotlin/org/utbot/engine/Traverser.kt | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/NotNullAnnotationTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/NotNullAnnotationTest.kt index 6ba1d8bf06..6bb162add4 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/NotNullAnnotationTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/NotNullAnnotationTest.kt @@ -3,6 +3,7 @@ package org.utbot.examples.annotations import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.utbot.testcheckers.eq +import org.utbot.testing.AtLeast import org.utbot.testing.UtValueTestCaseChecker internal class NotNullAnnotationTest : UtValueTestCaseChecker(testClass = NotNullAnnotation::class) { @@ -68,7 +69,8 @@ internal class NotNullAnnotationTest : UtValueTestCaseChecker(testClass = NotNul checkStatics( NotNullAnnotation::notNullStaticField, eq(1), - { statics, result -> result == statics.values.single().value } + { statics, result -> result == statics.values.single().value }, + coverage = AtLeast(66) ) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index 3a36fe177d..b0d3a00991 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -199,6 +199,7 @@ data class Memory( // TODO: split purely symbolic memory and information about s .mapValues { (_, values) -> values.first().value to values.last().value } val previousMemoryStates = staticFieldsStates.toMutableMap() + val previousFieldValues = fieldValues.toMutableMap() /** @@ -212,6 +213,11 @@ data class Memory( // TODO: split purely symbolic memory and information about s update.classIdToClearStatics?.let { classId -> Scene.v().getSootClass(classId.name).fields.forEach { sootField -> previousMemoryStates.remove(sootField.fieldId) + + if (sootField.isStatic) { + // Only statics should be cleared here + previousFieldValues.remove(sootField) + } } } @@ -254,7 +260,7 @@ data class Memory( // TODO: split purely symbolic memory and information about s initializedStaticFields = initializedStaticFields.addAll(update.initializedStaticFields), staticFieldsStates = previousMemoryStates.toPersistentMap().putAll(updatedStaticFields), meaningfulStaticFields = meaningfulStaticFields.addAll(update.meaningfulStaticFields), - fieldValues = fieldValues.putAll(update.fieldValues), + fieldValues = previousFieldValues.toPersistentMap().putAll(update.fieldValues), addrToArrayType = addrToArrayType.putAll(update.addrToArrayType), addrToMockInfo = addrToMockInfo.putAll(update.addrToMockInfo), updates = updates + update, @@ -278,7 +284,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s */ fun queuedStaticMemoryUpdates(): MemoryUpdate = MemoryUpdate( staticInstanceStorage = updates.staticInstanceStorage, - staticFieldsUpdates = updates.staticFieldsUpdates + staticFieldsUpdates = updates.staticFieldsUpdates, + fieldValues = updates.fieldValues.filter { it.key.isStatic }.toPersistentMap() ) /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 9a27ba4393..805df05c1e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -5,6 +5,7 @@ import kotlinx.collections.immutable.persistentHashSetOf import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList +import kotlinx.collections.immutable.toPersistentMap import kotlinx.collections.immutable.toPersistentSet import org.utbot.common.WorkaroundReason.HACK import org.utbot.framework.UtSettings.ignoreStaticsFromTrustedLibraries @@ -1936,7 +1937,6 @@ class Traverser( val touchedStaticFields = persistentListOf(staticFieldMemoryUpdate) queuedSymbolicStateUpdates += MemoryUpdate(staticFieldsUpdates = touchedStaticFields) - // TODO filter enum constant static fields JIRA:1681 if (!environment.method.isStaticInitializer && isStaticFieldMeaningful(field)) { queuedSymbolicStateUpdates += MemoryUpdate(meaningfulStaticFields = persistentSetOf(fieldId)) } @@ -3579,6 +3579,7 @@ class Traverser( val declaringClassId = declaringClass.id val staticFieldsUpdates = updates.staticFieldsUpdates.toMutableList() + val fieldValuesUpdates = updates.fieldValues.toMutableMap() val updatedFields = staticFieldsUpdates.mapTo(mutableSetOf()) { it.fieldId } val objectUpdates = mutableListOf() @@ -3591,6 +3592,7 @@ class Traverser( // remove updates from clinit, because we'll replace those values // with new unbounded symbolic variable staticFieldsUpdates.removeAll { update -> update.fieldId == it.fieldId } + fieldValuesUpdates.keys.removeAll { key -> key.fieldId == it.fieldId } val value = createConst(it.type, it.name) val valueToStore = if (value is ReferenceValue) { @@ -3610,6 +3612,7 @@ class Traverser( return updates.copy( stores = updates.stores.addAll(objectUpdates), staticFieldsUpdates = staticFieldsUpdates.toPersistentList(), + fieldValues = fieldValuesUpdates.toPersistentMap(), classIdToClearStatics = declaringClassId ) } From e5ac4c488504b22e88409604593ff85239d6b982 Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Thu, 29 Dec 2022 12:55:56 +0800 Subject: [PATCH 11/12] Added CompletableFuture.supplyAsync mock implementation --- .../engine/overrides/threads/CompletableFuture.java | 11 +++++++++++ .../org/utbot/examples/threads/FutureExamples.java | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java index 85df79fe3f..f1ea088412 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java @@ -3,6 +3,7 @@ import org.utbot.api.annotation.UtClassMock; import java.util.concurrent.Executor; +import java.util.function.Supplier; @UtClassMock(target = java.util.concurrent.CompletableFuture.class, internalUsage = true) public class CompletableFuture { @@ -16,4 +17,14 @@ public static java.util.concurrent.CompletableFuture runAsync(Runnable run public static java.util.concurrent.CompletableFuture runAsync(Runnable runnable, Executor ignoredExecutor) { return runAsync(runnable); } + + public static java.util.concurrent.CompletableFuture supplyAsync(Supplier supplier) { + try { + final U value = supplier.get(); + + return new UtCompletableFuture<>(value).toCompletableFuture(); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java index 48a2652adc..22a848c256 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java @@ -14,7 +14,7 @@ public void throwingRunnableExample() throws ExecutionException, InterruptedExce future.get(); } - public int resultFromGet() throws ExecutionException, InterruptedException { + public int resultFromGet() throws ExecutionException, InterruptedException { final CompletableFuture future = CompletableFuture.supplyAsync(() -> 42); return future.get(); From ab27cab2e96f96affceb86704803121ace77d9e8 Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Tue, 3 Jan 2023 10:50:46 +0800 Subject: [PATCH 12/12] Fixed review issues --- .../api/visible/UtStreamConsumingException.kt | 3 ++ .../threads/ExecutorServiceExamplesTest.kt | 3 +- .../examples/threads/FutureExamplesTest.kt | 3 +- .../examples/threads/ThreadExamplesTest.kt | 3 +- .../threads/UtCompletableFuture.java | 18 +------- .../overrides/threads/UtExecutorService.java | 44 +------------------ .../overrides/threads/UtThreadGroup.java | 12 ++--- .../main/kotlin/org/utbot/engine/Hierarchy.kt | 6 ++- .../main/kotlin/org/utbot/engine/Memory.kt | 5 ++- .../kotlin/org/utbot/engine/ObjectWrappers.kt | 17 ++++++- .../kotlin/org/utbot/engine/ThreadWrappers.kt | 11 ++--- .../main/kotlin/org/utbot/engine/Traverser.kt | 4 +- .../examples/threads/FutureExamples.java | 2 + 13 files changed, 51 insertions(+), 80 deletions(-) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/visible/UtStreamConsumingException.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/visible/UtStreamConsumingException.kt index fc25154daa..522a73b88f 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/visible/UtStreamConsumingException.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/visible/UtStreamConsumingException.kt @@ -3,6 +3,9 @@ package org.utbot.framework.plugin.api.visible /** * An artificial exception that stores an exception that would be thrown in case of consuming stream by invoking terminal operations. * [innerException] stores this possible exception (null if [UtStreamConsumingException] was constructed by the engine). + * + * NOTE: this class should be visible in almost all parts of the tool - Soot, engine, concrete execution and code generation, + * that's the reason why this class is placed in this module and this package is called `visible`. */ data class UtStreamConsumingException(private val innerException: Exception?) : RuntimeException() { /** diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt index 36dec1e037..dcd8af4731 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt @@ -8,7 +8,8 @@ import org.utbot.testing.ignoreExecutionsNumber import org.utbot.testing.isException // IMPORTANT: most of the these tests test only the symbolic engine -// and should not be used for testing conrete or code generation since they are possibly flaky in the real execution +// and should not be used for testing conrete or code generation since they are possibly flaky in the concrete execution +// (see https://github.com/UnitTestBot/UTBotJava/issues/1610) class ExecutorServiceExamplesTest : UtValueTestCaseChecker(testClass = ExecutorServiceExamples::class) { @Test fun testExceptionInExecute() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt index 37056224a9..c6c600b46f 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt @@ -9,7 +9,8 @@ import org.utbot.testing.isException import java.util.concurrent.ExecutionException // IMPORTANT: most of the these tests test only the symbolic engine -// and should not be used for testing conrete or code generation since they are possibly flaky in the real execution +// and should not be used for testing conrete or code generation since they are possibly flaky in the concrete execution +// (see https://github.com/UnitTestBot/UTBotJava/issues/1610) class FutureExamplesTest : UtValueTestCaseChecker(testClass = FutureExamples::class) { @Test fun testThrowingRunnable() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt index 454e5128dc..3432eb8779 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt @@ -8,7 +8,8 @@ import org.utbot.testing.ignoreExecutionsNumber import org.utbot.testing.isException // IMPORTANT: most of the these tests test only the symbolic engine -// and should not be used for testing conrete or code generation since they are possibly flaky in the real execution +// and should not be used for testing conrete or code generation since they are possibly flaky in the concrete execution +// (see https://github.com/UnitTestBot/UTBotJava/issues/1610) class ThreadExamplesTest : UtValueTestCaseChecker(testClass = ThreadExamples::class) { @Test fun testExceptionInStart() { diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java index 119e22e9fe..c87f14e356 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java @@ -7,7 +7,6 @@ import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -17,31 +16,23 @@ import java.util.function.Function; import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; -import static org.utbot.engine.overrides.UtOverrideMock.visit; -public class UtCompletableFuture implements Future, ScheduledFuture, CompletionStage { +public class UtCompletableFuture implements ScheduledFuture, CompletionStage { T result; Throwable exception; public UtCompletableFuture(T result) { - visit(this); this.result = result; } - public UtCompletableFuture() { - visit(this); - } + public UtCompletableFuture() {} public UtCompletableFuture(Throwable exception) { - visit(this); this.exception = exception; } public UtCompletableFuture(UtCompletableFuture future) { - visit(this); - result = future.result; exception = future.exception; } @@ -51,12 +42,7 @@ public void eqGenericType(T ignoredValue) { } public void preconditionCheck() { - if (alreadyVisited(this)) { - return; - } eqGenericType(result); - - visit(this); } @Override diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java index d9a64f390f..b5246654b9 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java @@ -7,35 +7,17 @@ import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; -import static org.utbot.engine.overrides.UtOverrideMock.visit; - -public class UtExecutorService implements ExecutorService, ScheduledExecutorService { +public class UtExecutorService implements ScheduledExecutorService { private boolean isShutdown; private boolean isTerminated; - public UtExecutorService() { - visit(this); - } - - private void preconditionCheck() { - if (alreadyVisited(this)) { - return; - } - - visit(this); - } - @Override public void shutdown() { - preconditionCheck(); - isShutdown = true; isTerminated = true; } @@ -43,8 +25,6 @@ public void shutdown() { @NotNull @Override public List shutdownNow() { - preconditionCheck(); - shutdown(); // Since all tasks are processed immediately, there are no waiting tasks return new ArrayList<>(); @@ -52,15 +32,11 @@ public List shutdownNow() { @Override public boolean isShutdown() { - preconditionCheck(); - return isShutdown; } @Override public boolean isTerminated() { - preconditionCheck(); - return isTerminated; } @@ -73,8 +49,6 @@ public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) { @NotNull @Override public Future submit(@NotNull Callable task) { - preconditionCheck(); - try { T result = task.call(); return new UtCompletableFuture<>(result); @@ -86,8 +60,6 @@ public Future submit(@NotNull Callable task) { @NotNull @Override public Future submit(@NotNull Runnable task, T result) { - preconditionCheck(); - try { task.run(); return new UtCompletableFuture<>(result); @@ -105,8 +77,6 @@ public Future submit(@NotNull Runnable task) { @NotNull @Override public List> invokeAll(@NotNull Collection> tasks) { - preconditionCheck(); - List> results = new ArrayList<>(); for (Callable task : tasks) { results.add(submit(task)); @@ -124,8 +94,6 @@ public List> invokeAll(@NotNull Collection> @NotNull @Override public T invokeAny(@NotNull Collection> tasks) throws ExecutionException { - preconditionCheck(); - for (Callable task : tasks) { try { return task.call(); @@ -145,16 +113,12 @@ public T invokeAny(@NotNull Collection> tasks, long ti @Override public void execute(@NotNull Runnable command) { - preconditionCheck(); - command.run(); } @NotNull @Override public ScheduledFuture schedule(@NotNull Runnable command, long delay, @NotNull TimeUnit unit) { - preconditionCheck(); - try { command.run(); return new UtCompletableFuture<>(); @@ -166,8 +130,6 @@ public ScheduledFuture schedule(@NotNull Runnable command, long delay, @NotNu @NotNull @Override public ScheduledFuture schedule(@NotNull Callable callable, long delay, @NotNull TimeUnit unit) { - preconditionCheck(); - try { V result = callable.call(); return new UtCompletableFuture<>(result); @@ -179,8 +141,6 @@ public ScheduledFuture schedule(@NotNull Callable callable, long delay @NotNull @Override public ScheduledFuture scheduleAtFixedRate(@NotNull Runnable command, long initialDelay, long period, @NotNull TimeUnit unit) { - preconditionCheck(); - if (period <= 0) { throw new IllegalArgumentException(); } @@ -191,8 +151,6 @@ public ScheduledFuture scheduleAtFixedRate(@NotNull Runnable command, long in @NotNull @Override public ScheduledFuture scheduleWithFixedDelay(@NotNull Runnable command, long initialDelay, long delay, @NotNull TimeUnit unit) { - preconditionCheck(); - if (delay <= 0) { throw new IllegalArgumentException(); } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java index 54452eb876..5450ed7706 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java @@ -101,7 +101,7 @@ public final void setMaxPriority(int pri) { return; } - for (int i = 0 ; i < ngroups ; i++) { + for (int i = 0; i < ngroups; i++) { groups[i].setMaxPriority(pri); } } @@ -199,11 +199,11 @@ public final void stop() { public final void interrupt() { preconditionCheck(); - for (int i = 0 ; i < nthreads ; i++) { + for (int i = 0; i < nthreads; i++) { threads[i].interrupt(); } - for (int i = 0 ; i < ngroups ; i++) { + for (int i = 0; i < ngroups; i++) { groups[i].interrupt(); } } @@ -232,12 +232,12 @@ public final void destroy() { nthreads = 0; threads = null; } - for (int i = 0 ; i < ngroups ; i += 1) { + for (int i = 0; i < ngroups; i += 1) { groups[i].destroy(); } } - private void add(ThreadGroup g){ + private void add(ThreadGroup g) { preconditionCheck(); if (destroyed) { @@ -261,7 +261,7 @@ private void remove(ThreadGroup g) { return; } - for (int i = 0 ; i < ngroups ; i++) { + for (int i = 0; i < ngroups; i++) { if (groups[i] == g) { ngroups -= 1; System.arraycopy(groups, i + 1, groups, i, ngroups - i); diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt index be167ac2ba..55145dc2bc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt @@ -33,8 +33,10 @@ class Hierarchy(private val typeRegistry: TypeRegistry) { val realFieldDeclaringType = typeRegistry.findRealType(field.declaringClass.type) as RefType // java.lang.Thread class has package-private fields, that can be used outside the class. - // Since wrapper UtThread does not inherit java.lang.Thread, we cannot use this inheritance condition only - val realTypeHasFieldByName = realType.sootClass.getFieldByNameUnsafe(field.name) != null + // Since wrapper UtThread does not inherit java.lang.Thread, we cannot use this inheritance condition only. + // The possible presence of hidden field is not important here - we just need + // to know whether we have at least one such field. + val realTypeHasFieldByName = realType.sootClass.getFieldUnsafe(field.subSignature) != null val realTypeIsInheritor = realFieldDeclaringType.sootClass in ancestors(realType.sootClass.id) if (!realTypeIsInheritor && !realTypeHasFieldByName) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index b0d3a00991..0b00b147ac 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -65,7 +65,10 @@ import soot.Type * * Note: [staticInitial] contains mapping from [FieldId] to the memory state at the moment of the field initialization. * - * [fieldValues] stores symbolic values for specified fields of the specified object instances. + * [fieldValues] stores symbolic values for specified fields of the concrete object instances. + * We need to associate field of a concrete instance with the created symbolic value to not lose an information about its type. + * For example, if field's declared type is Runnable but at the current state it is a specific lambda, + * we have to save this lambda as a type of this field to be able to retrieve it in the future. * * @see memoryForNestedMethod * @see FieldStates diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt index 6c27c40f55..5b3d47a613 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt @@ -75,6 +75,17 @@ val classToWrapper: MutableMap = putSootClass(java.lang.Thread::class, utThreadClass) putSootClass(java.lang.ThreadGroup::class, utThreadGroupClass) + // executors, futures and latches + putSootClass(ExecutorService::class, utExecutorServiceClass) + putSootClass(ThreadPoolExecutor::class, utExecutorServiceClass) + putSootClass(ForkJoinPool::class, utExecutorServiceClass) + putSootClass(ScheduledThreadPoolExecutor::class, utExecutorServiceClass) + putSootClass(CountDownLatch::class, utCountDownLatchClass) + putSootClass(CompletableFuture::class, utCompletableFutureClass) + putSootClass(CompletionStage::class, utCompletableFutureClass) + // A hack to be able to create UtCompletableFuture in its methods as a wrapper + putSootClass(UtCompletableFuture::class, utCompletableFutureClass) + putSootClass(RangeModifiableUnlimitedArray::class, RangeModifiableUnlimitedArrayWrapper::class) putSootClass(AssociativeArray::class, AssociativeArrayWrapper::class) @@ -228,9 +239,13 @@ private val wrappers = mapOf( wrap(SecurityManager::class) { type, addr -> objectValue(type, addr, SecurityManagerWrapper()) }, ).also { // check every `wrapped` class has a corresponding value in [classToWrapper] - it.keys.all { key -> + val missedWrappers = it.keys.filterNot { key -> Scene.v().getSootClass(key.name).type in classToWrapper.keys } + + require(missedWrappers.isEmpty()) { + "Missed wrappers for classes [${missedWrappers.joinToString(", ")}]" + } } private fun wrap(kClass: KClass<*>, implementation: (RefType, UtAddrExpression) -> ObjectValue) = diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt index c36e0521cc..2400462da5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt @@ -77,7 +77,8 @@ class ThreadWrapper : BaseOverriddenWrapper(utThreadClass.name) { } companion object { - private val runnableType: RefType = Scene.v().getSootClass(Runnable::class.qualifiedName).type!! + private val runnableType: RefType + get() = Scene.v().getSootClass(Runnable::class.qualifiedName).type!! private val targetFieldId: FieldId get() = utThreadClass.getField("target", runnableType).fieldId } @@ -114,10 +115,10 @@ class ThreadGroupWrapper : BaseOverriddenWrapper(utThreadGroupClass.name) { } } -private val TO_COMPLETABLE_FUTURE_SIGNATURE: String - get() = utCompletableFutureClass.getMethodByName(UtCompletableFuture<*>::toCompletableFuture.name).signature -private val UT_COMPLETABLE_FUTURE_EQ_GENERIC_TYPE_SIGNATURE: String - get() = utCompletableFutureClass.getMethodByName(UtCompletableFuture<*>::eqGenericType.name).signature +private val TO_COMPLETABLE_FUTURE_SIGNATURE: String = + utCompletableFutureClass.getMethodByName(UtCompletableFuture<*>::toCompletableFuture.name).signature +private val UT_COMPLETABLE_FUTURE_EQ_GENERIC_TYPE_SIGNATURE: String = + utCompletableFutureClass.getMethodByName(UtCompletableFuture<*>::eqGenericType.name).signature class CompletableFutureWrapper : BaseOverriddenWrapper(utCompletableFutureClass.name) { override fun Traverser.overrideInvoke( diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 805df05c1e..3f7beb7f3c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -962,9 +962,7 @@ class Traverser( } } - // We need to associate this field with the created symbolic value to not lose an information about its type. - // For example, if field's declared type is Runnable but at the current state it is a specific lambda, - // we have to save this lambda as a type of this field to be able to retrieve it in the future. + // Associate created value with field of used instance. For more information check Memory#fieldValue docs. val fieldValuesUpdate = fieldUpdate(left.field, instanceForField.addr, value) SymbolicStateUpdate(memoryUpdates = objectUpdate + fieldValuesUpdate) diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java index 22a848c256..2b233f239d 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java @@ -30,6 +30,8 @@ public int changingCollectionInFuture() throws ExecutionException, InterruptedEx return values.get(0); } + // NOTE: this tests looks similar as the test above BUT it is important to check correctness of the wrapper + // for CompletableFuture - an actions is executed regardless of invoking `get` method. @SuppressWarnings("unused") public int changingCollectionInFutureWithoutGet() { List values = new ArrayList<>();