diff --git a/.github/workflows/framework-tests-matrix.json b/.github/workflows/framework-tests-matrix.json index 300a6e187c..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_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-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/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-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..dcd8af4731 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt @@ -0,0 +1,40 @@ +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 concrete execution +// (see https://github.com/UnitTestBot/UTBotJava/issues/1610) +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 new file mode 100644 index 0000000000..c6c600b46f --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt @@ -0,0 +1,60 @@ +package org.utbot.examples.threads + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +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 concrete execution +// (see https://github.com/UnitTestBot/UTBotJava/issues/1610) +class FutureExamplesTest : UtValueTestCaseChecker(testClass = FutureExamples::class) { + @Test + fun testThrowingRunnable() { + withoutConcrete { + checkWithException( + FutureExamples::throwingRunnableExample, + eq(1), + { r -> r.isException() }, + coverage = AtLeast(71) + ) + } + } + + @Test + fun testResultFromGet() { + check( + FutureExamples::resultFromGet, + eq(1), + { 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..3432eb8779 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt @@ -0,0 +1,54 @@ +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 concrete execution +// (see https://github.com/UnitTestBot/UTBotJava/issues/1610) +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/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/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/CompletableFuture.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java new file mode 100644 index 0000000000..f1ea088412 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java @@ -0,0 +1,30 @@ +package org.utbot.engine.overrides.threads; + +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 { + 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); + } + + 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-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 new file mode 100644 index 0000000000..c87f14e356 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java @@ -0,0 +1,453 @@ +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.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledFuture; +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 java.util.concurrent.TimeUnit.NANOSECONDS; + +public class UtCompletableFuture implements ScheduledFuture, CompletionStage { + T result; + + Throwable exception; + + public UtCompletableFuture(T result) { + this.result = result; + } + + public UtCompletableFuture() {} + + public UtCompletableFuture(Throwable exception) { + this.exception = exception; + } + + public UtCompletableFuture(UtCompletableFuture future) { + result = future.result; + exception = future.exception; + } + + public void eqGenericType(T ignoredValue) { + // Will be processed symbolically + } + + public void preconditionCheck() { + eqGenericType(result); + } + + @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(); + } + + @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/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/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..b5246654b9 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java @@ -0,0 +1,160 @@ +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.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class UtExecutorService implements ScheduledExecutorService { + private boolean isShutdown; + private boolean isTerminated; + + @Override + public void shutdown() { + isShutdown = true; + isTerminated = true; + } + + @NotNull + @Override + public List shutdownNow() { + shutdown(); + // Since all tasks are processed immediately, there are no waiting tasks + return new ArrayList<>(); + } + + @Override + public boolean isShutdown() { + return isShutdown; + } + + @Override + public boolean isTerminated() { + 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) { + 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) { + 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) { + 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 { + 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) { + command.run(); + } + + @NotNull + @Override + public ScheduledFuture schedule(@NotNull Runnable command, long delay, @NotNull TimeUnit unit) { + 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) { + 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) { + 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) { + if (delay <= 0) { + throw new IllegalArgumentException(); + } + + return schedule(command, initialDelay, unit); + } +} 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..8a8b3560c6 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java @@ -0,0 +1,434 @@ +package org.utbot.engine.overrides.threads; + +import org.utbot.api.mock.UtMock; + +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; + 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; + + // 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. + */ + 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) { + visit(this); + + 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(); + assume(priority >= MIN_PRIORITY); + assume(priority <= MIN_PRIORITY); + this.priority = priority; + setPriority(this.priority); + + this.contextClassLoader = UtMock.makeSymbolic(); + 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() { + 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; + } + + public static boolean interrupted() { + return UtMock.makeSymbolic(); + } + + public boolean isInterrupted() { + preconditionCheck(); + + return isInterrupted; + } + + private boolean isInterrupted(boolean clearInterrupted) { + preconditionCheck(); + + boolean result = isInterrupted; + + if (clearInterrupted) { + isInterrupted = false; + } + + return result; + } + + 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(); + } + + if (group != null) { + if (newPriority > group.getMaxPriority()) { + newPriority = group.getMaxPriority(); + } + + priority = newPriority; + } + } + + public final int getPriority() { + preconditionCheck(); + + return priority; + } + + public final void setName(String name) { + preconditionCheck(); + + if (name == null) { + throw new NullPointerException(); + } + + this.name = name; + } + + public final String getName() { + preconditionCheck(); + + return name; + } + + public final ThreadGroup getThreadGroup() { + preconditionCheck(); + + return group; + } + + public static int activeCount() { + final Integer result = UtMock.makeSymbolic(); + assume(result >= 0); + + return result; + } + + public static int enumerate(UtThread[] tarray) { + Integer length = UtMock.makeSymbolic(); + assume(length >= 0); + assume(length <= tarray.length); + + for (int i = 0; i < length; i++) { + tarray[i] = UtMock.makeSymbolic(); + } + + return length; + } + + public int countStackFrames() { + preconditionCheck(); + + return UtMock.makeSymbolic(); + } + + public final void join(long millis) throws InterruptedException { + preconditionCheck(); + + if (millis < 0) { + throw new IllegalArgumentException(); + } + + // Do nothing + } + + public final void join(long millis, int nanos) throws InterruptedException { + preconditionCheck(); + + if (millis < 0) { + throw new IllegalArgumentException(); + } + + if (nanos < 0 || nanos > 999999) { + throw new IllegalArgumentException(); + } + + // Do nothing + } + + public final void join() throws InterruptedException { + preconditionCheck(); + + join(0); + } + + public static void dumpStack() { + // Do nothing + } + + 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() + "]"; + } else { + return "Thread[" + getName() + "," + getPriority() + "," + + "" + "]"; + } + } + + public ClassLoader getContextClassLoader() { + preconditionCheck(); + + return contextClassLoader; + } + + public void setContextClassLoader(ClassLoader cl) { + preconditionCheck(); + + contextClassLoader = cl; + } + + public static boolean holdsLock(Object obj) { + if (obj == null) { + throw new NullPointerException(); + } + + return UtMock.makeSymbolic(); + } + + public StackTraceElement[] getStackTrace() { + preconditionCheck(); + + return UtMock.makeSymbolic(); + } + + public static Map getAllStackTraces() { + return UtMock.makeSymbolic(); + } + + public long getId() { + preconditionCheck(); + + return tid; + } + + public Thread.State getState() { + preconditionCheck(); + + return UtMock.makeSymbolic(); + } + + public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) { + defaultUncaughtExceptionHandler = eh; + } + + public static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() { + return defaultUncaughtExceptionHandler; + } + + 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 new file mode 100644 index 0000000000..5450ed7706 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java @@ -0,0 +1,327 @@ +package org.utbot.engine.overrides.threads; + +import org.utbot.api.mock.UtMock; + +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; + 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) { + visit(this); + + this.name = name; + + 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 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; + } + + for (int i = 0; i < ngroups; i++) { + groups[i].setMaxPriority(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(); + 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) { + preconditionCheck(); + + if (destroyed) { + return 0; + } + + list = UtMock.makeSymbolic(); + + final Integer result = UtMock.makeSymbolic(); + assume(result <= list.length); + assume(result >= 0); + + return result; + } + + public int activeGroupCount() { + preconditionCheck(); + + if (destroyed) { + return 0; + } + + final Integer result = UtMock.makeSymbolic(); + 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) { + preconditionCheck(); + + if (destroyed) { + return 0; + } + + list = UtMock.makeSymbolic(); + + final Integer result = UtMock.makeSymbolic(); + 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(); + } + + for (int i = 0; i < ngroups; i++) { + groups[i].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(); + } + + 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) { + preconditionCheck(); + + 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) { + preconditionCheck(); + + 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() { + preconditionCheck(); + + if (destroyed) { + throw new IllegalThreadStateException(); + } + + nUnstartedThreads++; + } + + void add(Thread t) { + preconditionCheck(); + + 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() { + 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 + "]"; + } +} 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..55145dc2bc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt @@ -32,7 +32,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. + // 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) { error("No such field ${field.subSignature} found in ${realType.sootClass.name}") } return ChunkId("$realFieldDeclaringType", field.name) 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..0b00b147ac 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,11 @@ 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 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 */ @@ -77,6 +83,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 +117,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. @@ -188,6 +202,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() /** @@ -201,6 +216,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) + } } } @@ -243,6 +263,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 = previousFieldValues.toPersistentMap().putAll(update.fieldValues), addrToArrayType = addrToArrayType.putAll(update.addrToArrayType), addrToMockInfo = addrToMockInfo.putAll(update.addrToMockInfo), updates = updates + update, @@ -266,7 +287,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() ) /** @@ -342,6 +364,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 +384,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..5b3d47a613 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt @@ -21,12 +21,12 @@ 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 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 @@ -40,9 +40,15 @@ 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 java.util.concurrent.CountDownLatch +import java.util.concurrent.ExecutorService +import java.util.concurrent.ForkJoinPool +import java.util.concurrent.ScheduledThreadPoolExecutor +import java.util.concurrent.ThreadPoolExecutor import kotlin.reflect.KClass -import kotlin.reflect.KFunction4 typealias TypeToBeWrapped = RefType typealias WrapperType = RefType @@ -65,6 +71,21 @@ 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) + + // 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) @@ -146,6 +167,19 @@ 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(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(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 + wrap(UtCompletableFuture::class) { type, addr -> objectValue(type, addr, CompletableFutureWrapper()) }, + wrap(RangeModifiableUnlimitedArray::class) { type, addr -> objectValue(type, addr, RangeModifiableUnlimitedArrayWrapper()) }, @@ -205,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) = @@ -250,14 +288,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..2400462da5 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt @@ -0,0 +1,235 @@ +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 +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 +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.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 +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) +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( + 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 values = collectFieldModels(wrapper.addr, overriddenClass.type) + val targetModel = values[targetFieldId] 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) + } + + companion object { + private val runnableType: RefType + get() = Scene.v().getSootClass(Runnable::class.qualifiedName).type!! + private val targetFieldId: FieldId + get() = utThreadClass.getField("target", runnableType).fieldId + } +} + +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 values = collectFieldModels(wrapper.addr, overriddenClass.type) + val nameModel = values[nameFieldId] ?: UtNullModel(stringClassId) + + val instantiationCall = UtExecutableCallModel( + instance = null, + constructorId(classId, stringClassId), + listOf(nameModel) + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + private val nameFieldId: FieldId + get() = utThreadGroupClass.getField("name", STRING_TYPE).fieldId + } +} + +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( + 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 + } +} + +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.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/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 59da8bf29d..3f7beb7f3c 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 @@ -961,7 +962,10 @@ class Traverser( } } - SymbolicStateUpdate(memoryUpdates = objectUpdate) + // 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) } is JimpleLocal -> SymbolicStateUpdate(localMemoryUpdates = localMemoryUpdate(left.variable to value)) is InvokeExpr -> TODO("Not implemented: $left") @@ -1931,7 +1935,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)) } @@ -1941,7 +1944,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 +1953,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 +2068,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 +2245,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, @@ -3554,6 +3577,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() @@ -3566,6 +3590,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) { @@ -3585,6 +3610,7 @@ class Traverser( return updates.copy( stores = updates.stores.addAll(objectUpdates), staticFieldsUpdates = staticFieldsUpdates.toPersistentList(), + fieldValues = fieldValuesUpdates.toPersistentMap(), classIdToClearStatics = declaringClassId ) } 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..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 @@ -43,6 +43,9 @@ import soot.SootClass 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) { @@ -354,6 +357,16 @@ 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 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 0fc195d704..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 @@ -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,18 @@ 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.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.threads.UtCountDownLatch::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/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 new file mode 100644 index 0000000000..2b233f239d --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java @@ -0,0 +1,43 @@ +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 { + 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); + } + + // 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<>(); + + 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); + } +}