From f7cb2676d86d07953d751eef6352272c60e1a04f Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 12:05:06 +0300 Subject: [PATCH 01/19] Add highlighting link to UtBotSymbolicEngine in the comments --- .../utbot/engine/overrides/UtArrayMock.java | 22 +++++++++---------- .../utbot/engine/overrides/UtLogicMock.java | 2 +- .../engine/overrides/UtOverrideMock.java | 12 +++++----- .../overrides/collections/UtArrayList.java | 2 +- .../overrides/collections/UtHashMap.java | 2 +- .../overrides/collections/UtHashSet.java | 2 +- .../overrides/collections/UtOptional.java | 2 +- .../collections/UtOptionalDouble.java | 2 +- .../overrides/collections/UtOptionalInt.java | 2 +- .../overrides/collections/UtOptionalLong.java | 2 +- .../org/utbot/engine/CollectionWrappers.kt | 2 +- .../main/kotlin/org/utbot/engine/Memory.kt | 14 +++++++----- 12 files changed, 34 insertions(+), 32 deletions(-) diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java index 4fa2d34b2c..47cdca76d4 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java @@ -5,7 +5,7 @@ /** * Auxiliary class with static methods without implementation. - * These static methods are just markers for UtBotSymbolicEngine, + * These static methods are just markers for {@link org.utbot.engine.UtBotSymbolicEngine}., * to do some corresponding behavior, that can't be represent * with java instructions. *

@@ -15,7 +15,7 @@ @SuppressWarnings("unused") public class UtArrayMock { /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should * behave similar to call of {@link java.util.Arrays#copyOf(Object[], int)} * if length is less or equals to src.length, otherwise first * src.length elements are equal to src, but the rest are undefined. @@ -45,7 +45,7 @@ public static char[] copyOf(char[] src, int length) { } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -67,7 +67,7 @@ public static void arraycopy(Object[] src, int srcPos, Object[] dst, int destPos } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -83,7 +83,7 @@ public static void arraycopy(boolean[] src, int srcPos, boolean[] dst, int destP } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -99,7 +99,7 @@ public static void arraycopy(byte[] src, int srcPos, byte[] dst, int destPos, in } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -115,7 +115,7 @@ public static void arraycopy(char[] src, int srcPos, char[] dst, int destPos, in } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -131,7 +131,7 @@ public static void arraycopy(short[] src, int srcPos, short[] dst, int destPos, } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -147,7 +147,7 @@ public static void arraycopy(int[] src, int srcPos, int[] dst, int destPos, int } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -163,7 +163,7 @@ public static void arraycopy(long[] src, int srcPos, long[] dst, int destPos, in } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -179,7 +179,7 @@ public static void arraycopy(float[] src, int srcPos, float[] dst, int destPos, } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java index 550f09803c..411a0e6f80 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java @@ -2,7 +2,7 @@ /** * Auxiliary class with static methods without implementation. - * These static methods are just markers for UtBotSymbolicEngine, + * These static methods are just markers for {@link org.utbot.engine.UtBotSymbolicEngine}, * to do some corresponding behavior, that can be represented with smt expressions. *

* UtLogicMock is used to store bool smt bool expressions in diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java index 4a0e1b863f..0847a6cc9b 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java @@ -2,13 +2,13 @@ /** * Auxiliary class with static methods without implementation. - * These static methods are just markers for UtBotSymbolicEngine, + * These static methods are just markers for {@link org.utbot.engine.UtBotSymbolicEngine}, * to do some corresponding behavior, that can't be represent * with java instructions. * * Set of methods in UtOverrideMock is used in code of classes, * that override implementation of some standard class by new implementation, - * that is more simple for UtBotSymbolicEngine to traverse. + * that is more simple for {@link org.utbot.engine.UtBotSymbolicEngine} to traverse. */ @SuppressWarnings("unused") public class UtOverrideMock { @@ -25,7 +25,7 @@ public static boolean alreadyVisited(Object o) { } /** - * If UtBotSymbolicEngine meets invoke of this method in code, + * If {@link org.utbot.engine.UtBotSymbolicEngine} meets invoke of this method in code, * then it marks the address of object o in memory as visited * and creates new MemoryUpdate with parameter isVisited, equal to o.addr * @param o parameter, that need to be marked as visited. @@ -34,7 +34,7 @@ public static void visit(Object o) { } /** - * If UtBotSymbolicEngine meets invoke of this method in code, + * If {@link org.utbot.engine.UtBotSymbolicEngine} meets invoke of this method in code, * then it marks the method, where met instruction is placed, * and all the methods that will be traversed in nested invokes * as methods that couldn't throw exceptions. @@ -44,7 +44,7 @@ public static void doesntThrow() { } /** - * If UtBotSymbolicEngine meets invoke of this method in code, + * If {@link org.utbot.engine.UtBotSymbolicEngine} meets invoke of this method in code, * then it assumes that the specified object is parameter, * and need to be marked as parameter. * As address space of parameters in engine is non-positive, while @@ -63,7 +63,7 @@ public static void parameter(Object[] objects) { } /** - * If UtBotSymbolicEngine meets invoke of this method in code, + * If {@link org.utbot.engine.UtBotSymbolicEngine} meets invoke of this method in code, * then it starts concrete execution from this point. */ public static void executeConcretely() { diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java index 5db94b3fb7..5f2a72065d 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java @@ -28,7 +28,7 @@ /** - * Class represents hybrid implementation (java + engine instructions) of List interface for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of List interface for {@link org.utbot.engine.UtBotSymbolicEngine}. *

* Implementation is based on org.utbot.engine.overrides.collections.RangeModifiableArray. * Should behave similar to {@link java.util.ArrayList}. diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java index 57aba7f5c4..414531e759 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java @@ -23,7 +23,7 @@ /** - * Class represents hybrid implementation (java + engine instructions) of Map interface for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of Map interface for {@link org.utbot.engine.UtBotSymbolicEngine}. *

* Implementation is based on using org.utbot.engine.overrides.collections.RangeModifiableArray as keySet * and org.utbot.engine.overrides.collections.UtArray as associative array from keys to values. diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java index 69dd700e22..b854a2ac7f 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java @@ -20,7 +20,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of Set interface for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of Set interface for {@link org.utbot.engine.UtBotSymbolicEngine}. *

* Implementation is based on RangedModifiableArray, and all operations are linear. * Should behave similar to diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java index 3fbe639a74..fd1c28df5e 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java @@ -13,7 +13,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of Optional for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of Optional for {@link org.utbot.engine.UtBotSymbolicEngine}. *

* Should behave the same as {@link java.util.Optional}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java index 7dba6bdd45..9635a2aa8c 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java @@ -11,7 +11,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of OptionalDouble for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of OptionalDouble for {@link org.utbot.engine.UtBotSymbolicEngine}. *

* Should behave the same as {@link java.util.OptionalDouble}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java index bf6c5757e7..b31b4d06bb 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java @@ -11,7 +11,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of OptionalInt for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of OptionalInt for {@link org.utbot.engine.UtBotSymbolicEngine}. *

* Should behave the same as {@link java.util.OptionalInt}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java index 534eae3eba..e2bd74c048 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java @@ -10,7 +10,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of Optional for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of Optional for {@link org.utbot.engine.UtBotSymbolicEngine}. *

* Should behave the same as {@link java.util.Optional}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt index 55a65470cf..38aeb51459 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt @@ -65,7 +65,7 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) * * Multiple GraphResults are returned because, we shouldn't substitute invocation of specified * that was called inside substituted method of object with the same address as specified [wrapper]. - * (For example UtArrayList. invokes AbstractList. that also leads to UtBotSymbolicEngine.invoke, + * (For example UtArrayList. invokes AbstractList. that also leads to [UtBotSymbolicEngine.invoke], * and shouldn't be substituted with UtArrayList. again). Only one GraphResult is valid, that is * guaranteed by contradictory to each other sets of constraints, added to them. */ 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 cf77433848..edf693cc8c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -238,12 +238,14 @@ data class Memory( // TODO: split purely symbolic memory and information about s val previousMemoryStates = staticFieldsStates.toMutableMap() - // sometimes we want to change initial memory states of fields of a certain class, so we erase all the - // information about previous states and update it with the current state. For now, this processing only takes - // place after receiving MethodResult from [STATIC_INITIALIZER] method call at the end of - // [UtBotSymbolicEngine.processStaticInitializer]. The value of `update.classIdToClearStatics` is equal to the - // class for which the static initialization has performed. - // TODO: JIRA:1610 -- refactor working with statics later + /** + * sometimes we want to change initial memory states of fields of a certain class, so we erase all the + * information about previous states and update it with the current state. For now, this processing only takes + * place after receiving MethodResult from [STATIC_INITIALIZER] method call at the end of + * [UtBotSymbolicEngine.processStaticInitializer]. The value of `update.classIdToClearStatics` is equal to the + * class for which the static initialization has performed. + * TODO: JIRA:1610 -- refactor working with statics later + */ update.classIdToClearStatics?.let { classId -> Scene.v().getSootClass(classId.name).fields.forEach { sootField -> previousMemoryStates.remove(sootField.fieldId) From 258c8f569dabdfdda2055f915e572d8df7816e97 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 12:08:20 +0300 Subject: [PATCH 02/19] Rename UtBotSymbolicEngine to Traverser --- .../utbot/engine/overrides/UtArrayMock.java | 20 +++++++++---------- .../utbot/engine/overrides/UtLogicMock.java | 2 +- .../engine/overrides/UtOverrideMock.java | 12 +++++------ .../overrides/collections/UtArrayList.java | 2 +- .../overrides/collections/UtHashMap.java | 2 +- .../overrides/collections/UtHashSet.java | 2 +- .../overrides/collections/UtOptional.java | 2 +- .../collections/UtOptionalDouble.java | 2 +- .../overrides/collections/UtOptionalInt.java | 2 +- .../overrides/collections/UtOptionalLong.java | 2 +- .../org/utbot/engine/ArrayObjectWrappers.kt | 16 +++++++-------- .../org/utbot/engine/CollectionWrappers.kt | 14 ++++++------- .../kotlin/org/utbot/engine/DataClasses.kt | 2 +- .../kotlin/org/utbot/engine/Extensions.kt | 10 +++++----- .../main/kotlin/org/utbot/engine/Memory.kt | 2 +- .../src/main/kotlin/org/utbot/engine/Mocks.kt | 12 +++++------ .../kotlin/org/utbot/engine/ObjectWrappers.kt | 4 ++-- .../org/utbot/engine/OptionalWrapper.kt | 2 +- .../main/kotlin/org/utbot/engine/Resolver.kt | 8 ++++---- .../main/kotlin/org/utbot/engine/Strings.kt | 8 ++++---- .../kotlin/org/utbot/engine/SymbolicValue.kt | 4 ++-- .../{UtBotSymbolicEngine.kt => Traverser.kt} | 6 +++--- .../org/utbot/engine/pc/UtExpression.kt | 2 +- .../statics/concrete/EnumConcreteUtils.kt | 6 +++--- .../plugin/api/UtBotTestCaseGenerator.kt | 10 +++++----- .../exceptions/ExceptionExamplesTest.kt | 4 ++-- utbot-framework/src/test/resources/log4j2.xml | 2 +- .../src/main/resources/log4j2.xml | 2 +- 28 files changed, 81 insertions(+), 81 deletions(-) rename utbot-framework/src/main/kotlin/org/utbot/engine/{UtBotSymbolicEngine.kt => Traverser.kt} (99%) diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java index 47cdca76d4..12fc304e6e 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java @@ -15,7 +15,7 @@ @SuppressWarnings("unused") public class UtArrayMock { /** - * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of {@link java.util.Arrays#copyOf(Object[], int)} * if length is less or equals to src.length, otherwise first * src.length elements are equal to src, but the rest are undefined. @@ -45,7 +45,7 @@ public static char[] copyOf(char[] src, int length) { } /** - * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -67,7 +67,7 @@ public static void arraycopy(Object[] src, int srcPos, Object[] dst, int destPos } /** - * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -83,7 +83,7 @@ public static void arraycopy(boolean[] src, int srcPos, boolean[] dst, int destP } /** - * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -99,7 +99,7 @@ public static void arraycopy(byte[] src, int srcPos, byte[] dst, int destPos, in } /** - * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -115,7 +115,7 @@ public static void arraycopy(char[] src, int srcPos, char[] dst, int destPos, in } /** - * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -131,7 +131,7 @@ public static void arraycopy(short[] src, int srcPos, short[] dst, int destPos, } /** - * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -147,7 +147,7 @@ public static void arraycopy(int[] src, int srcPos, int[] dst, int destPos, int } /** - * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -163,7 +163,7 @@ public static void arraycopy(long[] src, int srcPos, long[] dst, int destPos, in } /** - * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -179,7 +179,7 @@ public static void arraycopy(float[] src, int srcPos, float[] dst, int destPos, } /** - * Traversing this instruction by {@link org.utbot.engine.UtBotSymbolicEngine} should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java index 411a0e6f80..79bccfe301 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java @@ -2,7 +2,7 @@ /** * Auxiliary class with static methods without implementation. - * These static methods are just markers for {@link org.utbot.engine.UtBotSymbolicEngine}, + * These static methods are just markers for {@link org.utbot.engine.Traverser}, * to do some corresponding behavior, that can be represented with smt expressions. *

* UtLogicMock is used to store bool smt bool expressions in diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java index 0847a6cc9b..2a016f12b7 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java @@ -2,13 +2,13 @@ /** * Auxiliary class with static methods without implementation. - * These static methods are just markers for {@link org.utbot.engine.UtBotSymbolicEngine}, + * These static methods are just markers for {@link org.utbot.engine.Traverser}, * to do some corresponding behavior, that can't be represent * with java instructions. * * Set of methods in UtOverrideMock is used in code of classes, * that override implementation of some standard class by new implementation, - * that is more simple for {@link org.utbot.engine.UtBotSymbolicEngine} to traverse. + * that is more simple for {@link org.utbot.engine.Traverser} to traverse. */ @SuppressWarnings("unused") public class UtOverrideMock { @@ -25,7 +25,7 @@ public static boolean alreadyVisited(Object o) { } /** - * If {@link org.utbot.engine.UtBotSymbolicEngine} meets invoke of this method in code, + * If {@link org.utbot.engine.Traverser} meets invoke of this method in code, * then it marks the address of object o in memory as visited * and creates new MemoryUpdate with parameter isVisited, equal to o.addr * @param o parameter, that need to be marked as visited. @@ -34,7 +34,7 @@ public static void visit(Object o) { } /** - * If {@link org.utbot.engine.UtBotSymbolicEngine} meets invoke of this method in code, + * If {@link org.utbot.engine.Traverser} meets invoke of this method in code, * then it marks the method, where met instruction is placed, * and all the methods that will be traversed in nested invokes * as methods that couldn't throw exceptions. @@ -44,7 +44,7 @@ public static void doesntThrow() { } /** - * If {@link org.utbot.engine.UtBotSymbolicEngine} meets invoke of this method in code, + * If {@link org.utbot.engine.Traverser} meets invoke of this method in code, * then it assumes that the specified object is parameter, * and need to be marked as parameter. * As address space of parameters in engine is non-positive, while @@ -63,7 +63,7 @@ public static void parameter(Object[] objects) { } /** - * If {@link org.utbot.engine.UtBotSymbolicEngine} meets invoke of this method in code, + * If {@link org.utbot.engine.Traverser} meets invoke of this method in code, * then it starts concrete execution from this point. */ public static void executeConcretely() { diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java index 5f2a72065d..e7990f1d6a 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java @@ -28,7 +28,7 @@ /** - * Class represents hybrid implementation (java + engine instructions) of List interface for {@link org.utbot.engine.UtBotSymbolicEngine}. + * Class represents hybrid implementation (java + engine instructions) of List interface for {@link org.utbot.engine.Traverser}. *

* Implementation is based on org.utbot.engine.overrides.collections.RangeModifiableArray. * Should behave similar to {@link java.util.ArrayList}. diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java index 414531e759..f3a9b22405 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java @@ -23,7 +23,7 @@ /** - * Class represents hybrid implementation (java + engine instructions) of Map interface for {@link org.utbot.engine.UtBotSymbolicEngine}. + * Class represents hybrid implementation (java + engine instructions) of Map interface for {@link org.utbot.engine.Traverser}. *

* Implementation is based on using org.utbot.engine.overrides.collections.RangeModifiableArray as keySet * and org.utbot.engine.overrides.collections.UtArray as associative array from keys to values. diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java index b854a2ac7f..6daf394505 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java @@ -20,7 +20,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of Set interface for {@link org.utbot.engine.UtBotSymbolicEngine}. + * Class represents hybrid implementation (java + engine instructions) of Set interface for {@link org.utbot.engine.Traverser}. *

* Implementation is based on RangedModifiableArray, and all operations are linear. * Should behave similar to diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java index fd1c28df5e..35d8106070 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java @@ -13,7 +13,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of Optional for {@link org.utbot.engine.UtBotSymbolicEngine}. + * Class represents hybrid implementation (java + engine instructions) of Optional for {@link org.utbot.engine.Traverser}. *

* Should behave the same as {@link java.util.Optional}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java index 9635a2aa8c..99b2d4d6c5 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java @@ -11,7 +11,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of OptionalDouble for {@link org.utbot.engine.UtBotSymbolicEngine}. + * Class represents hybrid implementation (java + engine instructions) of OptionalDouble for {@link org.utbot.engine.Traverser}. *

* Should behave the same as {@link java.util.OptionalDouble}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java index b31b4d06bb..d88c65c0ad 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java @@ -11,7 +11,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of OptionalInt for {@link org.utbot.engine.UtBotSymbolicEngine}. + * Class represents hybrid implementation (java + engine instructions) of OptionalInt for {@link org.utbot.engine.Traverser}. *

* Should behave the same as {@link java.util.OptionalInt}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java index e2bd74c048..34d3282907 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java @@ -10,7 +10,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of Optional for {@link org.utbot.engine.UtBotSymbolicEngine}. + * Class represents hybrid implementation (java + engine instructions) of Optional for {@link org.utbot.engine.Traverser}. *

* Should behave the same as {@link java.util.Optional}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt index 5c53d0c751..c5f3cde9e9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt @@ -37,7 +37,7 @@ import soot.SootMethod val rangeModifiableArrayId: ClassId = RangeModifiableUnlimitedArray::class.id class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { - override fun UtBotSymbolicEngine.invoke( + override fun Traverser.invoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -203,10 +203,10 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { } } - private fun UtBotSymbolicEngine.getStorageArrayField(addr: UtAddrExpression) = + private fun Traverser.getStorageArrayField(addr: UtAddrExpression) = getArrayField(addr, rangeModifiableArrayClass, storageField) - private fun UtBotSymbolicEngine.getStorageArrayExpression( + private fun Traverser.getStorageArrayExpression( wrapper: ObjectValue ): UtExpression = selectArrayExpressionFromMemory(getStorageArrayField(wrapper.addr)) @@ -285,7 +285,7 @@ class AssociativeArrayWrapper : WrapperInterface { private val storageField = associativeArrayClass.getField("java.lang.Object[] storage") - override fun UtBotSymbolicEngine.invoke( + override fun Traverser.invoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -414,16 +414,16 @@ class AssociativeArrayWrapper : WrapperInterface { return model } - private fun UtBotSymbolicEngine.getStorageArrayField(addr: UtAddrExpression) = + private fun Traverser.getStorageArrayField(addr: UtAddrExpression) = getArrayField(addr, associativeArrayClass, storageField) - private fun UtBotSymbolicEngine.getTouchedArrayField(addr: UtAddrExpression) = + private fun Traverser.getTouchedArrayField(addr: UtAddrExpression) = getArrayField(addr, associativeArrayClass, touchedField) - private fun UtBotSymbolicEngine.getTouchedArrayExpression(wrapper: ObjectValue): UtExpression = + private fun Traverser.getTouchedArrayExpression(wrapper: ObjectValue): UtExpression = selectArrayExpressionFromMemory(getTouchedArrayField(wrapper.addr)) - private fun UtBotSymbolicEngine.getStorageArrayExpression( + private fun Traverser.getStorageArrayExpression( wrapper: ObjectValue ): UtExpression = selectArrayExpressionFromMemory(getStorageArrayField(wrapper.addr)) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt index 38aeb51459..bf7d3e0f5a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt @@ -50,7 +50,7 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) * * @see invoke */ - protected abstract fun UtBotSymbolicEngine.overrideInvoke( + protected abstract fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -65,11 +65,11 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) * * Multiple GraphResults are returned because, we shouldn't substitute invocation of specified * that was called inside substituted method of object with the same address as specified [wrapper]. - * (For example UtArrayList. invokes AbstractList. that also leads to [UtBotSymbolicEngine.invoke], + * (For example UtArrayList. invokes AbstractList. that also leads to [Traverser.invoke], * and shouldn't be substituted with UtArrayList. again). Only one GraphResult is valid, that is * guaranteed by contradictory to each other sets of constraints, added to them. */ - override fun UtBotSymbolicEngine.invoke( + override fun Traverser.invoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -162,7 +162,7 @@ abstract class BaseContainerWrapper(containerClassName: String) : BaseOverridden } abstract class BaseGenericStorageBasedContainerWrapper(containerClassName: String) : BaseContainerWrapper(containerClassName) { - override fun UtBotSymbolicEngine.overrideInvoke( + override fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -283,7 +283,7 @@ class SetWrapper : BaseGenericStorageBasedContainerWrapper(UtHashSet::class.qual * entries, then real behavior of generated test can differ from expected and undefined. */ class MapWrapper : BaseContainerWrapper(UtHashMap::class.qualifiedName!!) { - override fun UtBotSymbolicEngine.overrideInvoke( + override fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -418,14 +418,14 @@ val HASH_MAP_TYPE: RefType val STREAM_TYPE: RefType get() = Scene.v().getSootClass(java.util.stream.Stream::class.java.canonicalName).type -internal fun UtBotSymbolicEngine.getArrayField( +internal fun Traverser.getArrayField( addr: UtAddrExpression, wrapperClass: SootClass, field: SootField ): ArrayValue = createFieldOrMock(wrapperClass.type, addr, field, mockInfoGenerator = null) as ArrayValue -internal fun UtBotSymbolicEngine.getIntFieldValue(wrapper: ObjectValue, field: SootField): UtExpression { +internal fun Traverser.getIntFieldValue(wrapper: ObjectValue, field: SootField): UtExpression { val chunkId = hierarchy.chunkIdForField(field.declaringClass.type, field) val descriptor = MemoryChunkDescriptor(chunkId, field.declaringClass.type, IntType.v()) val array = memory.findArray(descriptor, MemoryState.CURRENT) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/DataClasses.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/DataClasses.kt index 860d1c7d71..bb7ce114ed 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/DataClasses.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/DataClasses.kt @@ -184,7 +184,7 @@ data class OverrideResult( * there is only one usage of it: to support instanceof for arrays we have to update them in the memory. * * @see UtInstanceOfExpression - * @see UtBotSymbolicEngine.resolveIfCondition + * @see Traverser.resolveIfCondition */ data class ResolvedCondition( val condition: UtBoolExpression, diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt index d94d51b1b6..02a6438230 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt @@ -201,7 +201,7 @@ val Type.numDimensions get() = if (this is ArrayType) numDimensions else 0 /** * Invocation. Can generate multiple targets. * - * @see UtBotSymbolicEngine.virtualAndInterfaceInvoke + * @see Traverser.virtualAndInterfaceInvoke */ data class Invocation( val instance: ReferenceValue?, @@ -220,7 +220,7 @@ data class Invocation( /** * Invocation target. Contains constraints to be satisfied for this instance class (related to virtual invoke). * - * @see UtBotSymbolicEngine.virtualAndInterfaceInvoke + * @see Traverser.virtualAndInterfaceInvoke */ data class InvocationTarget( val instance: ReferenceValue?, @@ -243,11 +243,11 @@ data class MethodInvocationTarget( ) /** - * Used in the [UtBotSymbolicEngine.findLibraryTargets] to substitute common types + * Used in the [Traverser.findLibraryTargets] to substitute common types * like [Iterable] with the types that have corresponding wrappers. * - * @see UtBotSymbolicEngine.findLibraryTargets - * @see UtBotSymbolicEngine.findInvocationTargets + * @see Traverser.findLibraryTargets + * @see Traverser.findInvocationTargets */ val libraryTargets: Map> = mapOf( Iterable::class.java.name to listOf(ArrayList::class.java.name, HashSet::class.java.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 edf693cc8c..0659b1df82 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -242,7 +242,7 @@ data class Memory( // TODO: split purely symbolic memory and information about s * sometimes we want to change initial memory states of fields of a certain class, so we erase all the * information about previous states and update it with the current state. For now, this processing only takes * place after receiving MethodResult from [STATIC_INITIALIZER] method call at the end of - * [UtBotSymbolicEngine.processStaticInitializer]. The value of `update.classIdToClearStatics` is equal to the + * [Traverser.processStaticInitializer]. The value of `update.classIdToClearStatics` is equal to the * class for which the static initialization has performed. * TODO: JIRA:1610 -- refactor working with statics later */ diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt index 1dd8526d5d..52e3d98037 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt @@ -46,7 +46,7 @@ class UtMockInfoGenerator(private val generator: (UtAddrExpression) -> UtMockInf * Contains mock class id and mock address to work with object cache. * * Note: addr for static method mocks contains addr of the "host" object - * received by [UtBotSymbolicEngine.locateStaticObject]. + * received by [Traverser.locateStaticObject]. * * @property classId classId of the object this mock represents. * @property addr address of the mock object. @@ -90,9 +90,9 @@ data class UtObjectMockInfo( /** * Mock for the "host" object for static methods and fields with [classId] declaringClass. - * [addr] is a value received by [UtBotSymbolicEngine.locateStaticObject]. + * [addr] is a value received by [Traverser.locateStaticObject]. * - * @see UtBotSymbolicEngine.locateStaticObject + * @see Traverser.locateStaticObject */ data class UtStaticObjectMockInfo( override val classId: ClassId, @@ -115,12 +115,12 @@ data class UtNewInstanceMockInfo( * Represents mocks for static methods. * Contains the methodId. * - * Used only in [UtBotSymbolicEngine.mockStaticMethod] method to pass information into [Mocker] about the method. + * Used only in [Traverser.mockStaticMethod] method to pass information into [Mocker] about the method. * All the executables will be stored in executables of the object with [UtStaticObjectMockInfo] and the same addr. * * Note: we use non null addr here because of [createMockObject] method. We have to know address of the object * that we want to make. Although static method doesn't have "caller", we still can use address of the object - * received by [UtBotSymbolicEngine.locateStaticObject]. + * received by [Traverser.locateStaticObject]. */ data class UtStaticMethodMockInfo( override val addr: UtAddrExpression, @@ -265,7 +265,7 @@ class UtMockWrapper( private val mockInfo: UtMockInfo ) : WrapperInterface { - override fun UtBotSymbolicEngine.invoke( + override fun Traverser.invoke( wrapper: ObjectValue, method: SootMethod, parameters: List 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 3a162ed092..2d318242a1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt @@ -204,7 +204,7 @@ interface WrapperInterface { /** * Returns list of invocation results */ - operator fun UtBotSymbolicEngine.invoke( + operator fun Traverser.invoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -215,7 +215,7 @@ interface WrapperInterface { // TODO: perhaps we have to have wrapper around concrete value here data class ThrowableWrapper(val throwable: Throwable) : WrapperInterface { - override fun UtBotSymbolicEngine.invoke(wrapper: ObjectValue, method: SootMethod, parameters: List) = + override fun Traverser.invoke(wrapper: ObjectValue, method: SootMethod, parameters: List) = workaround(MAKE_SYMBOLIC) { listOf( MethodResult( diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt index 19348fc6f9..f3b889dbd8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt @@ -58,7 +58,7 @@ class OptionalWrapper(private val utOptionalClass: UtOptionalClass) : BaseOverri private val AS_OPTIONAL_METHOD_SIGNATURE = overriddenClass.getMethodByName(UtOptional<*>::asOptional.name).signature - override fun UtBotSymbolicEngine.overrideInvoke( + override fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt index 4fca4b478d..b443bb0628 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -683,7 +683,7 @@ class Resolver( * the method returns null as the result. * * @see Memory.touchedAddresses - * @see UtBotSymbolicEngine.touchAddress + * @see Traverser.touchAddress */ private fun UtSolverStatusSAT.constructTypeOrNull(addr: UtAddrExpression, defaultType: Type): Type? { return constructTypeOrNull(addr, defaultType, isTouched(addr)) @@ -1032,7 +1032,7 @@ val typesOfObjectsToRecreate = listOf( * * we have to determine, which null values must be constructed; * * we must distinguish primitives and wrappers, but because of kotlin types we cannot do it without the [sootType]; */ -fun UtBotSymbolicEngine.toMethodResult(value: Any?, sootType: Type): MethodResult { +fun Traverser.toMethodResult(value: Any?, sootType: Type): MethodResult { if (sootType is PrimType) return MethodResult(value.primitiveToSymbolic()) return when (value) { @@ -1107,7 +1107,7 @@ fun UtBotSymbolicEngine.toMethodResult(value: Any?, sootType: Type): MethodResul } } -private fun UtBotSymbolicEngine.arrayToMethodResult( +private fun Traverser.arrayToMethodResult( size: Int, elementType: Type, takeElement: (Int) -> UtExpression @@ -1143,7 +1143,7 @@ private fun UtBotSymbolicEngine.arrayToMethodResult( ) } -fun UtBotSymbolicEngine.constructEnumStaticFieldResult( +fun Traverser.constructEnumStaticFieldResult( fieldName: String, fieldType: Type, declaringClass: SootClass, diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt index 72695c927f..1d14c7294c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt @@ -64,10 +64,10 @@ class StringWrapper : BaseOverriddenWrapper(utStringClass.name) { private val charAtMethodSignature = overriddenClass.getMethodByName(UtString::charAtImpl.name).subSignature - private fun UtBotSymbolicEngine.getValueArray(addr: UtAddrExpression) = + private fun Traverser.getValueArray(addr: UtAddrExpression) = getArrayField(addr, overriddenClass, STRING_VALUE) - override fun UtBotSymbolicEngine.overrideInvoke( + override fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -200,7 +200,7 @@ private fun nextStringName() = "\$string${stringNameIndex++}" class UtNativeStringWrapper : WrapperInterface { private val valueDescriptor = NATIVE_STRING_VALUE_DESCRIPTOR - override fun UtBotSymbolicEngine.invoke( + override fun Traverser.invoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -292,7 +292,7 @@ sealed class UtAbstractStringBuilderWrapper(className: String) : BaseOverriddenW private val asStringBuilderMethodSignature = overriddenClass.getMethodByName("asStringBuilder").subSignature - override fun UtBotSymbolicEngine.overrideInvoke( + override fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/SymbolicValue.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/SymbolicValue.kt index a8713bae73..808ea96eee 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/SymbolicValue.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/SymbolicValue.kt @@ -88,7 +88,7 @@ sealed class ReferenceValue(open val addr: UtAddrExpression) : SymbolicValue() * otherwise it is possible for an object to have inappropriate or incorrect typeId and dimensionNum. * * @see TypeRegistry.typeConstraint - * @see UtBotSymbolicEngine.createObject + * @see Traverser.createObject */ data class ObjectValue( override val typeStorage: TypeStorage, @@ -127,7 +127,7 @@ data class ObjectValue( * otherwise it is possible for an object to have inappropriate or incorrect typeId and dimensionNum. * * @see TypeRegistry.typeConstraint - * @see UtBotSymbolicEngine.createObject + * @see Traverser.createObject */ data class ArrayValue( override val typeStorage: TypeStorage, diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt similarity index 99% rename from utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt rename to utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 3a09753057..9086a630e0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -313,7 +313,7 @@ private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegi } } -class UtBotSymbolicEngine( +class Traverser( private val controller: EngineController, private val methodUnderTest: UtMethod<*>, private val graph: ExceptionalUnitGraph, @@ -3377,7 +3377,7 @@ class UtBotSymbolicEngine( * different object addresses in such case * - We do not compare null addresses here, it happens in resolveIfCondition * - * @see UtBotSymbolicEngine.resolveIfCondition + * @see Traverser.resolveIfCondition */ private fun compareReferenceValues( lhs: ReferenceValue, @@ -3877,7 +3877,7 @@ class UtBotSymbolicEngine( return SymbolicSuccess(value) } - internal fun asMethodResult(function: UtBotSymbolicEngine.() -> SymbolicValue): MethodResult { + internal fun asMethodResult(function: Traverser.() -> SymbolicValue): MethodResult { val prevSymbolicStateUpdate = queuedSymbolicStateUpdates.copy() // TODO: refactor this `try` with `finally` later queuedSymbolicStateUpdates = SymbolicStateUpdate() diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpression.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpression.kt index 4d4cefd8b2..94e3944172 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpression.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpression.kt @@ -243,7 +243,7 @@ data class UtArraySelectExpression(val arrayExpression: UtExpression, val index: } /** - * Uses in [org.utbot.engine.UtBotSymbolicEngine.classCastExceptionCheck]. + * Uses in [org.utbot.engine.Traverser.classCastExceptionCheck]. * Returns the most nested index in the [UtArraySelectExpression]. * * I.e. for (select a i) it returns i, for (select (select (select a i) j) k) it still returns i diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt index 84deca8af5..f1d547628c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt @@ -20,7 +20,7 @@ import soot.jimple.StaticFieldRef import soot.jimple.Stmt import soot.jimple.internal.JAssignStmt -fun UtBotSymbolicEngine.makeSymbolicValuesFromEnumConcreteValues( +fun Traverser.makeSymbolicValuesFromEnumConcreteValues( type: Type, enumConstantRuntimeValues: List> ): Pair, Map> { @@ -62,7 +62,7 @@ fun associateEnumSootFieldsWithConcreteValues( /** * Construct symbolic updates for enum static fields and a symbolic value for a local in the left part of the assignment. */ -fun UtBotSymbolicEngine.makeEnumStaticFieldsUpdates( +fun Traverser.makeEnumStaticFieldsUpdates( staticFields: List>>, declaringClass: SootClass, enumConstantSymbolicResultsByName: Map, @@ -109,7 +109,7 @@ fun UtBotSymbolicEngine.makeEnumStaticFieldsUpdates( return staticFieldsUpdates to symbolicValueForLocal } -fun UtBotSymbolicEngine.makeEnumNonStaticFieldsUpdates( +fun Traverser.makeEnumNonStaticFieldsUpdates( enumConstantSymbolicValues: List, nonStaticFields: List>> ): SymbolicStateUpdate { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt index b2d4f8f45f..6a406b02d8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt @@ -8,7 +8,7 @@ import org.utbot.common.trace import org.utbot.engine.EngineController import org.utbot.engine.MockStrategy import org.utbot.engine.Mocker -import org.utbot.engine.UtBotSymbolicEngine +import org.utbot.engine.Traverser import org.utbot.engine.jimpleBody import org.utbot.engine.pureJavaSignature import org.utbot.framework.TestSelectionStrategyType @@ -199,12 +199,12 @@ object UtBotTestCaseGenerator : TestCaseGenerator { mockStrategy: MockStrategyApi, chosenClassesToMockAlways: Set, executionTimeEstimator: ExecutionTimeEstimator - ): UtBotSymbolicEngine { + ): Traverser { // TODO: create classLoader from buildDir/classpath and migrate from UtMethod to MethodId? logger.debug("Starting symbolic execution for $method --$mockStrategy--") val graph = graph(method) - return UtBotSymbolicEngine( + return Traverser( controller, method, graph, @@ -216,7 +216,7 @@ object UtBotTestCaseGenerator : TestCaseGenerator { ) } - private fun createDefaultFlow(engine: UtBotSymbolicEngine): Flow { + private fun createDefaultFlow(engine: Traverser): Flow { var flow = engine.traverse() if (UtSettings.useFuzzing) { flow = flowOf( @@ -265,7 +265,7 @@ object UtBotTestCaseGenerator : TestCaseGenerator { mockStrategy: MockStrategyApi, chosenClassesToMockAlways: Set = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id }, methodsGenerationTimeout: Long = utBotGenerationTimeoutInMillis, - generate: (engine: UtBotSymbolicEngine) -> Flow = ::createDefaultFlow + generate: (engine: Traverser) -> Flow = ::createDefaultFlow ): List { if (isCanceled()) return methods.map { UtTestCase(it) } diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/ExceptionExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/ExceptionExamplesTest.kt index 7ff4b3c412..ad43e4c0bb 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/ExceptionExamplesTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/ExceptionExamplesTest.kt @@ -93,7 +93,7 @@ internal class ExceptionExamplesTest : AbstractTestCaseGeneratorTest( } /** - * Used for path generation check in [org.utbot.engine.UtBotSymbolicEngine.fullPath] + * Used for path generation check in [org.utbot.engine.Traverser.fullPath] */ @Test fun testCatchDeepNestedThrow() { @@ -107,7 +107,7 @@ internal class ExceptionExamplesTest : AbstractTestCaseGeneratorTest( } /** - * Used for path generation check in [org.utbot.engine.UtBotSymbolicEngine.fullPath] + * Used for path generation check in [org.utbot.engine.Traverser.fullPath] */ @Test fun testDontCatchDeepNestedThrow() { diff --git a/utbot-framework/src/test/resources/log4j2.xml b/utbot-framework/src/test/resources/log4j2.xml index 11a2d0701c..c3330409ac 100644 --- a/utbot-framework/src/test/resources/log4j2.xml +++ b/utbot-framework/src/test/resources/log4j2.xml @@ -17,7 +17,7 @@ - + diff --git a/utbot-junit-contest/src/main/resources/log4j2.xml b/utbot-junit-contest/src/main/resources/log4j2.xml index fce466c24d..98ea5cf5d2 100644 --- a/utbot-junit-contest/src/main/resources/log4j2.xml +++ b/utbot-junit-contest/src/main/resources/log4j2.xml @@ -19,7 +19,7 @@ - + From 58ae325f74e9a82b50134fd377c52d482a7cca32 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 12:15:42 +0300 Subject: [PATCH 03/19] Introduce StateLabel --- .../kotlin/org/utbot/engine/ExecutionState.kt | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt index 9c831771c0..4ea96f5b99 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt @@ -23,6 +23,24 @@ const val CALL_DECISION_NUM = -2 data class Edge(val src: Stmt, val dst: Stmt, val decisionNum: Int) +/** + * Possible state types. Engine matches on them and processes differently. + * + * [INTERMEDIATE] is a label for an intermediate state which is suitable for further symbolic analysis. + * + * [TERMINAL] is a label for a terminal state from which we might (or might not) execute concretely and construct + * UtExecution. This state represents the final state of the program execution, that is a throw or return from the outer + * method. + * + * [CONCRETE] is a label for a state which is not suitable for further symbolic analysis and it is also not a terminal + * state. Such states are only suitable for a concrete execution and may appear from Assumptions constraints + */ +enum class StateLabel { + INTERMEDIATE, + TERMINAL, + CONCRETE +} + /** * The stack element of the [ExecutionState]. * Contains properties, that are suitable for specified method in call stack. @@ -112,6 +130,7 @@ data class ExecutionState( val lastMethod: SootMethod? = null, val methodResult: MethodResult? = null, val exception: SymbolicFailure? = null, + val label: StateLabel = StateLabel.INTERMEDIATE, private var stateAnalyticsProperties: StateAnalyticsProperties = StateAnalyticsProperties() ) : AutoCloseable { val solver: UtSolver by symbolicState::solver @@ -161,6 +180,7 @@ data class ExecutionState( lastEdge = edge, lastMethod = executionStack.last().method, exception = exception, + label = label, stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) ) } @@ -187,7 +207,8 @@ data class ExecutionState( pathLength = pathLength + 1, lastEdge = edge, lastMethod = executionStack.last().method, - methodResult, + methodResult = methodResult, + label = label, stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) ) } @@ -226,6 +247,7 @@ data class ExecutionState( pathLength = pathLength + 1, lastEdge = edge, lastMethod = stackElement.method, + label = label, stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) ) } @@ -271,6 +293,7 @@ data class ExecutionState( pathLength = pathLength + 1, lastEdge = edge, lastMethod = stackElement.method, + label = label, stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) ) } @@ -284,6 +307,8 @@ data class ExecutionState( solver.expectUndefined = true } + fun withLabel(newLabel: StateLabel) = copy(label = newLabel) + override fun close() { solver.close() } From f1f2a733be6770742f708f0b7a38e097067293ff Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 12:21:48 +0300 Subject: [PATCH 04/19] Move function from TypeRegistry to Traverser --- .../src/main/kotlin/org/utbot/engine/Memory.kt | 11 ----------- .../src/main/kotlin/org/utbot/engine/Traverser.kt | 14 +++++++++++++- 2 files changed, 13 insertions(+), 12 deletions(-) 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 0659b1df82..549d02f16b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -539,17 +539,6 @@ class TypeRegistry { finalCost } - private val objectCounter = AtomicInteger(objectCounterInitialValue) - fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression { - val newAddr = objectCounter.getAndIncrement() - // return negative address for objects created inside static initializer - // to make their address space be intersected with address space of - // parameters of method under symbolic execution - // @see ObjectWithFinalStaticTest::testParameterEqualsFinalStatic - val signedAddr = if (insideStaticInitializer) -newAddr else newAddr - return UtAddrExpression(signedAddr) - } - private val classRefCounter = AtomicInteger(classRefAddrsInitialValue) private fun nextClassRefAddr() = UtAddrExpression(classRefCounter.getAndIncrement()) 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 9086a630e0..4846c70228 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -265,6 +265,7 @@ import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl import sun.reflect.generics.reflectiveObjects.TypeVariableImpl import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl import java.lang.reflect.Method +import java.util.concurrent.atomic.AtomicInteger private val logger = KotlinLogging.logger {} val pathLogger = KotlinLogging.logger(logger.name + ".path") @@ -387,7 +388,18 @@ class Traverser( private val insideStaticInitializer get() = environment.state.executionStack.any { it.method.isStaticInitializer } - internal fun findNewAddr() = typeRegistry.findNewAddr(insideStaticInitializer).also { touchAddress(it) } + + private val objectCounter = AtomicInteger(TypeRegistry.objectCounterInitialValue) + private fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression { + val newAddr = objectCounter.getAndIncrement() + // return negative address for objects created inside static initializer + // to make their address space be intersected with address space of + // parameters of method under symbolic execution + // @see ObjectWithFinalStaticTest::testParameterEqualsFinalStatic + val signedAddr = if (insideStaticInitializer) -newAddr else newAddr + return UtAddrExpression(signedAddr) + } + internal fun findNewAddr() = findNewAddr(insideStaticInitializer).also { touchAddress(it) } // Counter used for a creation symbolic results of "hashcode" and "equals" methods. private var equalsCounter = 0 From 8dce0485178285e3416d16902df8a83891897c4e Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 12:36:44 +0300 Subject: [PATCH 05/19] Remove strange hack on nullable SymbolicResult for only void return --- .../src/main/kotlin/org/utbot/engine/Resolver.kt | 3 +-- .../src/main/kotlin/org/utbot/engine/Traverser.kt | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt index b443bb0628..93a1eba650 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -346,10 +346,9 @@ class Resolver( /** * Resolves current result (return value). */ - fun resolveResult(symResult: SymbolicResult?): UtExecutionResult = + fun resolveResult(symResult: SymbolicResult): UtExecutionResult = withMemoryState(CURRENT) { when (symResult) { - null -> UtExecutionSuccess(UtVoidModel) is SymbolicSuccess -> { collectMocksAndInstrumentation() val model = resolveModel(symResult.value) 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 4846c70228..6e1961b9c2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -728,7 +728,7 @@ class Traverser( is JInvokeStmt -> traverseInvokeStmt(current) is SwitchStmt -> traverseSwitchStmt(current) is JReturnStmt -> processResult(current.symbolicSuccess()) - is JReturnVoidStmt -> processResult(null) + is JReturnVoidStmt -> processResult(SymbolicSuccess(voidValue)) is JRetStmt -> error("This one should be already removed by Soot: $current") is JThrowStmt -> traverseThrowStmt(current) is JBreakpointStmt -> pathSelector.offer(environment.state.updateQueued(globalGraph.succ(current))) @@ -3737,7 +3737,7 @@ class Traverser( ) } - private suspend fun FlowCollector.processResult(symbolicResult: SymbolicResult? /* null for void only: strange hack */) { + private suspend fun FlowCollector.processResult(symbolicResult: SymbolicResult) { val resolvedParameters = environment.state.parameters.map { it.value } //choose types that have biggest priority @@ -3791,9 +3791,8 @@ class Traverser( } else { MemoryUpdate() // all memory updates are already added in [environment.state] } - val symbolicResultOrVoid = symbolicResult ?: SymbolicSuccess(typeResolver.nullObject(VoidType.v())) val methodResult = MethodResult( - symbolicResultOrVoid, + symbolicResult, queuedSymbolicStateUpdates + updates ) val stateToOffer = environment.state.pop(methodResult) From cd2e1ee1b2d5768369486af1282d789560d50f3d Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 13:00:02 +0300 Subject: [PATCH 06/19] Refactor resolve function: turn receiver parameter into argument --- .../main/kotlin/org/utbot/engine/Traverser.kt | 244 +++++++++--------- 1 file changed, 124 insertions(+), 120 deletions(-) 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 6e1961b9c2..afaf6126f7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -804,7 +804,7 @@ class Traverser( if (right !is JNewMultiArrayExpr) return false val graph = unfoldMultiArrayExpr(stmt) - val resolvedSizes = right.sizes.map { (it.resolve(IntType.v()) as PrimitiveValue).align() } + val resolvedSizes = right.sizes.map { (resolve(it, IntType.v()) as PrimitiveValue).align() } negativeArraySizeCheck(*resolvedSizes.toTypedArray()) @@ -1112,7 +1112,7 @@ class Traverser( val rightPartWrappedAsMethodResults = if (rightValue is InvokeExpr) { invokeResult(rightValue) } else { - val value = rightValue.resolve(current.leftOp.type) + val value = resolve(rightValue, current.leftOp.type) listOf(MethodResult(value)) } @@ -1180,11 +1180,11 @@ class Traverser( */ private fun traverseAssignLeftPart(left: Value, value: SymbolicValue): SymbolicStateUpdate = when (left) { is ArrayRef -> { - val arrayInstance = left.base.resolve() as ArrayValue + val arrayInstance = resolve(left.base) as ArrayValue val addr = arrayInstance.addr nullPointerExceptionCheck(addr) - val index = (left.index.resolve() as PrimitiveValue).align() + val index = (resolve(left.index) as PrimitiveValue).align() val length = memory.findArrayLength(addr) indexOutOfBoundsChecks(index, length) @@ -1227,7 +1227,7 @@ class Traverser( // This hack solves the problem with static final fields, which are equal by reference with parameter workaround(HACK) { if (left.field.isFinal) { - addConstraintsForFinalAssign(left.resolve(), value) + addConstraintsForFinalAssign(resolve(left), value) } } @@ -1255,8 +1255,8 @@ class Traverser( is JInstanceFieldRef -> { // Runs resolve() to check possible NPE and create required arrays related to the field. // Ignores the result of resolve(). - fieldRef.resolve() - val baseObject = fieldRef.base.resolve() as ObjectValue + resolve(fieldRef) + val baseObject = resolve(fieldRef.base) as ObjectValue val typeStorage = TypeStorage(fieldRef.field.declaringClass.type) baseObject.copy(typeStorage = typeStorage) } @@ -1531,7 +1531,7 @@ class Traverser( } private fun traverseSwitchStmt(current: SwitchStmt) { - val valueExpr = current.key.resolve() as PrimitiveValue + val valueExpr = resolve(current.key) as PrimitiveValue val successors = when (current) { is JTableSwitchStmt -> { val indexed = (current.lowIndex..current.highIndex).mapIndexed { i, index -> @@ -1568,7 +1568,7 @@ class Traverser( } private suspend fun FlowCollector.traverseThrowStmt(current: JThrowStmt) { - val symException = explicitThrown(current.op.resolve(), isInNestedMethod()) + val symException = explicitThrown(resolve(current.op), isInNestedMethod()) traverseException(current, symException) } @@ -1647,18 +1647,18 @@ class Traverser( return ObjectValue(typeStorage, addr, concreteImplementation) } - private fun Constant.resolve(): SymbolicValue = - when (this) { - is IntConstant -> this.value.toPrimitiveValue() - is LongConstant -> this.value.toPrimitiveValue() - is FloatConstant -> this.value.toPrimitiveValue() - is DoubleConstant -> this.value.toPrimitiveValue() + private fun resolveConstant(constant: Constant): SymbolicValue = + when (constant) { + is IntConstant -> constant.value.toPrimitiveValue() + is LongConstant -> constant.value.toPrimitiveValue() + is FloatConstant -> constant.value.toPrimitiveValue() + is DoubleConstant -> constant.value.toPrimitiveValue() is StringConstant -> { val addr = findNewAddr() - val refType = this.type as RefType + val refType = constant.type as RefType // We disable creation of string literals to avoid unsats because of too long lines - if (UtSettings.ignoreStringLiterals && value.length > MAX_STRING_SIZE) { + if (UtSettings.ignoreStringLiterals && constant.value.length > MAX_STRING_SIZE) { // instead of it we create an unbounded symbolic variable workaround(HACK) { statesForConcreteExecution += environment.state @@ -1668,109 +1668,110 @@ class Traverser( queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, TypeStorage(refType)).all().asHardConstraint() objectValue(refType, addr, StringWrapper()).also { - initStringLiteral(it, this.value) + initStringLiteral(it, constant.value) } } } is ClassConstant -> { - val sootType = toSootType() + val sootType = constant.toSootType() val result = if (sootType is RefLikeType) { typeRegistry.createClassRef(sootType.baseType, sootType.numDimensions) } else { - error("Can't get class constant for $value") + error("Can't get class constant for ${constant.value}") } queuedSymbolicStateUpdates += result.symbolicStateUpdate (result.symbolicResult as SymbolicSuccess).value } - else -> error("Unsupported type: $this") + else -> error("Unsupported type: $constant") } - private fun Expr.resolve(valueType: Type = this.type): SymbolicValue = when (this) { - is BinopExpr -> { - val left = this.op1.resolve() - val right = this.op2.resolve() - when { - left is ReferenceValue && right is ReferenceValue -> { - when (this) { - is JEqExpr -> addrEq(left.addr, right.addr).toBoolValue() - is JNeExpr -> mkNot(addrEq(left.addr, right.addr)).toBoolValue() - else -> TODO("Unknown op $this for $left and $right") - } - } - left is PrimitiveValue && right is PrimitiveValue -> { - // division by zero special case - if ((this is JDivExpr || this is JRemExpr) && left.expr.isInteger() && right.expr.isInteger()) { - divisionByZeroCheck(right) + private fun resolve(expr: Expr, valueType: Type = expr.type): SymbolicValue = + when (expr) { + is BinopExpr -> { + val left = resolve(expr.op1) + val right = resolve(expr.op2) + when { + left is ReferenceValue && right is ReferenceValue -> { + when (expr) { + is JEqExpr -> addrEq(left.addr, right.addr).toBoolValue() + is JNeExpr -> mkNot(addrEq(left.addr, right.addr)).toBoolValue() + else -> TODO("Unknown op $expr for $left and $right") + } } + left is PrimitiveValue && right is PrimitiveValue -> { + // division by zero special case + if ((expr is JDivExpr || expr is JRemExpr) && left.expr.isInteger() && right.expr.isInteger()) { + divisionByZeroCheck(right) + } - if (UtSettings.treatOverflowAsError) { - // overflow detection - if (left.expr.isInteger() && right.expr.isInteger()) { - intOverflowCheck(this, left, right) + if (UtSettings.treatOverflowAsError) { + // overflow detection + if (left.expr.isInteger() && right.expr.isInteger()) { + intOverflowCheck(expr, left, right) + } } - } - doOperation(this, left, right).toPrimitiveValue(this.type) + doOperation(expr, left, right).toPrimitiveValue(expr.type) + } + else -> TODO("Unknown op $expr for $left and $right") } - else -> TODO("Unknown op $this for $left and $right") } - } - is JNegExpr -> UtNegExpression(op.resolve() as PrimitiveValue).toPrimitiveValue(this.type) - is JNewExpr -> { - val addr = findNewAddr() - val generator = UtMockInfoGenerator { mockAddr -> - UtNewInstanceMockInfo( - baseType.id, - mockAddr, - environment.method.declaringClass.id - ) + is JNegExpr -> UtNegExpression(resolve(expr.op) as PrimitiveValue).toPrimitiveValue(expr.type) + is JNewExpr -> { + val addr = findNewAddr() + val generator = UtMockInfoGenerator { mockAddr -> + UtNewInstanceMockInfo( + expr.baseType.id, + mockAddr, + environment.method.declaringClass.id + ) + } + val objectValue = createObject(addr, expr.baseType, useConcreteType = true, generator) + addConstraintsForDefaultValues(objectValue) + objectValue + } + is JNewArrayExpr -> { + val size = (resolve(expr.size) as PrimitiveValue).align() + val type = expr.type as ArrayType + createNewArray(size, type, type.elementType).also { + val defaultValue = type.defaultSymValue + queuedSymbolicStateUpdates += arrayUpdateWithValue(it.addr, type, defaultValue as UtArrayExpressionBase) + } + } + is JNewMultiArrayExpr -> { + val result = environment.state.methodResult + ?: error("There is no unfolded JNewMultiArrayExpr found in the methodResult") + queuedSymbolicStateUpdates += result.symbolicStateUpdate + (result.symbolicResult as SymbolicSuccess).value } - val objectValue = createObject(addr, baseType, useConcreteType = true, generator) - addConstraintsForDefaultValues(objectValue) - objectValue - } - is JNewArrayExpr -> { - val size = (this.size.resolve() as PrimitiveValue).align() - val type = this.type as ArrayType - createNewArray(size, type, type.elementType).also { - val defaultValue = type.defaultSymValue - queuedSymbolicStateUpdates += arrayUpdateWithValue(it.addr, type, defaultValue as UtArrayExpressionBase) - } - } - is JNewMultiArrayExpr -> { - val result = environment.state.methodResult - ?: error("There is no unfolded JNewMultiArrayExpr found in the methodResult") - queuedSymbolicStateUpdates += result.symbolicStateUpdate - (result.symbolicResult as SymbolicSuccess).value - } - is JLengthExpr -> { - val operand = op as? JimpleLocal ?: error("Unknown op: $op") - when (operand.type) { - is ArrayType -> { - val arrayInstance = localVariableMemory.local(operand.variable) as ArrayValue? - ?: error("$op not found in the locals") - nullPointerExceptionCheck(arrayInstance.addr) - memory.findArrayLength(arrayInstance.addr).also { length -> - queuedSymbolicStateUpdates += Ge(length, 0).asHardConstraint() + is JLengthExpr -> { + val operand = expr.op as? JimpleLocal ?: error("Unknown op: ${expr.op}") + when (operand.type) { + is ArrayType -> { + val arrayInstance = localVariableMemory.local(operand.variable) as ArrayValue? + ?: error("${expr.op} not found in the locals") + nullPointerExceptionCheck(arrayInstance.addr) + memory.findArrayLength(arrayInstance.addr).also { length -> + queuedSymbolicStateUpdates += Ge(length, 0).asHardConstraint() + } } + else -> error("Unknown op: ${expr.op}") } - else -> error("Unknown op: $op") } - } - is JCastExpr -> when (val value = op.resolve(valueType)) { - is PrimitiveValue -> value.cast(type) - is ObjectValue -> { - castObject(value, type, op) + is JCastExpr -> when (val value = resolve(expr.op, valueType)) { + is PrimitiveValue -> value.cast(expr.type) + is ObjectValue -> { + castObject(value, expr.type, expr.op) + } + is ArrayValue -> castArray(value, expr.type) } - is ArrayValue -> castArray(value, type) - } - is JInstanceOfExpr -> when (val value = op.resolve(valueType)) { - is PrimitiveValue -> error("Unexpected instanceof on primitive $value") - is ObjectValue -> objectInstanceOf(value, checkType, op) - is ArrayValue -> arrayInstanceOf(value, checkType) + is JInstanceOfExpr -> when (val value = resolve(expr.op, valueType)) { + is PrimitiveValue -> error("Unexpected instanceof on primitive $value") + is ObjectValue -> objectInstanceOf(value, expr.checkType, expr.op) + is ArrayValue -> arrayInstanceOf(value, expr.checkType) + } + else -> TODO("$expr") } - else -> TODO("$this") - } private fun initStringLiteral(stringWrapper: ObjectValue, value: String) { queuedSymbolicStateUpdates += objectUpdate( @@ -2050,39 +2051,42 @@ class Traverser( // Type is needed for null values: we should know, which null do we require. // If valueType is NullType, return typelessNullObject. It can happen in a situation, // where we cannot find the type, for example in condition (null == null) - private fun Value.resolve(valueType: Type = this.type): SymbolicValue = when (this) { - is JimpleLocal -> localVariableMemory.local(this.variable) ?: error("$name not found in the locals") - is Constant -> if (this is NullConstant) typeResolver.nullObject(valueType) else resolve() - is Expr -> resolve(valueType).simplify() + private fun resolve( + value: Value, + valueType: Type = value.type + ): SymbolicValue = when (value) { + is JimpleLocal -> localVariableMemory.local(value.variable) ?: error("${value.name} not found in the locals") + is Constant -> if (value is NullConstant) typeResolver.nullObject(valueType) else resolveConstant(value) + is Expr -> resolve(value, valueType).simplify() is JInstanceFieldRef -> { - val instance = (base.resolve() as ObjectValue) - recordInstanceFieldRead(instance.addr, field) + val instance = (resolve(value.base) as ObjectValue) + recordInstanceFieldRead(instance.addr, value.field) nullPointerExceptionCheck(instance.addr) val objectType = if (instance.concrete?.value is BaseOverriddenWrapper) { instance.concrete.value.overriddenClass.type } else { - field.declaringClass.type as RefType + value.field.declaringClass.type as RefType } - val generator = (field.type as? RefType)?.let { refType -> + val generator = (value.field.type as? RefType)?.let { refType -> UtMockInfoGenerator { mockAddr -> - val fieldId = FieldId(objectType.id, field.name) + val fieldId = FieldId(objectType.id, value.field.name) UtFieldMockInfo(refType.id, mockAddr, fieldId, instance.addr) } } - createFieldOrMock(objectType, instance.addr, field, generator).also { value -> + createFieldOrMock(objectType, instance.addr, value.field, generator).also { fieldValue -> preferredCexInstanceCache[instance]?.let { usedCache -> - if (usedCache.add(field)) { - applyPreferredConstraints(value) + if (usedCache.add(value.field)) { + applyPreferredConstraints(fieldValue) } } } } is JArrayRef -> { - val arrayInstance = base.resolve() as ArrayValue + val arrayInstance = resolve(value.base) as ArrayValue nullPointerExceptionCheck(arrayInstance.addr) - val index = (index.resolve() as PrimitiveValue).align() + val index = (resolve(value.index) as PrimitiveValue).align() val length = memory.findArrayLength(arrayInstance.addr) indexOutOfBoundsChecks(index, length) @@ -2117,8 +2121,8 @@ class Traverser( else -> PrimitiveValue(elementType, array.select(arrayInstance.addr, index.expr)) } } - is StaticFieldRef -> readStaticField(this) - else -> error("${this::class} is not supported") + is StaticFieldRef -> readStaticField(value) + else -> error("${value::class} is not supported") } private fun readStaticField(fieldRef: StaticFieldRef): SymbolicValue { @@ -2185,7 +2189,7 @@ class Traverser( } private fun resolveParameters(parameters: List, types: List) = - parameters.zip(types).map { (value, type) -> value.resolve(type) } + parameters.zip(types).map { (value, type) -> resolve(value, type) } private fun applyPreferredConstraints(value: SymbolicValue) { when (value) { @@ -2651,7 +2655,7 @@ class Traverser( methodRef: SootMethodRef, parameters: List ): List { - val instance = base.resolve() + val instance = resolve(base) if (instance !is ReferenceValue) error("We cannot run $methodRef on $instance") nullPointerExceptionCheck(instance.addr) @@ -2777,7 +2781,7 @@ class Traverser( } private fun specialInvoke(invokeExpr: JSpecialInvokeExpr): List { - val instance = invokeExpr.base.resolve() + val instance = resolve(invokeExpr.base) if (instance !is ReferenceValue) error("We cannot run ${invokeExpr.methodRef} on $instance") nullPointerExceptionCheck(instance.addr) @@ -3254,8 +3258,8 @@ class Traverser( // We add cond.op.type for null values only. If we have condition like "null == r1" // we'll have ObjectInstance(r1::type) and ObjectInstance(r1::type) for now // For non-null values type is ignored. - val lhs = cond.op1.resolve(cond.op2.type) - val rhs = cond.op2.resolve(cond.op1.type) + val lhs = resolve(cond.op1, cond.op2.type) + val rhs = resolve(cond.op2, cond.op1.type) return when { lhs.isNullObject() || rhs.isNullObject() -> { val eq = addrEq(lhs.addr, rhs.addr) @@ -3265,7 +3269,7 @@ class Traverser( ResolvedCondition(compareReferenceValues(lhs, rhs, cond is NeExpr)) } else -> { - val expr = cond.resolve().asPrimitiveValueOrError as UtBoolExpression + val expr = resolve(cond).asPrimitiveValueOrError as UtBoolExpression val memoryUpdates = collectSymbolicStateUpdates(expr) ResolvedCondition( expr, @@ -3348,8 +3352,8 @@ class Traverser( var positiveCaseConstraint: UtBoolExpression? = null var negativeCaseConstraint: UtBoolExpression? = null - val left = cond.op1.resolve(cond.op2.type) - val right = cond.op2.resolve(cond.op1.type) + val left = resolve(cond.op1, cond.op2.type) + val right = resolve(cond.op2, cond.op1.type) if (left !is PrimitiveValue || right !is PrimitiveValue) return SoftConstraintsForResolvedCondition() @@ -3881,7 +3885,7 @@ class Traverser( private fun ReturnStmt.symbolicSuccess(): SymbolicSuccess { val type = environment.method.returnType - val value = when (val instance = op.resolve(type)) { + val value = when (val instance = resolve(op, type)) { is PrimitiveValue -> instance.cast(type) else -> instance } From a42d75c2a686cf6ac5c977954c50b253d35b62f8 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 14:00:27 +0300 Subject: [PATCH 07/19] Remove isInNestedMethod function from engine --- .../src/main/kotlin/org/utbot/engine/Strings.kt | 2 +- .../main/kotlin/org/utbot/engine/Traverser.kt | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt index 1d14c7294c..cb0e1fba1d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt @@ -128,7 +128,7 @@ class StringWrapper : BaseOverriddenWrapper(utStringClass.name) { explicitThrown( StringIndexOutOfBoundsException(), findNewAddr(), - isInNestedMethod() + environment.state.isInNestedMethod() ), hardConstraints = mkNot(inBoundsCondition).asHardConstraint() ) 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 afaf6126f7..e325a470c2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -1356,7 +1356,7 @@ class Traverser( * Returns null if mock is not allowed - Engine traverses nested method call or parameter type is not RefType. */ private fun parameterMockInfoGenerator(parameterRef: IdentityRef): UtMockInfoGenerator? { - if (isInNestedMethod()) return null + if (environment.state.isInNestedMethod()) return null if (parameterRef !is ParameterRef) return null val type = parameterRef.type if (type !is RefType) return null @@ -1568,7 +1568,7 @@ class Traverser( } private suspend fun FlowCollector.traverseThrowStmt(current: JThrowStmt) { - val symException = explicitThrown(resolve(current.op), isInNestedMethod()) + val symException = explicitThrown(resolve(current.op), environment.state.isInNestedMethod()) traverseException(current, symException) } @@ -3628,7 +3628,7 @@ class Traverser( ) { if (environment.state.executionStack.last().doesntThrow) return - val symException = implicitThrown(exception, findNewAddr(), isInNestedMethod()) + val symException = implicitThrown(exception, findNewAddr(), environment.state.isInNestedMethod()) if (!traverseCatchBlock(environment.state.stmt, symException, conditions)) { environment.state.expectUndefined() val nextState = environment.state.createExceptionState( @@ -3755,7 +3755,9 @@ class Traverser( workaround(REMOVE_ANONYMOUS_CLASSES) { val sootClass = returnValue.type.sootClass - if (!isInNestedMethod() && (sootClass.isAnonymous || sootClass.isArtificialEntity)) return + if (!environment.state.isInNestedMethod() && (sootClass.isAnonymous || sootClass.isArtificialEntity)) { + return + } } } @@ -3776,7 +3778,7 @@ class Traverser( val updatedMemory = memory.update(queuedSymbolicStateUpdates.memoryUpdates) //no need to respect soft constraints in NestedMethod - val holder = newSolver.check(respectSoft = !isInNestedMethod()) + val holder = newSolver.check(respectSoft = !environment.state.isInNestedMethod()) if (holder !is UtSolverStatusSAT) { logger.trace { "processResult<${environment.method.signature}> UNSAT" } @@ -3784,7 +3786,7 @@ class Traverser( } //execution frame from level 2 or above - if (isInNestedMethod()) { + if (environment.state.isInNestedMethod()) { // static fields substitution // TODO: JIRA:1610 -- better way of working with statics val updates = if (environment.method.name == STATIC_INITIALIZER && substituteStaticsWithSymbolicVariable) { @@ -3881,8 +3883,6 @@ class Traverser( } } - internal fun isInNestedMethod() = environment.state.isInNestedMethod() - private fun ReturnStmt.symbolicSuccess(): SymbolicSuccess { val type = environment.method.returnType val value = when (val instance = resolve(op, type)) { From fbc0541be4ab302e88ed703d6d697a7c27925cb6 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 15:14:48 +0300 Subject: [PATCH 08/19] Split processResult --- .../kotlin/org/utbot/engine/ExecutionState.kt | 2 +- .../main/kotlin/org/utbot/engine/Traverser.kt | 50 ++++++++++++------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt index 4ea96f5b99..13690ee8d3 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt @@ -252,7 +252,7 @@ data class ExecutionState( ) } - fun updateMemory( + fun update( stateUpdate: SymbolicStateUpdate ): ExecutionState { val last = executionStack.last() 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 e325a470c2..8eb9454a6d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -1797,7 +1797,7 @@ class Traverser( } queuedSymbolicStateUpdates += arrayUpdateWithValue(arrayValue.addr, CharType.v().arrayType, newArray) - environment.state = environment.state.updateMemory(queuedSymbolicStateUpdates) + environment.state = environment.state.update(queuedSymbolicStateUpdates) queuedSymbolicStateUpdates = queuedSymbolicStateUpdates.copy(memoryUpdates = MemoryUpdate()) } @@ -3770,38 +3770,32 @@ class Traverser( queuedSymbolicStateUpdates += mkNot(mkEq(symbolicResult.value.addr, nullObjectAddr)).asHardConstraint() } - val newSolver = solver.add( - hard = queuedSymbolicStateUpdates.hardConstraints, - soft = queuedSymbolicStateUpdates.softConstraints - ) - - val updatedMemory = memory.update(queuedSymbolicStateUpdates.memoryUpdates) + val state = environment.state.update(queuedSymbolicStateUpdates) + val memory = state.memory + val solver = state.solver //no need to respect soft constraints in NestedMethod - val holder = newSolver.check(respectSoft = !environment.state.isInNestedMethod()) + val holder = solver.check(respectSoft = !state.isInNestedMethod()) if (holder !is UtSolverStatusSAT) { logger.trace { "processResult<${environment.method.signature}> UNSAT" } return } + val methodResult = MethodResult(symbolicResult) //execution frame from level 2 or above - if (environment.state.isInNestedMethod()) { + if (state.isInNestedMethod()) { // static fields substitution // TODO: JIRA:1610 -- better way of working with statics val updates = if (environment.method.name == STATIC_INITIALIZER && substituteStaticsWithSymbolicVariable) { substituteStaticFieldsWithSymbolicVariables( environment.method.declaringClass, - updatedMemory.queuedStaticMemoryUpdates() + memory.queuedStaticMemoryUpdates() ) } else { MemoryUpdate() // all memory updates are already added in [environment.state] } - val methodResult = MethodResult( - symbolicResult, - queuedSymbolicStateUpdates + updates - ) - val stateToOffer = environment.state.pop(methodResult) + val stateToOffer = state.pop(methodResult.copy(symbolicStateUpdate = updates.asUpdate())) pathSelector.offer(stateToOffer) logger.trace { "processResult<${environment.method.signature}> return from nested method" } @@ -3809,13 +3803,35 @@ class Traverser( } //toplevel method + val terminalExecutionState = + state.copy( + methodResult = methodResult, // a way to put SymbolicResult into terminal state + label = StateLabel.TERMINAL + ) + consumeTerminalState(terminalExecutionState) + } + + private suspend fun FlowCollector.consumeTerminalState( + state: ExecutionState, + ) { + // some checks to be sure the state is correct + require(state.label == StateLabel.TERMINAL) { "Can't process non-terminal state!" } + require(!state.isInNestedMethod()) { "The state has to correspond to the MUT"} + + val memory = state.memory + val solver = state.solver + val parameters = state.parameters.map { it.value } + val symbolicResult = requireNotNull(state.methodResult?.symbolicResult) { "The state must have symbolicResult"} + // it's free to make a check, because in the result is SAT, it should be already cached + val holder = requireNotNull(solver.check(respectSoft = true) as? UtSolverStatusSAT) { "The state must be SAT!" } + val predictedTestName = Predictors.testName.predict(environment.state.path) Predictors.testName.provide(environment.state.path, predictedTestName, "") val resolver = - Resolver(hierarchy, updatedMemory, typeRegistry, typeResolver, holder, methodUnderTest, softMaxArraySize) + Resolver(hierarchy, memory, typeRegistry, typeResolver, holder, methodUnderTest, softMaxArraySize) - val (modelsBefore, modelsAfter, instrumentation) = resolver.resolveModels(resolvedParameters) + val (modelsBefore, modelsAfter, instrumentation) = resolver.resolveModels(parameters) val symbolicExecutionResult = resolver.resolveResult(symbolicResult) From 259078b1a4a7f7e77a98b643c454bf46ea343596 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 15:46:31 +0300 Subject: [PATCH 09/19] Refactor: remove graph parameter from Traverser constructor --- .../main/kotlin/org/utbot/engine/Traverser.kt | 6 ++- .../plugin/api/UtBotTestCaseGenerator.kt | 39 +++++++------------ .../examples/AbstractTestCaseGeneratorTest.kt | 5 ++- 3 files changed, 21 insertions(+), 29 deletions(-) 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 8eb9454a6d..3fbe3d867b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -266,6 +266,7 @@ import sun.reflect.generics.reflectiveObjects.TypeVariableImpl import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl import java.lang.reflect.Method import java.util.concurrent.atomic.AtomicInteger +import org.utbot.framework.plugin.api.jimpleBody private val logger = KotlinLogging.logger {} val pathLogger = KotlinLogging.logger(logger.name + ".path") @@ -317,7 +318,6 @@ private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegi class Traverser( private val controller: EngineController, private val methodUnderTest: UtMethod<*>, - private val graph: ExceptionalUnitGraph, classpath: String, dependencyPaths: String, mockStrategy: MockStrategy = NO_MOCKS, @@ -325,6 +325,10 @@ class Traverser( private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis ) : UtContextInitializer() { + private val graph = jimpleBody(methodUnderTest).also { + logger.trace { "JIMPLE for $methodUnderTest:\n$this" } + }.graph() + private val methodUnderAnalysisStmts: Set = graph.stmts.toSet() private val visitedStmts: MutableSet = mutableSetOf() private val globalGraph = InterProceduralUnitGraph(graph) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt index 6a406b02d8..a0a5bc5a50 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt @@ -48,7 +48,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.yield import mu.KotlinLogging -import org.utbot.engine.* import soot.Scene import soot.jimple.JimpleBody import soot.toolkits.graph.ExceptionalUnitGraph @@ -202,12 +201,9 @@ object UtBotTestCaseGenerator : TestCaseGenerator { ): Traverser { // TODO: create classLoader from buildDir/classpath and migrate from UtMethod to MethodId? logger.debug("Starting symbolic execution for $method --$mockStrategy--") - val graph = graph(method) - return Traverser( controller, method, - graph, classpathForEngine, dependencyPaths = dependencyPaths, mockStrategy = apiToModel(mockStrategy), @@ -419,31 +415,22 @@ object UtBotTestCaseGenerator : TestCaseGenerator { else -> error("Cannot map API Mock Strategy model to Engine model: $mockStrategyApi") } - private fun graph(method: UtMethod<*>): ExceptionalUnitGraph { - val className = method.clazz.java.name - val clazz = Scene.v().classes.singleOrNull { it.name == className } - ?: error("No such $className found in the Scene") - val signature = method.callable.signature - val sootMethod = clazz.methods.singleOrNull { it.pureJavaSignature == signature } - ?: error("No such $signature found") - if (!sootMethod.canRetrieveBody()) { - error("No method body for $sootMethod found") - } - val methodBody = sootMethod.jimpleBody() - val graph = methodBody.graph() - - logger.trace { "JIMPLE for $method:\n${methodBody}" } +} - return graph - } +fun graph(method: UtMethod<*>): ExceptionalUnitGraph { + val methodBody = jimpleBody(method) + return methodBody.graph() +} - fun jimpleBody(method: UtMethod<*>): JimpleBody { - val clazz = Scene.v().classes.single { it.name == method.clazz.java.name } - val signature = method.callable.signature - val sootMethod = clazz.methods.single { it.pureJavaSignature == signature } +fun jimpleBody(method: UtMethod<*>): JimpleBody { + val className = method.clazz.java.name + val clazz = Scene.v().classes.singleOrNull { it.name == className } + ?: error("No such $className found in the Scene") + val signature = method.callable.signature + val sootMethod = clazz.methods.singleOrNull { it.pureJavaSignature == signature } + ?: error("No such $signature found") - return sootMethod.jimpleBody() - } + return sootMethod.jimpleBody() } fun JimpleBody.graph() = ExceptionalUnitGraph(this) diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt index 77f05d9787..2b4e941d71 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt @@ -68,6 +68,7 @@ import kotlin.reflect.KFunction5 import mu.KotlinLogging import org.junit.jupiter.api.Assertions.assertTrue import org.utbot.framework.PathSelectorType +import org.utbot.framework.plugin.api.jimpleBody val logger = KotlinLogging.logger {} @@ -2343,7 +2344,7 @@ abstract class AbstractTestCaseGeneratorTest( walk(utMethod, mockStrategy, additionalDependenciesClassPath) } testCase.summarize(searchDirectory) - val valueTestCase = testCase.toValueTestCase(UtBotTestCaseGenerator.jimpleBody(utMethod)) + val valueTestCase = testCase.toValueTestCase(jimpleBody(utMethod)) assertTrue(testCase.errors.isEmpty()) { "We have errors: ${ @@ -2478,7 +2479,7 @@ abstract class AbstractTestCaseGeneratorTest( additionalDependenciesClassPath: String = "" ): MethodResult { val testCase = executions(method, mockStrategy, additionalDependenciesClassPath) - val jimpleBody = UtBotTestCaseGenerator.jimpleBody(method) + val jimpleBody = jimpleBody(method) val methodCoverage = methodCoverage( method, testCase.toValueTestCase(jimpleBody).executions, From d2266726ac34a50b97803a26dd3ea1623dc58650 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 16:20:31 +0300 Subject: [PATCH 10/19] Refactor: remove useless environment field usage --- .../src/main/kotlin/org/utbot/engine/Traverser.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 3fbe3d867b..68ef27e6e8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -3829,8 +3829,8 @@ class Traverser( // it's free to make a check, because in the result is SAT, it should be already cached val holder = requireNotNull(solver.check(respectSoft = true) as? UtSolverStatusSAT) { "The state must be SAT!" } - val predictedTestName = Predictors.testName.predict(environment.state.path) - Predictors.testName.provide(environment.state.path, predictedTestName, "") + val predictedTestName = Predictors.testName.predict(state.path) + Predictors.testName.provide(state.path, predictedTestName, "") val resolver = Resolver(hierarchy, memory, typeRegistry, typeResolver, holder, methodUnderTest, softMaxArraySize) @@ -3849,10 +3849,10 @@ class Traverser( symbolicExecutionResult, instrumentation, entryMethodPath(), - environment.state.fullPath() + state.fullPath() ) - globalGraph.traversed(environment.state) + globalGraph.traversed(state) if (!UtSettings.useConcreteExecution || // Can't execute concretely because overflows do not cause actual exceptions. From a5cb81cf06bb8c67ed4bcfe75ca0370bf19f18d6 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Jun 2022 16:27:28 +0300 Subject: [PATCH 11/19] Refactor entryMethodPath function --- .../src/main/kotlin/org/utbot/engine/Traverser.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 68ef27e6e8..5b4851db30 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -3660,9 +3660,9 @@ class Traverser( * Collects entry method statement path for ML. Eliminates duplicated statements, e.g. assignment with invocation * in right part. */ - private fun entryMethodPath(): MutableList { + private fun entryMethodPath(state: ExecutionState): MutableList { val entryPath = mutableListOf() - environment.state.fullPath().forEach { step -> + state.fullPath().forEach { step -> // TODO: replace step.stmt in methodUnderAnalysisStmts with step.depth == 0 // when fix SAT-812: [JAVA] Wrong depth when exception thrown if (step.stmt in methodUnderAnalysisStmts && step.stmt !== entryPath.lastOrNull()?.stmt) { @@ -3848,7 +3848,7 @@ class Traverser( stateAfter, symbolicExecutionResult, instrumentation, - entryMethodPath(), + entryMethodPath(environment.state), state.fullPath() ) From b3137a9854eb4bbf0e07619bbf7cd37eb609e59e Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 27 Jun 2022 08:19:50 +0300 Subject: [PATCH 12/19] Move function isInsideStaticInitializer to ExecutionState.kt --- .../src/main/kotlin/org/utbot/engine/ExecutionState.kt | 3 +++ utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt index 13690ee8d3..157636ef5f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt @@ -160,6 +160,9 @@ data class ExecutionState( val isThrowException: Boolean get() = (lastEdge?.decisionNum ?: 0) < CALL_DECISION_NUM + val isInsideStaticInitializer + get() = executionStack.any { it.method.isStaticInitializer } + fun createExceptionState( exception: SymbolicFailure, update: SymbolicStateUpdate 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 5b4851db30..2b083142c8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -389,10 +389,6 @@ class Traverser( private val featureProcessor: FeatureProcessor? = if (enableFeatureProcess) EngineAnalyticsContext.featureProcessorFactory(globalGraph) else null - private val insideStaticInitializer - get() = environment.state.executionStack.any { it.method.isStaticInitializer } - - private val objectCounter = AtomicInteger(TypeRegistry.objectCounterInitialValue) private fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression { val newAddr = objectCounter.getAndIncrement() From 6046b22be7242ee4f7e77f1fb4a6d5907b2a10d6 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 27 Jun 2022 10:15:16 +0300 Subject: [PATCH 13/19] Refactor: move negativeArraySizeCheck from createNewArray to call site --- .../src/main/kotlin/org/utbot/engine/Traverser.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 2b083142c8..52f2952822 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -1733,6 +1733,7 @@ class Traverser( is JNewArrayExpr -> { val size = (resolve(expr.size) as PrimitiveValue).align() val type = expr.type as ArrayType + negativeArraySizeCheck(size) createNewArray(size, type, type.elementType).also { val defaultValue = type.defaultSymValue queuedSymbolicStateUpdates += arrayUpdateWithValue(it.addr, type, defaultValue as UtArrayExpressionBase) @@ -2015,8 +2016,11 @@ class Traverser( return castedArray } + /** + * @param size [SymbolicValue] representing size of an array. It's caller responsibility to handle negative + * size. + */ internal fun createNewArray(size: PrimitiveValue, type: ArrayType, elementType: Type): ArrayValue { - negativeArraySizeCheck(size) val addr = findNewAddr() val length = memory.findArrayLength(addr) From 1d7017ac2e71c99f0edd01a7f0841f6b8ba91cb7 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 27 Jun 2022 10:34:06 +0300 Subject: [PATCH 14/19] Introduce TraversingContext --- .../org/utbot/engine/TraversalContext.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt new file mode 100644 index 0000000000..cf70077b3f --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt @@ -0,0 +1,30 @@ +package org.utbot.engine + +/** + * Represents a mutable _Context_ during the [ExecutionState] traversing. This _Context_ consists of all mutable and + * immutable properties and fields which are created and updated during analysis of **one** Jimple instruction. + * + * Traverser functions should be implemented as an extension functions with [TraversalContext] as a receiver. + * + * TODO: extend this class with other properties, such as [Environment], which is [Traverser] mutable property now. + */ +class TraversalContext { + // TODO: move properties from [UtBotSymbolicEngine] here + + + // TODO: Q: maybe it's better to pass stateConsumer as an argument to constructor? + private val states = mutableListOf() + + /** + * Offers new [ExecutionState] which can be obtained later with [nextStates]. + */ + fun offerState(state: ExecutionState) { + states.add(state) + } + + /** + * New states from traversal. + */ + val nextStates: Collection + get() = states +} \ No newline at end of file From 33f21b7e1293f89b3564a9d93a9af9f9192de638 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 27 Jun 2022 10:45:57 +0300 Subject: [PATCH 15/19] Split Traverser.kt into Traverser.kt and UtBotSymbolicEngine.kt Convert Traversal functions to extension functions --- .../main/kotlin/org/utbot/engine/Traverser.kt | 820 +++--------------- .../org/utbot/engine/UtBotSymbolicEngine.kt | 654 ++++++++++++++ .../plugin/api/UtBotTestCaseGenerator.kt | 10 +- 3 files changed, 770 insertions(+), 714 deletions(-) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt 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 52f2952822..eab1011f84 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -7,30 +7,12 @@ import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentMap import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Job -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.ensureActive -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.isActive -import kotlinx.coroutines.job -import kotlinx.coroutines.yield -import mu.KotlinLogging -import org.utbot.analytics.EngineAnalyticsContext -import org.utbot.analytics.FeatureProcessor -import org.utbot.analytics.Predictors import org.utbot.common.WorkaroundReason.HACK import org.utbot.common.WorkaroundReason.REMOVE_ANONYMOUS_CLASSES -import org.utbot.common.bracket -import org.utbot.common.debug import org.utbot.common.findField import org.utbot.common.unreachableBranch import org.utbot.common.withAccessibility import org.utbot.common.workaround -import org.utbot.engine.MockStrategy.NO_MOCKS import org.utbot.engine.overrides.UtArrayMock import org.utbot.engine.overrides.UtLogicMock import org.utbot.engine.overrides.UtOverrideMock @@ -84,87 +66,37 @@ import org.utbot.engine.pc.mkNot import org.utbot.engine.pc.mkOr import org.utbot.engine.pc.select import org.utbot.engine.pc.store -import org.utbot.engine.selectors.PathSelector -import org.utbot.engine.selectors.StrategyOption -import org.utbot.engine.selectors.coveredNewSelector -import org.utbot.engine.selectors.cpInstSelector -import org.utbot.engine.selectors.forkDepthSelector -import org.utbot.engine.selectors.inheritorsSelector -import org.utbot.engine.selectors.nnRewardGuidedSelector -import org.utbot.engine.selectors.nurs.NonUniformRandomSearch -import org.utbot.engine.selectors.pollUntilFastSAT -import org.utbot.engine.selectors.randomPathSelector -import org.utbot.engine.selectors.randomSelector -import org.utbot.engine.selectors.strategies.GraphViz -import org.utbot.engine.selectors.subpathGuidedSelector import org.utbot.engine.symbolic.HardConstraint import org.utbot.engine.symbolic.SoftConstraint import org.utbot.engine.symbolic.Assumption -import org.utbot.engine.symbolic.SymbolicState import org.utbot.engine.symbolic.SymbolicStateUpdate import org.utbot.engine.symbolic.asHardConstraint import org.utbot.engine.symbolic.asSoftConstraint import org.utbot.engine.symbolic.asAssumption import org.utbot.engine.symbolic.asUpdate import org.utbot.engine.util.trusted.isFromTrustedLibrary -import org.utbot.engine.util.mockListeners.MockListener -import org.utbot.engine.util.mockListeners.MockListenerController import org.utbot.engine.util.statics.concrete.associateEnumSootFieldsWithConcreteValues import org.utbot.engine.util.statics.concrete.isEnumAffectingExternalStatics import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName import org.utbot.engine.util.statics.concrete.makeEnumNonStaticFieldsUpdates import org.utbot.engine.util.statics.concrete.makeEnumStaticFieldsUpdates import org.utbot.engine.util.statics.concrete.makeSymbolicValuesFromEnumConcreteValues -import org.utbot.framework.PathSelectorType import org.utbot.framework.UtSettings import org.utbot.framework.UtSettings.maximizeCoverageUsingReflection -import org.utbot.framework.UtSettings.checkSolverTimeoutMillis -import org.utbot.framework.UtSettings.enableFeatureProcess -import org.utbot.framework.UtSettings.pathSelectorStepsLimit -import org.utbot.framework.UtSettings.pathSelectorType import org.utbot.framework.UtSettings.preferredCexOption import org.utbot.framework.UtSettings.substituteStaticsWithSymbolicVariable -import org.utbot.framework.UtSettings.useDebugVisualization -import org.utbot.framework.UtSettings.processUnknownStatesDuringConcreteExecution -import org.utbot.framework.concrete.UtConcreteExecutionData -import org.utbot.framework.concrete.UtConcreteExecutionResult -import org.utbot.framework.concrete.UtExecutionInstrumentation import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.ConcreteExecutionFailureException -import org.utbot.framework.plugin.api.EnvironmentModels import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.Instruction import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.MissingState -import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.UtConcreteExecutionFailure -import org.utbot.framework.plugin.api.UtError -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtInstrumentation import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtOverflowFailure -import org.utbot.framework.plugin.api.UtResult import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.graph import org.utbot.framework.plugin.api.id -import org.utbot.framework.plugin.api.onSuccess -import org.utbot.framework.plugin.api.util.executableId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.signature import org.utbot.framework.plugin.api.util.utContext -import org.utbot.framework.plugin.api.util.description import org.utbot.framework.util.executableId -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.ModelProvider -import org.utbot.fuzzer.FallbackModelProvider -import org.utbot.fuzzer.collectConstantsForFuzzer -import org.utbot.fuzzer.defaultModelProviders -import org.utbot.fuzzer.fuzz -import org.utbot.fuzzer.names.MethodBasedNameSuggester -import org.utbot.fuzzer.names.ModelBasedNameSuggester -import org.utbot.instrumentation.ConcreteExecutor import java.lang.reflect.ParameterizedType import kotlin.collections.plus import kotlin.collections.plusAssign @@ -173,7 +105,6 @@ import kotlin.math.min import kotlin.reflect.full.instanceParameter import kotlin.reflect.full.valueParameters import kotlin.reflect.jvm.javaType -import kotlin.system.measureTimeMillis import soot.ArrayType import soot.BooleanType import soot.ByteType @@ -257,104 +188,31 @@ import soot.jimple.internal.JTableSwitchStmt import soot.jimple.internal.JThrowStmt import soot.jimple.internal.JVirtualInvokeExpr import soot.jimple.internal.JimpleLocal -import soot.tagkit.ParamNamesTag import soot.toolkits.graph.ExceptionalUnitGraph import sun.reflect.Reflection import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl import sun.reflect.generics.reflectiveObjects.TypeVariableImpl import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl -import java.lang.reflect.Method import java.util.concurrent.atomic.AtomicInteger -import org.utbot.framework.plugin.api.jimpleBody - -private val logger = KotlinLogging.logger {} -val pathLogger = KotlinLogging.logger(logger.name + ".path") private val CAUGHT_EXCEPTION = LocalVariable("@caughtexception") -//in future we should put all timeouts here -class EngineController { - var paused: Boolean = false - var executeConcretely: Boolean = false - var stop: Boolean = false - var job: Job? = null -} - -//for debugging purpose only -private var stateSelectedCount = 0 - -//all id values of synthetic default models must be greater that for real ones -private var nextDefaultModelId = 1500_000_000 - -private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegistry) = - when (pathSelectorType) { - PathSelectorType.COVERED_NEW_SELECTOR -> coveredNewSelector(graph) { - withStepsLimit(pathSelectorStepsLimit) - } - PathSelectorType.INHERITORS_SELECTOR -> inheritorsSelector(graph, typeRegistry) { - withStepsLimit(pathSelectorStepsLimit) - } - PathSelectorType.SUBPATH_GUIDED_SELECTOR -> subpathGuidedSelector(graph, StrategyOption.DISTANCE) { - withStepsLimit(pathSelectorStepsLimit) - } - PathSelectorType.CPI_SELECTOR -> cpInstSelector(graph, StrategyOption.DISTANCE) { - withStepsLimit(pathSelectorStepsLimit) - } - PathSelectorType.FORK_DEPTH_SELECTOR -> forkDepthSelector(graph, StrategyOption.DISTANCE) { - withStepsLimit(pathSelectorStepsLimit) - } - PathSelectorType.NN_REWARD_GUIDED_SELECTOR -> nnRewardGuidedSelector(graph, StrategyOption.DISTANCE) { - withStepsLimit(pathSelectorStepsLimit) - } - PathSelectorType.RANDOM_SELECTOR -> randomSelector(graph, StrategyOption.DISTANCE) { - withStepsLimit(pathSelectorStepsLimit) - } - PathSelectorType.RANDOM_PATH_SELECTOR -> randomPathSelector(graph, StrategyOption.DISTANCE) { - withStepsLimit(pathSelectorStepsLimit) - } - } - class Traverser( - private val controller: EngineController, private val methodUnderTest: UtMethod<*>, - classpath: String, - dependencyPaths: String, - mockStrategy: MockStrategy = NO_MOCKS, - chosenClassesToMockAlways: Set, - private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis + internal val typeRegistry: TypeRegistry, + internal val hierarchy: Hierarchy, + // TODO HACK violation of encapsulation + internal val typeResolver: TypeResolver, + private val globalGraph: InterProceduralUnitGraph, + private val mocker: Mocker, ) : UtContextInitializer() { - private val graph = jimpleBody(methodUnderTest).also { - logger.trace { "JIMPLE for $methodUnderTest:\n$this" } - }.graph() - - private val methodUnderAnalysisStmts: Set = graph.stmts.toSet() private val visitedStmts: MutableSet = mutableSetOf() - private val globalGraph = InterProceduralUnitGraph(graph) - internal val typeRegistry: TypeRegistry = TypeRegistry() - private val pathSelector: PathSelector = pathSelector(globalGraph, typeRegistry) private val classLoader: ClassLoader get() = utContext.classLoader - internal val hierarchy: Hierarchy = Hierarchy(typeRegistry) - - // TODO HACK violation of encapsulation - internal val typeResolver: TypeResolver = TypeResolver(typeRegistry, hierarchy) - - private val classUnderTest: ClassId = methodUnderTest.clazz.id - - private val mocker: Mocker = Mocker( - mockStrategy, - classUnderTest, - hierarchy, - chosenClassesToMockAlways, - MockListenerController(controller) - ) - - private val statesForConcreteExecution: MutableList = mutableListOf() - lateinit var environment: Environment private val solver: UtSolver get() = environment.state.solver @@ -366,17 +224,9 @@ class Traverser( private val localVariableMemory: LocalVariableMemory get() = environment.state.localVariableMemory - //HACK (long strings) internal var softMaxArraySize = 40 - private val concreteExecutor = - ConcreteExecutor( - UtExecutionInstrumentation, - classpath, - dependencyPaths - ).apply { this.classLoader = utContext.classLoader } - /** * Contains information about the generic types used in the parameters of the method under test. */ @@ -386,9 +236,6 @@ class Traverser( private var queuedSymbolicStateUpdates = SymbolicStateUpdate() - private val featureProcessor: FeatureProcessor? = - if (enableFeatureProcess) EngineAnalyticsContext.featureProcessorFactory(globalGraph) else null - private val objectCounter = AtomicInteger(TypeRegistry.objectCounterInitialValue) private fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression { val newAddr = objectCounter.getAndIncrement() @@ -399,7 +246,7 @@ class Traverser( val signedAddr = if (insideStaticInitializer) -newAddr else newAddr return UtAddrExpression(signedAddr) } - internal fun findNewAddr() = findNewAddr(insideStaticInitializer).also { touchAddress(it) } + internal fun findNewAddr() = findNewAddr(environment.state.isInsideStaticInitializer).also { touchAddress(it) } // Counter used for a creation symbolic results of "hashcode" and "equals" methods. private var equalsCounter = 0 @@ -408,317 +255,45 @@ class Traverser( // A counter for objects created as native method call result. private var unboundedConstCounter = 0 - private val trackableResources: MutableSet = mutableSetOf() + fun traverse(state: ExecutionState): Collection { + val context = TraversalContext() - private fun postTraverse() { - for (r in trackableResources) - try { - r.close() - } catch (e: Throwable) { - logger.error(e) { "Closing resource failed" } - } - trackableResources.clear() - featureProcessor?.dumpFeatures() - } - - private suspend fun preTraverse() { - //fixes leak in useless Context() created in AutoCloseable() - close() - if (!currentCoroutineContext().isActive) return - stateSelectedCount = 0 - } - - fun traverse(): Flow = traverseImpl() - .onStart { preTraverse() } - .onCompletion { postTraverse() } - - private fun traverseImpl(): Flow = flow { - - require(trackableResources.isEmpty()) - - if (useDebugVisualization) GraphViz(globalGraph, pathSelector) - - val initStmt = graph.head - val initState = ExecutionState( - initStmt, - SymbolicState(UtSolver(typeRegistry, trackableResources, solverTimeoutInMillis)), - executionStack = persistentListOf(ExecutionStackElement(null, method = graph.body.method)) - ) - - pathSelector.offer(initState) - - environment = Environment( - method = globalGraph.method(initStmt), - state = initState - ) - pathSelector.use { - - while (currentCoroutineContext().isActive) { - if (controller.stop) - break - - if (controller.paused) { - try { - yield() - } catch (e: CancellationException) { //todo in future we should just throw cancellation - break - } - continue - } - - stateSelectedCount++ - pathLogger.trace { "traverse<$methodUnderTest>: choosing next state($stateSelectedCount), " + - "queue size=${(pathSelector as? NonUniformRandomSearch)?.size ?: -1}" - } - - if (controller.executeConcretely || statesForConcreteExecution.isNotEmpty()) { - val state = pathSelector.pollUntilFastSAT() - ?: statesForConcreteExecution.pollUntilSat(processUnknownStatesDuringConcreteExecution) - ?: break - // This state can contain inconsistent wrappers - for example, Map with keys but missing values. - // We cannot use withWrapperConsistencyChecks here because it needs solver to work. - // So, we have to process such cases accurately in wrappers resolving. - - logger.trace { "executing $state concretely..." } - - environment.state = state - - - logger.debug().bracket("concolicStrategy<$methodUnderTest>: execute concretely") { - val resolver = Resolver( - hierarchy, - memory, - typeRegistry, - typeResolver, - state.solver.lastStatus as UtSolverStatusSAT, - methodUnderTest, - softMaxArraySize - ) - - val resolvedParameters = state.methodUnderTestParameters - val (modelsBefore, _, instrumentation) = resolver.resolveModels(resolvedParameters) - val stateBefore = modelsBefore.constructStateForMethod(methodUnderTest) - - try { - val concreteExecutionResult = - concreteExecutor.executeConcretely(methodUnderTest, stateBefore, instrumentation) - - val concreteUtExecution = UtExecution( - stateBefore, - concreteExecutionResult.stateAfter, - concreteExecutionResult.result, - instrumentation, - mutableListOf(), - listOf(), - concreteExecutionResult.coverage - ) - emit(concreteUtExecution) - - logger.debug { "concolicStrategy<${methodUnderTest}>: returned $concreteUtExecution" } - } catch (e: CancellationException) { - logger.debug(e) { "Cancellation happened" } - } catch (e: ConcreteExecutionFailureException) { - emitFailedConcreteExecutionResult(stateBefore, e) - } catch (e: Throwable) { - emit(UtError("Concrete execution failed", e)) - } - } - - } else { - val state = pathSelector.poll() - - // state is null in case states queue is empty - // or path selector exceed some limits (steps limit, for example) - if (state == null) { - // check do we have remaining states that we can execute concretely - val pathSelectorStatesForConcreteExecution = pathSelector - .remainingStatesForConcreteExecution - .map { it.withWrapperConsistencyChecks() } - if (pathSelectorStatesForConcreteExecution.isNotEmpty()) { - statesForConcreteExecution += pathSelectorStatesForConcreteExecution - logger.debug { - "${pathSelectorStatesForConcreteExecution.size} remaining states " + - "were moved from path selector for concrete execution" - } - continue // the next step in while loop processes concrete states - } else { - break - } - } - - state.executingTime += measureTimeMillis { - environment.state = state - - val currentStmt = environment.state.stmt - - if (currentStmt !in visitedStmts) { - environment.state.updateIsVisitedNew() - visitedStmts += currentStmt - } - - environment.method = globalGraph.method(currentStmt) - - environment.state.lastEdge?.let { - globalGraph.visitEdge(it) - } - - try { - val exception = environment.state.exception - if (exception != null) { - traverseException(currentStmt, exception) - } else { - traverseStmt(currentStmt) - } - - // Here job can be cancelled from within traverse, e.g. by using force mocking without Mockito. - // So we need to make it throw CancelledException by method below: - currentCoroutineContext().job.ensureActive() - } catch (ex: Throwable) { - environment.state.close() - - if (ex !is CancellationException) { - logger.error(ex) { "Test generation failed on stmt $currentStmt, symbolic stack trace:\n$symbolicStackTrace" } - // TODO: enrich with nice description for known issues - emit(UtError(ex.description, ex)) - } else { - logger.debug(ex) { "Cancellation happened" } - } - } - } - } - queuedSymbolicStateUpdates = SymbolicStateUpdate() - globalGraph.visitNode(environment.state) - } - } - } + val currentStmt = state.stmt + environment = Environment(globalGraph.method(state.stmt), state) - /** - * Run fuzzing flow. - * - * @param until is used by fuzzer to cancel all tasks if the current time is over this value - * @param modelProvider provides model values for a method - */ - fun fuzzing(until: Long = Long.MAX_VALUE, modelProvider: (ModelProvider) -> ModelProvider = { it }) = flow { - val executableId = if (methodUnderTest.isConstructor) { - methodUnderTest.javaConstructor!!.executableId - } else { - methodUnderTest.javaMethod!!.executableId + if (currentStmt !in visitedStmts) { + environment.state.updateIsVisitedNew() + visitedStmts += currentStmt } - val isFuzzable = executableId.parameters.all { classId -> - classId != Method::class.java.id && // causes the child process crash at invocation - classId != Class::class.java.id // causes java.lang.IllegalAccessException: java.lang.Class at sun.misc.Unsafe.allocateInstance(Native Method) + environment.state.lastEdge?.let { + globalGraph.visitEdge(it) } - if (!isFuzzable) { - return@flow - } - - val fallbackModelProvider = FallbackModelProvider { nextDefaultModelId++ } - val thisInstance = when { - methodUnderTest.isStatic -> null - methodUnderTest.isConstructor -> if ( - methodUnderTest.clazz.isAbstract || // can't instantiate abstract class - methodUnderTest.clazz.java.isEnum // can't reflectively create enum objects - ) { - return@flow + try { + val exception = environment.state.exception + if (exception != null) { + context.traverseException(currentStmt, exception) } else { - null - } - else -> { - fallbackModelProvider.toModel(methodUnderTest.clazz).apply { - if (this is UtNullModel) { // it will definitely fail because of NPE, - return@flow - } - } - } - } - - val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph)).apply { - compilableName = if (methodUnderTest.isMethod) executableId.name else null - val names = graph.body.method.tags.filterIsInstance().firstOrNull()?.names - parameterNameMap = { index -> names?.getOrNull(index) } - } - val modelProviderWithFallback = modelProvider(defaultModelProviders { nextDefaultModelId++ }).withFallback(fallbackModelProvider::toModel) - val coveredInstructionTracker = mutableSetOf() - var attempts = UtSettings.fuzzingMaxAttempts - fuzz(methodUnderTestDescription, modelProviderWithFallback).forEach { values -> - if (System.currentTimeMillis() >= until) { - logger.info { "Fuzzing overtime: $methodUnderTest" } - return@flow + context.traverseStmt(currentStmt) } + } catch (ex: Throwable) { + environment.state.close() - val initialEnvironmentModels = EnvironmentModels(thisInstance, values.map { it.model }, mapOf()) - - try { - val concreteExecutionResult = - concreteExecutor.executeConcretely(methodUnderTest, initialEnvironmentModels, listOf()) - - workaround(REMOVE_ANONYMOUS_CLASSES) { - concreteExecutionResult.result.onSuccess { - if (it.classId.isAnonymous) { - logger.debug("Anonymous class found as a concrete result, symbolic one will be returned") - return@flow - } - } - } - - if (!coveredInstructionTracker.addAll(concreteExecutionResult.coverage.coveredInstructions)) { - if (--attempts < 0) { - return@flow - } - } - - val nameSuggester = sequenceOf(ModelBasedNameSuggester(), MethodBasedNameSuggester()) - val testMethodName = try { - nameSuggester.flatMap { it.suggest(methodUnderTestDescription, values, concreteExecutionResult.result) }.firstOrNull() - } catch (t: Throwable) { - logger.error(t) { "Cannot create suggested test name for ${methodUnderTest.displayName}" } - null - } - - emit( - UtExecution( - stateBefore = initialEnvironmentModels, - stateAfter = concreteExecutionResult.stateAfter, - result = concreteExecutionResult.result, - instrumentation = emptyList(), - path = mutableListOf(), - fullPath = emptyList(), - coverage = concreteExecutionResult.coverage, - testMethodName = testMethodName?.testName, - displayName = testMethodName?.displayName - ) - ) - } catch (e: CancellationException) { - logger.debug { "Cancelled by timeout" } - } catch (e: ConcreteExecutionFailureException) { - emitFailedConcreteExecutionResult(initialEnvironmentModels, e) - } catch (e: Throwable) { - emit(UtError("Default concrete execution failed", e)) + if (ex !is CancellationException) { + logger.error(ex) { "Test generation failed on stmt $currentStmt, symbolic stack trace:\n$symbolicStackTrace" } + // TODO: enrich with nice description for known issues + throw ex + } else { + logger.debug(ex) { "Cancellation happened" } } } + queuedSymbolicStateUpdates = SymbolicStateUpdate() + return context.nextStates } - private suspend fun FlowCollector.emitFailedConcreteExecutionResult( - stateBefore: EnvironmentModels, - e: ConcreteExecutionFailureException - ) { - val failedConcreteExecution = UtExecution( - stateBefore = stateBefore, - stateAfter = MissingState, - result = UtConcreteExecutionFailure(e), - instrumentation = emptyList(), - path = mutableListOf(), - fullPath = listOf() - ) - - emit(failedConcreteExecution) - } - - - private suspend fun FlowCollector.traverseStmt(current: Stmt) { + private fun TraversalContext.traverseStmt(current: Stmt) { if (doPreparatoryWorkIfRequired(current)) return when (current) { @@ -727,14 +302,14 @@ class Traverser( is JIfStmt -> traverseIfStmt(current) is JInvokeStmt -> traverseInvokeStmt(current) is SwitchStmt -> traverseSwitchStmt(current) - is JReturnStmt -> processResult(current.symbolicSuccess()) + is JReturnStmt -> processResult(symbolicSuccess(current)) is JReturnVoidStmt -> processResult(SymbolicSuccess(voidValue)) is JRetStmt -> error("This one should be already removed by Soot: $current") is JThrowStmt -> traverseThrowStmt(current) - is JBreakpointStmt -> pathSelector.offer(environment.state.updateQueued(globalGraph.succ(current))) - is JGotoStmt -> pathSelector.offer(environment.state.updateQueued(globalGraph.succ(current))) - is JNopStmt -> pathSelector.offer(environment.state.updateQueued(globalGraph.succ(current))) - is MonitorStmt -> pathSelector.offer(environment.state.updateQueued(globalGraph.succ(current))) + is JBreakpointStmt -> offerState(environment.state.updateQueued(globalGraph.succ(current))) + is JGotoStmt -> offerState(environment.state.updateQueued(globalGraph.succ(current))) + is JNopStmt -> offerState(environment.state.updateQueued(globalGraph.succ(current))) + is MonitorStmt -> offerState(environment.state.updateQueued(globalGraph.succ(current))) is DefinitionStmt -> TODO("$current") else -> error("Unsupported: ${current::class}") } @@ -751,7 +326,7 @@ class Traverser( * - False if preparatory work is not required or it is already done. * environment.state.methodResult can contain the work result. */ - private suspend fun FlowCollector.doPreparatoryWorkIfRequired(current: Stmt): Boolean { + private fun TraversalContext.doPreparatoryWorkIfRequired(current: Stmt): Boolean { if (current !is JAssignStmt) return false return when { @@ -774,7 +349,7 @@ class Traverser( * * Note: similar but more granular approach used if Engine decides to process static field concretely. */ - private suspend fun FlowCollector.processStaticInitializerIfRequired(stmt: JAssignStmt): Boolean { + private fun TraversalContext.processStaticInitializerIfRequired(stmt: JAssignStmt): Boolean { val right = stmt.rightOp val left = stmt.leftOp val method = environment.method @@ -796,7 +371,7 @@ class Traverser( * environment.state.methodResult. * - False otherwise */ - private fun unfoldMultiArrayExprIfRequired(stmt: JAssignStmt): Boolean { + private fun TraversalContext.unfoldMultiArrayExprIfRequired(stmt: JAssignStmt): Boolean { // We have already unfolded the statement and processed constructed graph, have the calculated result if (environment.state.methodResult != null) return false @@ -823,7 +398,7 @@ class Traverser( * * Returns true if processing takes place and Engine should end traversal of current statement. */ - private suspend fun FlowCollector.processStaticInitializer( + private fun TraversalContext.processStaticInitializer( fieldRef: StaticFieldRef, stmt: Stmt ): Boolean { @@ -853,7 +428,7 @@ class Traverser( when (result.symbolicResult) { // This branch could be useful if we have a static field, i.e. x = 5 / 0 is SymbolicFailure -> traverseException(stmt, result.symbolicResult) - is SymbolicSuccess -> pathSelector.offer( + is SymbolicSuccess -> offerState( environment.state.updateQueued( environment.state.lastEdge!!, result.symbolicStateUpdate @@ -915,7 +490,7 @@ class Traverser( * * Returns true if processing takes place and Engine should end traversal of current statement. */ - private fun processStaticFieldConcretely(fieldRef: StaticFieldRef, stmt: Stmt): Boolean { + private fun TraversalContext.processStaticFieldConcretely(fieldRef: StaticFieldRef, stmt: Stmt): Boolean { val field = fieldRef.field val fieldId = field.fieldId if (memory.isInitialized(fieldId)) { @@ -942,7 +517,7 @@ class Traverser( val edge = environment.state.lastEdge ?: globalGraph.succ(stmt) val newState = environment.state.updateQueued(edge, updates) - pathSelector.offer(newState) + offerState(newState) return true } @@ -1074,7 +649,7 @@ class Traverser( private fun isStaticInstanceInMethodResult(id: ClassId, methodResult: MethodResult?) = methodResult != null && id in methodResult.memoryUpdates.staticInstanceStorage - private fun skipVerticesForThrowableCreation(current: JAssignStmt) { + private fun TraversalContext.skipVerticesForThrowableCreation(current: JAssignStmt) { val rightType = current.rightOp.type as RefType val exceptionType = Scene.v().getSootClass(rightType.className).type val createdException = createObject(findNewAddr(), exceptionType, true) @@ -1089,10 +664,10 @@ class Traverser( globalGraph.visitNode(environment.state) } while (!environment.state.stmt.isConstructorCall(currentExceptionJimpleLocal)) - pathSelector.offer(environment.state.updateQueued(globalGraph.succ(environment.state.stmt))) + offerState(environment.state.updateQueued(globalGraph.succ(environment.state.stmt))) } - private fun traverseAssignStmt(current: JAssignStmt) { + private fun TraversalContext.traverseAssignStmt(current: JAssignStmt) { val rightValue = current.rightOp workaround(HACK) { @@ -1127,7 +702,7 @@ class Traverser( queuedSymbolicStateUpdates + methodResult.symbolicStateUpdate ) globalGraph.registerImplicitEdge(nextState.lastEdge!!) - pathSelector.offer(nextState) + offerState(nextState) } is SymbolicSuccess -> { @@ -1135,7 +710,7 @@ class Traverser( current.leftOp, methodResult.symbolicResult.value ) - pathSelector.offer( + offerState( environment.state.updateQueued( globalGraph.succ(current), update + methodResult.symbolicStateUpdate @@ -1178,7 +753,7 @@ class Traverser( /** * Traverses left part of assignment i.e. where to store resolved value. */ - private fun traverseAssignLeftPart(left: Value, value: SymbolicValue): SymbolicStateUpdate = when (left) { + private fun TraversalContext.traverseAssignLeftPart(left: Value, value: SymbolicValue): SymbolicStateUpdate = when (left) { is ArrayRef -> { val arrayInstance = resolve(left.base) as ArrayValue val addr = arrayInstance.addr @@ -1251,7 +826,7 @@ class Traverser( /** * Resolves instance for field. For static field it's a special object represents static fields of particular class. */ - private fun resolveInstanceForField(fieldRef: FieldRef) = when (fieldRef) { + private fun TraversalContext.resolveInstanceForField(fieldRef: FieldRef) = when (fieldRef) { is JInstanceFieldRef -> { // Runs resolve() to check possible NPE and create required arrays related to the field. // Ignores the result of resolve(). @@ -1282,7 +857,7 @@ class Traverser( is PrimitiveValue -> UtCastExpression(value, type) } - private fun traverseIdentityStmt(current: JIdentityStmt) { + private fun TraversalContext.traverseIdentityStmt(current: JIdentityStmt) { val localVariable = (current.leftOp as? JimpleLocal)?.variable ?: error("Unknown op: ${current.leftOp}") when (val identityRef = current.rightOp as IdentityRef) { is ParameterRef, is ThisRef -> { @@ -1335,7 +910,7 @@ class Traverser( globalGraph.succ(current), SymbolicStateUpdate(localMemoryUpdates = localMemoryUpdate(localVariable to value)) ) - pathSelector.offer(nextState) + offerState(nextState) } is JCaughtExceptionRef -> { val value = localVariableMemory.local(CAUGHT_EXCEPTION) @@ -1344,7 +919,7 @@ class Traverser( globalGraph.succ(current), SymbolicStateUpdate(localMemoryUpdates = localMemoryUpdate(localVariable to value, CAUGHT_EXCEPTION to null)) ) - pathSelector.offer(nextState) + offerState(nextState) } else -> error("Unsupported $identityRef") } @@ -1437,7 +1012,7 @@ class Traverser( } } - private fun traverseIfStmt(current: JIfStmt) { + private fun TraversalContext.traverseIfStmt(current: JIfStmt) { // positiveCaseEdge could be null - see Conditions::emptyBranches val (negativeCaseEdge, positiveCaseEdge) = globalGraph.succs(current).let { it[0] to it.getOrNull(1) } val cond = current.condition @@ -1479,7 +1054,7 @@ class Traverser( softConstraints = setOfNotNull(positiveCaseSoftConstraint).asSoftConstraint() ) + resolvedCondition.symbolicStateUpdates.positiveCase ) - pathSelector.offer(positiveCaseState) + offerState(positiveCaseState) } } @@ -1495,7 +1070,7 @@ class Traverser( assumptions = assumption ) + resolvedCondition.symbolicStateUpdates.negativeCase ) - pathSelector.offer(negativeCaseState) + offerState(negativeCaseState) } /** @@ -1507,7 +1082,7 @@ class Traverser( return invokeExpression.method.isUtMockAssumeOrExecuteConcretely } - private fun traverseInvokeStmt(current: JInvokeStmt) { + private fun TraversalContext.traverseInvokeStmt(current: JInvokeStmt) { val results = invokeResult(current.invokeExpr) results.forEach { result -> @@ -1515,7 +1090,7 @@ class Traverser( return@forEach } - pathSelector.offer( + offerState( when (result.symbolicResult) { is SymbolicFailure -> environment.state.createExceptionState( result.symbolicResult, @@ -1530,7 +1105,7 @@ class Traverser( } } - private fun traverseSwitchStmt(current: SwitchStmt) { + private fun TraversalContext.traverseSwitchStmt(current: SwitchStmt) { val valueExpr = resolve(current.key) as PrimitiveValue val successors = when (current) { is JTableSwitchStmt -> { @@ -1558,7 +1133,7 @@ class Traverser( } successors.forEach { (target, expr) -> - pathSelector.offer( + offerState( environment.state.updateQueued( target, SymbolicStateUpdate(hardConstraints = expr.asHardConstraint()), @@ -1567,7 +1142,7 @@ class Traverser( } } - private suspend fun FlowCollector.traverseThrowStmt(current: JThrowStmt) { + private fun TraversalContext.traverseThrowStmt(current: JThrowStmt) { val symException = explicitThrown(resolve(current.op), environment.state.isInNestedMethod()) traverseException(current, symException) } @@ -1647,7 +1222,7 @@ class Traverser( return ObjectValue(typeStorage, addr, concreteImplementation) } - private fun resolveConstant(constant: Constant): SymbolicValue = + private fun TraversalContext.resolveConstant(constant: Constant): SymbolicValue = when (constant) { is IntConstant -> constant.value.toPrimitiveValue() is LongConstant -> constant.value.toPrimitiveValue() @@ -1661,7 +1236,7 @@ class Traverser( if (UtSettings.ignoreStringLiterals && constant.value.length > MAX_STRING_SIZE) { // instead of it we create an unbounded symbolic variable workaround(HACK) { - statesForConcreteExecution += environment.state + offerState(environment.state.withLabel(StateLabel.CONCRETE)) createObject(addr, refType, useConcreteType = true) } } else { @@ -1685,7 +1260,7 @@ class Traverser( else -> error("Unsupported type: $constant") } - private fun resolve(expr: Expr, valueType: Type = expr.type): SymbolicValue = + private fun TraversalContext.resolve(expr: Expr, valueType: Type = expr.type): SymbolicValue = when (expr) { is BinopExpr -> { val left = resolve(expr.op1) @@ -1909,7 +1484,7 @@ class Traverser( } } - private fun castObject(objectValue: ObjectValue, typeAfterCast: Type, op: Value): SymbolicValue { + private fun TraversalContext.castObject(objectValue: ObjectValue, typeAfterCast: Type, op: Value): SymbolicValue { classCastExceptionCheck(objectValue, typeAfterCast) val currentType = objectValue.type @@ -1963,7 +1538,7 @@ class Traverser( return castedObject } - private fun castArray(arrayValue: ArrayValue, typeAfterCast: Type): ArrayValue { + private fun TraversalContext.castArray(arrayValue: ArrayValue, typeAfterCast: Type): ArrayValue { classCastExceptionCheck(arrayValue, typeAfterCast) if (typeAfterCast.isJavaLangObject()) return arrayValue @@ -2055,7 +1630,7 @@ class Traverser( // Type is needed for null values: we should know, which null do we require. // If valueType is NullType, return typelessNullObject. It can happen in a situation, // where we cannot find the type, for example in condition (null == null) - private fun resolve( + private fun TraversalContext.resolve( value: Value, valueType: Type = value.type ): SymbolicValue = when (value) { @@ -2192,7 +1767,7 @@ class Traverser( return created } - private fun resolveParameters(parameters: List, types: List) = + private fun TraversalContext.resolveParameters(parameters: List, types: List) = parameters.zip(types).map { (value, type) -> resolve(value, type) } private fun applyPreferredConstraints(value: SymbolicValue) { @@ -2489,7 +2064,7 @@ class Traverser( } } - private suspend fun FlowCollector.traverseException(current: Stmt, exception: SymbolicFailure) { + private fun TraversalContext.traverseException(current: Stmt, exception: SymbolicFailure) { if (!traverseCatchBlock(current, exception, emptySet())) { processResult(exception) } @@ -2500,7 +2075,7 @@ class Traverser( * * Returns true if found, false otherwise. */ - private fun traverseCatchBlock( + private fun TraversalContext.traverseCatchBlock( current: Stmt, exception: SymbolicFailure, conditions: Set @@ -2511,7 +2086,7 @@ class Traverser( ) val edge = findCatchBlock(current, classId) ?: return false - pathSelector.offer( + offerState( environment.state.updateQueued( edge, SymbolicStateUpdate( @@ -2530,7 +2105,7 @@ class Traverser( }.firstOrNull { it.second in hierarchy.ancestors(classId) }?.first } - private fun invokeResult(invokeExpr: Expr): List = + private fun TraversalContext.invokeResult(invokeExpr: Expr): List = environment.state.methodResult?.let { listOf(it) } ?: when (invokeExpr) { @@ -2598,7 +2173,7 @@ class Traverser( * * @see mockStaticMethod */ - private fun mockMakeSymbolic(invokeExpr: JStaticInvokeExpr): List? { + private fun TraversalContext.mockMakeSymbolic(invokeExpr: JStaticInvokeExpr): List? { val methodSignature = invokeExpr.method.signature if (methodSignature != makeSymbolicMethod.signature && methodSignature != nonNullableMakeSymbolic.signature) return null @@ -2636,9 +2211,7 @@ class Traverser( ) } - fun attachMockListener(mockListener: MockListener) = mocker.mockListenerController?.attach(mockListener) - - private fun staticInvoke(invokeExpr: JStaticInvokeExpr): List { + private fun TraversalContext.staticInvoke(invokeExpr: JStaticInvokeExpr): List { val parameters = resolveParameters(invokeExpr.args, invokeExpr.method.parameterTypes) val result = mockMakeSymbolic(invokeExpr) ?: mockStaticMethod(invokeExpr.method, parameters) @@ -2654,7 +2227,7 @@ class Traverser( * Each target defines/reduces object type to set of concrete (not abstract, not interface) * classes with particular method implementation. */ - private fun virtualAndInterfaceInvoke( + private fun TraversalContext.virtualAndInterfaceInvoke( base: Value, methodRef: SootMethodRef, parameters: List @@ -2784,7 +2357,7 @@ class Traverser( .toList() } - private fun specialInvoke(invokeExpr: JSpecialInvokeExpr): List { + private fun TraversalContext.specialInvoke(invokeExpr: JSpecialInvokeExpr): List { val instance = resolve(invokeExpr.base) if (instance !is ReferenceValue) error("We cannot run ${invokeExpr.methodRef} on $instance") @@ -2798,10 +2371,10 @@ class Traverser( return commonInvokePart(invocation) } - private fun dynamicInvoke(invokeExpr: JDynamicInvokeExpr): List { + private fun TraversalContext.dynamicInvoke(invokeExpr: JDynamicInvokeExpr): List { workaround(HACK) { // The engine does not yet support JDynamicInvokeExpr, so switch to concrete execution if we encounter it - statesForConcreteExecution += environment.state + offerState(environment.state.withLabel(StateLabel.CONCRETE)) queuedSymbolicStateUpdates += UtFalse.asHardConstraint() return emptyList() } @@ -2812,7 +2385,7 @@ class Traverser( * * Returns results of native calls cause other calls push changes directly to path selector. */ - private fun commonInvokePart(invocation: Invocation): List { + private fun TraversalContext.commonInvokePart(invocation: Invocation): List { // First, check if there is override for the invocation itself, not for the targets val artificialMethodOverride = overrideInvocation(invocation, target = null) @@ -2889,7 +2462,7 @@ class Traverser( return overriddenResults + originResults } - private fun invoke( + private fun TraversalContext.invoke( target: InvocationTarget, parameters: List ): List = with(target.method) { @@ -2923,7 +2496,7 @@ class Traverser( globalGraph.succ(environment.state.stmt), symbolicStateUpdate ) - pathSelector.offer(stateToContinue) + offerState(stateToContinue) // we already pushed state with the fulfilled predicate, so we can just drop our branch here by // adding UtFalse to the constraints. @@ -2941,7 +2514,7 @@ class Traverser( } } - private fun utOverrideMockInvoke(target: InvocationTarget, parameters: List): List { + private fun TraversalContext.utOverrideMockInvoke(target: InvocationTarget, parameters: List): List { when (target.method.name) { utOverrideMockAlreadyVisitedMethodName -> { return listOf(MethodResult(memory.isVisited(parameters[0].addr).toBoolValue())) @@ -2959,7 +2532,7 @@ class Traverser( globalGraph.succ(environment.state.stmt), doesntThrow = true ) - pathSelector.offer(stateToContinue) + offerState(stateToContinue) queuedSymbolicStateUpdates += UtFalse.asHardConstraint() return emptyList() } @@ -2973,7 +2546,7 @@ class Traverser( hardConstraints = Le(addr, nullObjectAddr.toIntValue()).asHardConstraint() ) ) - pathSelector.offer(stateToContinue) + offerState(stateToContinue) } is ArrayValue -> { val addr = param.addr @@ -3002,7 +2575,7 @@ class Traverser( memoryUpdates = update ) ) - pathSelector.offer(stateToContinue) + offerState(stateToContinue) } } @@ -3013,7 +2586,7 @@ class Traverser( return emptyList() } utOverrideMockExecuteConcretelyMethodName -> { - statesForConcreteExecution += environment.state + offerState(environment.state.withLabel(StateLabel.CONCRETE)) queuedSymbolicStateUpdates += UtFalse.asHardConstraint() return emptyList() } @@ -3102,7 +2675,7 @@ class Traverser( * * Proceeds overridden method as non-library. */ - private fun overrideInvocation(invocation: Invocation, target: InvocationTarget?): OverrideResult { + private fun TraversalContext.overrideInvocation(invocation: Invocation, target: InvocationTarget?): OverrideResult { // If we try to override invocation itself, the target is null, and we have to process // the instance from the invocation, otherwise take the one from the target val instance = if (target == null) invocation.instance else target.instance @@ -3158,7 +2731,7 @@ class Traverser( val results = invoke(instance as ObjectValue, invocation.method, invocation.parameters) if (results.isEmpty()) { // Drop the branch and switch to concrete execution - statesForConcreteExecution += environment.state + offerState(environment.state.withLabel(StateLabel.CONCRETE)) queuedSymbolicStateUpdates += UtFalse.asHardConstraint() } return OverrideResult(success = true, results) @@ -3229,7 +2802,7 @@ class Traverser( .firstOrNull { it.canRetrieveBody() || it.isNative } } - private fun pushToPathSelector( + private fun TraversalContext.pushToPathSelector( graph: ExceptionalUnitGraph, caller: ReferenceValue?, callParameters: List, @@ -3238,7 +2811,7 @@ class Traverser( ) { globalGraph.join(environment.state.stmt, graph, !isLibraryMethod) val parametersWithThis = listOfNotNull(caller) + callParameters - pathSelector.offer( + offerState( environment.state.push( graph.head, inputArguments = ArrayDeque(parametersWithThis), @@ -3258,7 +2831,7 @@ class Traverser( doesntThrow ) - private fun resolveIfCondition(cond: BinopExpr): ResolvedCondition { + private fun TraversalContext.resolveIfCondition(cond: BinopExpr): ResolvedCondition { // We add cond.op.type for null values only. If we have condition like "null == r1" // we'll have ObjectInstance(r1::type) and ObjectInstance(r1::type) for now // For non-null values type is ignored. @@ -3352,7 +2925,7 @@ class Traverser( } } - private fun constructSoftConstraintsForCondition(cond: BinopExpr): SoftConstraintsForResolvedCondition { + private fun TraversalContext.constructSoftConstraintsForCondition(cond: BinopExpr): SoftConstraintsForResolvedCondition { var positiveCaseConstraint: UtBoolExpression? = null var negativeCaseConstraint: UtBoolExpression? = null @@ -3408,7 +2981,7 @@ class Traverser( return if (negate) mkNot(eq) else eq } - private fun nullPointerExceptionCheck(addr: UtAddrExpression) { + private fun TraversalContext.nullPointerExceptionCheck(addr: UtAddrExpression) { val canBeNull = addrEq(addr, nullObjectAddr) val canNotBeNull = mkNot(canBeNull) val notMarked = mkEq(memory.isSpeculativelyNotNull(addr), mkFalse()) @@ -3418,10 +2991,9 @@ class Traverser( implicitlyThrowException(NullPointerException(), setOf(notMarkedAndNull)) } - queuedSymbolicStateUpdates += canNotBeNull.asHardConstraint() - } + queuedSymbolicStateUpdates += canNotBeNull.asHardConstraint() } - private fun divisionByZeroCheck(denom: PrimitiveValue) { + private fun TraversalContext.divisionByZeroCheck(denom: PrimitiveValue) { val equalsToZero = Eq(denom, 0) implicitlyThrowException(ArithmeticException("/ by zero"), setOf(equalsToZero)) queuedSymbolicStateUpdates += mkNot(equalsToZero).asHardConstraint() @@ -3526,7 +3098,7 @@ class Traverser( ) } - private fun intOverflowCheck(op: BinopExpr, leftRaw: PrimitiveValue, rightRaw: PrimitiveValue) { + private fun TraversalContext.intOverflowCheck(op: BinopExpr, leftRaw: PrimitiveValue, rightRaw: PrimitiveValue) { // cast to the bigger type val sort = simpleMaxSort(leftRaw, rightRaw) as UtPrimitiveSort val left = leftRaw.expr.toPrimitiveValue(sort.type) @@ -3555,7 +3127,7 @@ class Traverser( } } - private fun indexOutOfBoundsChecks(index: PrimitiveValue, length: PrimitiveValue) { + private fun TraversalContext.indexOutOfBoundsChecks(index: PrimitiveValue, length: PrimitiveValue) { val ltZero = Lt(index, 0) implicitlyThrowException(IndexOutOfBoundsException("Less than zero"), setOf(ltZero)) @@ -3566,7 +3138,7 @@ class Traverser( queuedSymbolicStateUpdates += mkNot(geLength).asHardConstraint() } - private fun negativeArraySizeCheck(vararg sizes: PrimitiveValue) { + private fun TraversalContext.negativeArraySizeCheck(vararg sizes: PrimitiveValue) { val ltZero = mkOr(sizes.map { Lt(it, 0) }) implicitlyThrowException(NegativeArraySizeException("Less than zero"), setOf(ltZero)) queuedSymbolicStateUpdates += mkNot(ltZero).asHardConstraint() @@ -3578,7 +3150,7 @@ class Traverser( * Note: if we have the valueToCast.addr related to some parameter with addr p_0, and that parameter's type is a parameterizedType, * we ignore potential exception throwing if the typeAfterCast is one of the generics included in that type. */ - private fun classCastExceptionCheck(valueToCast: ReferenceValue, typeAfterCast: Type) { + private fun TraversalContext.classCastExceptionCheck(valueToCast: ReferenceValue, typeAfterCast: Type) { val baseTypeAfterCast = if (typeAfterCast is ArrayType) typeAfterCast.baseType else typeAfterCast val addr = valueToCast.addr @@ -3625,7 +3197,7 @@ class Traverser( queuedSymbolicStateUpdates += mkOr(isExpression, nullEqualityConstraint).asHardConstraint() } - private fun implicitlyThrowException( + private fun TraversalContext.implicitlyThrowException( exception: Exception, conditions: Set, softConditions: Set = emptySet() @@ -3642,7 +3214,7 @@ class Traverser( + softConditions.asSoftConstraint() ) globalGraph.registerImplicitEdge(nextState.lastEdge!!) - pathSelector.offer(nextState) + offerState(nextState) } } @@ -3656,22 +3228,6 @@ class Traverser( } } - /** - * Collects entry method statement path for ML. Eliminates duplicated statements, e.g. assignment with invocation - * in right part. - */ - private fun entryMethodPath(state: ExecutionState): MutableList { - val entryPath = mutableListOf() - state.fullPath().forEach { step -> - // TODO: replace step.stmt in methodUnderAnalysisStmts with step.depth == 0 - // when fix SAT-812: [JAVA] Wrong depth when exception thrown - if (step.stmt in methodUnderAnalysisStmts && step.stmt !== entryPath.lastOrNull()?.stmt) { - entryPath += step - } - } - return entryPath - } - private fun constructConstraintForType(value: ReferenceValue, possibleTypes: Collection): UtBoolExpression { val preferredTypes = typeResolver.findTopRatedTypes(possibleTypes, take = NUMBER_OF_PREFERRED_TYPES) val mostCommonType = preferredTypes.singleOrNull() ?: OBJECT_TYPE @@ -3745,7 +3301,7 @@ class Traverser( ) } - private suspend fun FlowCollector.processResult(symbolicResult: SymbolicResult) { + private fun TraversalContext.processResult(symbolicResult: SymbolicResult) { val resolvedParameters = environment.state.parameters.map { it.value } //choose types that have biggest priority @@ -3800,112 +3356,23 @@ class Traverser( MemoryUpdate() // all memory updates are already added in [environment.state] } val stateToOffer = state.pop(methodResult.copy(symbolicStateUpdate = updates.asUpdate())) - pathSelector.offer(stateToOffer) + offerState(stateToOffer) logger.trace { "processResult<${environment.method.signature}> return from nested method" } return } //toplevel method - val terminalExecutionState = - state.copy( - methodResult = methodResult, // a way to put SymbolicResult into terminal state - label = StateLabel.TERMINAL - ) - consumeTerminalState(terminalExecutionState) - } - - private suspend fun FlowCollector.consumeTerminalState( - state: ExecutionState, - ) { - // some checks to be sure the state is correct - require(state.label == StateLabel.TERMINAL) { "Can't process non-terminal state!" } - require(!state.isInNestedMethod()) { "The state has to correspond to the MUT"} - - val memory = state.memory - val solver = state.solver - val parameters = state.parameters.map { it.value } - val symbolicResult = requireNotNull(state.methodResult?.symbolicResult) { "The state must have symbolicResult"} - // it's free to make a check, because in the result is SAT, it should be already cached - val holder = requireNotNull(solver.check(respectSoft = true) as? UtSolverStatusSAT) { "The state must be SAT!" } - - val predictedTestName = Predictors.testName.predict(state.path) - Predictors.testName.provide(state.path, predictedTestName, "") - - val resolver = - Resolver(hierarchy, memory, typeRegistry, typeResolver, holder, methodUnderTest, softMaxArraySize) - - val (modelsBefore, modelsAfter, instrumentation) = resolver.resolveModels(parameters) - - val symbolicExecutionResult = resolver.resolveResult(symbolicResult) - - val stateBefore = modelsBefore.constructStateForMethod(methodUnderTest) - val stateAfter = modelsAfter.constructStateForMethod(methodUnderTest) - require(stateBefore.parameters.size == stateAfter.parameters.size) - - val symbolicUtExecution = UtExecution( - stateBefore, - stateAfter, - symbolicExecutionResult, - instrumentation, - entryMethodPath(environment.state), - state.fullPath() + val terminalExecutionState = state.copy( + methodResult = methodResult, // the way to put SymbolicResult into terminal state + label = StateLabel.TERMINAL ) - - globalGraph.traversed(state) - - if (!UtSettings.useConcreteExecution || - // Can't execute concretely because overflows do not cause actual exceptions. - // Still, we need overflows to act as implicit exceptions. - (UtSettings.treatOverflowAsError && symbolicExecutionResult is UtOverflowFailure) - ) { - logger.debug { - "processResult<${methodUnderTest}>: no concrete execution allowed, " + - "emit purely symbolic result $symbolicUtExecution" - } - emit(symbolicUtExecution) - return - } - - //It's possible that symbolic and concrete stateAfter/results are diverged. - //So we trust concrete results more. - try { - logger.debug().bracket("processResult<$methodUnderTest>: concrete execution") { - - //this can throw CancellationException - val concreteExecutionResult = concreteExecutor.executeConcretely( - methodUnderTest, - stateBefore, - instrumentation - ) - - workaround(REMOVE_ANONYMOUS_CLASSES) { - concreteExecutionResult.result.onSuccess { - if (it.classId.isAnonymous) { - logger.debug("Anonymous class found as a concrete result, symbolic one will be returned") - emit(symbolicUtExecution) - return - } - } - } - - val concolicUtExecution = symbolicUtExecution.copy( - stateAfter = concreteExecutionResult.stateAfter, - result = concreteExecutionResult.result, - coverage = concreteExecutionResult.coverage - ) - - emit(concolicUtExecution) - logger.debug { "processResult<${methodUnderTest}>: returned $concolicUtExecution" } - } - } catch (e: ConcreteExecutionFailureException) { - emitFailedConcreteExecutionResult(stateBefore, e) - } + offerState(terminalExecutionState) } - private fun ReturnStmt.symbolicSuccess(): SymbolicSuccess { + private fun TraversalContext.symbolicSuccess(stmt: ReturnStmt): SymbolicSuccess { val type = environment.method.returnType - val value = when (val instance = resolve(op, type)) { + val value = when (val instance = resolve(stmt.op, type)) { is PrimitiveValue -> instance.cast(type) else -> instance } @@ -3926,69 +3393,4 @@ class Traverser( queuedSymbolicStateUpdates = prevSymbolicStateUpdate } } -} - -private fun ResolvedModels.constructStateForMethod(methodUnderTest: UtMethod<*>): EnvironmentModels { - val (thisInstanceBefore, paramsBefore) = when { - methodUnderTest.isStatic -> null to parameters - methodUnderTest.isConstructor -> null to parameters.drop(1) - else -> parameters.first() to parameters.drop(1) - } - return EnvironmentModels(thisInstanceBefore, paramsBefore, statics) -} - -private suspend fun ConcreteExecutor.executeConcretely( - methodUnderTest: UtMethod<*>, - stateBefore: EnvironmentModels, - instrumentation: List -): UtConcreteExecutionResult = executeAsync( - methodUnderTest.callable, - arrayOf(), - parameters = UtConcreteExecutionData(stateBefore, instrumentation) -).convertToAssemble(methodUnderTest) - -/** - * Before pushing our states for concrete execution, we have to be sure that every state is consistent. - * For now state could be inconsistent in case MUT parameters are wrappers that are not fully visited. - * For example, not fully visited map can contain duplicate keys that leads to incorrect behaviour. - * To prevent it, we need to add visited constraint for each MUT parameter-wrapper in state. - */ -private fun ExecutionState.withWrapperConsistencyChecks(): ExecutionState { - val visitedConstraints = mutableSetOf() - val methodUnderTestWrapperParameters = methodUnderTestParameters.filterNot { it.asWrapperOrNull == null } - val methodUnderTestWrapperParametersAddresses = methodUnderTestWrapperParameters.map { it.addr }.toSet() - - if (methodUnderTestWrapperParameters.isEmpty()) { - return this - } - - // make consistency checks for parameters-wrappers ... - methodUnderTestWrapperParameters.forEach { symbolicValue -> - symbolicValue.asWrapperOrNull?.let { - makeWrapperConsistencyCheck(symbolicValue, memory, visitedConstraints) - } - } - - // ... and all locals that depends on these parameters-wrappers - val localReferenceValues = localVariableMemory - .localValues - .filterIsInstance() - .filter { it.addr.internal is UtArraySelectExpression } - localReferenceValues.forEach { - val theMostNestedAddr = findTheMostNestedAddr(it.addr.internal as UtArraySelectExpression) - if (theMostNestedAddr in methodUnderTestWrapperParametersAddresses) { - makeWrapperConsistencyCheck(it, memory, visitedConstraints) - } - } - - return copy(symbolicState = symbolicState + visitedConstraints.asHardConstraint()) -} - -private fun makeWrapperConsistencyCheck( - symbolicValue: SymbolicValue, - memory: Memory, - visitedConstraints: MutableSet -) { - val visitedSelectExpression = memory.isVisited(symbolicValue.addr) - visitedConstraints += mkEq(visitedSelectExpression, mkInt(1)) -} +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt new file mode 100644 index 0000000000..77cb250611 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -0,0 +1,654 @@ +package org.utbot.engine + +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.isActive +import kotlinx.coroutines.job +import kotlinx.coroutines.yield +import mu.KotlinLogging +import org.utbot.analytics.EngineAnalyticsContext +import org.utbot.analytics.FeatureProcessor +import org.utbot.analytics.Predictors +import org.utbot.common.WorkaroundReason.REMOVE_ANONYMOUS_CLASSES +import org.utbot.common.bracket +import org.utbot.common.debug +import org.utbot.common.workaround +import org.utbot.engine.MockStrategy.NO_MOCKS +import org.utbot.engine.pc.UtArraySelectExpression +import org.utbot.engine.pc.UtBoolExpression +import org.utbot.engine.pc.UtContextInitializer +import org.utbot.engine.pc.UtSolver +import org.utbot.engine.pc.UtSolverStatusSAT +import org.utbot.engine.pc.findTheMostNestedAddr +import org.utbot.engine.pc.mkEq +import org.utbot.engine.pc.mkInt +import org.utbot.engine.selectors.PathSelector +import org.utbot.engine.selectors.StrategyOption +import org.utbot.engine.selectors.coveredNewSelector +import org.utbot.engine.selectors.cpInstSelector +import org.utbot.engine.selectors.forkDepthSelector +import org.utbot.engine.selectors.inheritorsSelector +import org.utbot.engine.selectors.nnRewardGuidedSelector +import org.utbot.engine.selectors.nurs.NonUniformRandomSearch +import org.utbot.engine.selectors.pollUntilFastSAT +import org.utbot.engine.selectors.randomPathSelector +import org.utbot.engine.selectors.randomSelector +import org.utbot.engine.selectors.strategies.GraphViz +import org.utbot.engine.selectors.subpathGuidedSelector +import org.utbot.engine.symbolic.SymbolicState +import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.util.mockListeners.MockListener +import org.utbot.engine.util.mockListeners.MockListenerController +import org.utbot.framework.PathSelectorType +import org.utbot.framework.UtSettings +import org.utbot.framework.UtSettings.checkSolverTimeoutMillis +import org.utbot.framework.UtSettings.enableFeatureProcess +import org.utbot.framework.UtSettings.pathSelectorStepsLimit +import org.utbot.framework.UtSettings.pathSelectorType +import org.utbot.framework.UtSettings.processUnknownStatesDuringConcreteExecution +import org.utbot.framework.UtSettings.useDebugVisualization +import org.utbot.framework.concrete.UtConcreteExecutionData +import org.utbot.framework.concrete.UtConcreteExecutionResult +import org.utbot.framework.concrete.UtExecutionInstrumentation +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConcreteExecutionFailureException +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.Instruction +import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.Step +import org.utbot.framework.plugin.api.UtConcreteExecutionFailure +import org.utbot.framework.plugin.api.UtError +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtInstrumentation +import org.utbot.framework.plugin.api.UtMethod +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtOverflowFailure +import org.utbot.framework.plugin.api.UtResult +import org.utbot.framework.plugin.api.graph +import org.utbot.framework.plugin.api.jimpleBody +import org.utbot.framework.plugin.api.onSuccess +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.framework.plugin.api.util.description +import org.utbot.fuzzer.FallbackModelProvider +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzer.collectConstantsForFuzzer +import org.utbot.fuzzer.defaultModelProviders +import org.utbot.fuzzer.fuzz +import org.utbot.fuzzer.names.MethodBasedNameSuggester +import org.utbot.fuzzer.names.ModelBasedNameSuggester +import org.utbot.instrumentation.ConcreteExecutor +import soot.jimple.Stmt +import soot.tagkit.ParamNamesTag +import java.lang.reflect.Method +import kotlin.system.measureTimeMillis + +val logger = KotlinLogging.logger {} +val pathLogger = KotlinLogging.logger(logger.name + ".path") + +//in future we should put all timeouts here +class EngineController { + var paused: Boolean = false + var executeConcretely: Boolean = false + var stop: Boolean = false + var job: Job? = null +} + +//for debugging purpose only +private var stateSelectedCount = 0 + +//all id values of synthetic default models must be greater that for real ones +private var nextDefaultModelId = 1500_000_000 + +private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegistry) = + when (pathSelectorType) { + PathSelectorType.COVERED_NEW_SELECTOR -> coveredNewSelector(graph) { + withStepsLimit(pathSelectorStepsLimit) + } + PathSelectorType.INHERITORS_SELECTOR -> inheritorsSelector(graph, typeRegistry) { + withStepsLimit(pathSelectorStepsLimit) + } + PathSelectorType.SUBPATH_GUIDED_SELECTOR -> subpathGuidedSelector(graph, StrategyOption.DISTANCE) { + withStepsLimit(pathSelectorStepsLimit) + } + PathSelectorType.CPI_SELECTOR -> cpInstSelector(graph, StrategyOption.DISTANCE) { + withStepsLimit(pathSelectorStepsLimit) + } + PathSelectorType.FORK_DEPTH_SELECTOR -> forkDepthSelector(graph, StrategyOption.DISTANCE) { + withStepsLimit(pathSelectorStepsLimit) + } + PathSelectorType.NN_REWARD_GUIDED_SELECTOR -> nnRewardGuidedSelector(graph, StrategyOption.DISTANCE) { + withStepsLimit(pathSelectorStepsLimit) + } + PathSelectorType.RANDOM_SELECTOR -> randomSelector(graph, StrategyOption.DISTANCE) { + withStepsLimit(pathSelectorStepsLimit) + } + PathSelectorType.RANDOM_PATH_SELECTOR -> randomPathSelector(graph, StrategyOption.DISTANCE) { + withStepsLimit(pathSelectorStepsLimit) + } + } + +class UtBotSymbolicEngine( + private val controller: EngineController, + private val methodUnderTest: UtMethod<*>, + classpath: String, + dependencyPaths: String, + mockStrategy: MockStrategy = NO_MOCKS, + chosenClassesToMockAlways: Set, + private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis +) : UtContextInitializer() { + + private val graph = jimpleBody(methodUnderTest).also { + logger.trace { "JIMPLE for $methodUnderTest:\n$this" } + }.graph() + + private val methodUnderAnalysisStmts: Set = graph.stmts.toSet() + private val globalGraph = InterProceduralUnitGraph(graph) + private val typeRegistry: TypeRegistry = TypeRegistry() + private val pathSelector: PathSelector = pathSelector(globalGraph, typeRegistry) + + internal val hierarchy: Hierarchy = Hierarchy(typeRegistry) + + // TODO HACK violation of encapsulation + internal val typeResolver: TypeResolver = TypeResolver(typeRegistry, hierarchy) + + private val classUnderTest: ClassId = methodUnderTest.clazz.id + + private val mocker: Mocker = Mocker( + mockStrategy, + classUnderTest, + hierarchy, + chosenClassesToMockAlways, + MockListenerController(controller) + ) + + fun attachMockListener(mockListener: MockListener) = mocker.mockListenerController?.attach(mockListener) + + private val statesForConcreteExecution: MutableList = mutableListOf() + + private val traverser = Traverser( + methodUnderTest, + typeRegistry, + hierarchy, + typeResolver, + globalGraph, + mocker, + ) + + //HACK (long strings) + internal var softMaxArraySize = 40 + + private val concreteExecutor = + ConcreteExecutor( + UtExecutionInstrumentation, + classpath, + dependencyPaths + ).apply { this.classLoader = utContext.classLoader } + + private val featureProcessor: FeatureProcessor? = + if (enableFeatureProcess) EngineAnalyticsContext.featureProcessorFactory(globalGraph) else null + + + private val trackableResources: MutableSet = mutableSetOf() + + private fun postTraverse() { + for (r in trackableResources) + try { + r.close() + } catch (e: Throwable) { + logger.error(e) { "Closing resource failed" } + } + trackableResources.clear() + featureProcessor?.dumpFeatures() + } + + private suspend fun preTraverse() { + //fixes leak in useless Context() created in AutoCloseable() + close() + if (!currentCoroutineContext().isActive) return + stateSelectedCount = 0 + } + + fun traverse(): Flow = traverseImpl() + .onStart { preTraverse() } + .onCompletion { postTraverse() } + + private fun traverseImpl(): Flow = flow { + + require(trackableResources.isEmpty()) + + if (useDebugVisualization) GraphViz(globalGraph, pathSelector) + + val initStmt = graph.head + val initState = ExecutionState( + initStmt, + SymbolicState(UtSolver(typeRegistry, trackableResources, solverTimeoutInMillis)), + executionStack = persistentListOf(ExecutionStackElement(null, method = graph.body.method)) + ) + + pathSelector.offer(initState) + + pathSelector.use { + + while (currentCoroutineContext().isActive) { + if (controller.stop) + break + + if (controller.paused) { + try { + yield() + } catch (e: CancellationException) { //todo in future we should just throw cancellation + break + } + continue + } + + stateSelectedCount++ + pathLogger.trace { + "traverse<$methodUnderTest>: choosing next state($stateSelectedCount), " + + "queue size=${(pathSelector as? NonUniformRandomSearch)?.size ?: -1}" + } + + if (controller.executeConcretely || statesForConcreteExecution.isNotEmpty()) { + val state = pathSelector.pollUntilFastSAT() + ?: statesForConcreteExecution.pollUntilSat(processUnknownStatesDuringConcreteExecution) + ?: break + // This state can contain inconsistent wrappers - for example, Map with keys but missing values. + // We cannot use withWrapperConsistencyChecks here because it needs solver to work. + // So, we have to process such cases accurately in wrappers resolving. + + logger.trace { "executing $state concretely..." } + + + logger.debug().bracket("concolicStrategy<$methodUnderTest>: execute concretely") { + val resolver = Resolver( + hierarchy, + state.memory, + typeRegistry, + typeResolver, + state.solver.lastStatus as UtSolverStatusSAT, + methodUnderTest, + softMaxArraySize + ) + + val resolvedParameters = state.methodUnderTestParameters + val (modelsBefore, _, instrumentation) = resolver.resolveModels(resolvedParameters) + val stateBefore = modelsBefore.constructStateForMethod(methodUnderTest) + + try { + val concreteExecutionResult = + concreteExecutor.executeConcretely(methodUnderTest, stateBefore, instrumentation) + + val concreteUtExecution = UtExecution( + stateBefore, + concreteExecutionResult.stateAfter, + concreteExecutionResult.result, + instrumentation, + mutableListOf(), + listOf(), + concreteExecutionResult.coverage + ) + emit(concreteUtExecution) + + logger.debug { "concolicStrategy<${methodUnderTest}>: returned $concreteUtExecution" } + } catch (e: CancellationException) { + logger.debug(e) { "Cancellation happened" } + } catch (e: ConcreteExecutionFailureException) { + emitFailedConcreteExecutionResult(stateBefore, e) + } catch (e: Throwable) { + emit(UtError("Concrete execution failed", e)) + } + } + + } else { + val state = pathSelector.poll() + + // state is null in case states queue is empty + // or path selector exceed some limits (steps limit, for example) + if (state == null) { + // check do we have remaining states that we can execute concretely + val pathSelectorStatesForConcreteExecution = pathSelector + .remainingStatesForConcreteExecution + .map { it.withWrapperConsistencyChecks() } + if (pathSelectorStatesForConcreteExecution.isNotEmpty()) { + statesForConcreteExecution += pathSelectorStatesForConcreteExecution + logger.debug { + "${pathSelectorStatesForConcreteExecution.size} remaining states " + + "were moved from path selector for concrete execution" + } + continue // the next step in while loop processes concrete states + } else { + break + } + } + + state.executingTime += measureTimeMillis { + val newStates = try { + traverser.traverse(state) + } catch (ex: Throwable) { + emit(UtError(ex.description, ex)) + return@measureTimeMillis + } + for (newState in newStates) { + when (newState.label) { + StateLabel.INTERMEDIATE -> pathSelector.offer(newState) + StateLabel.CONCRETE -> statesForConcreteExecution.add(newState) + StateLabel.TERMINAL -> consumeTerminalState(newState) + } + } + + // Here job can be cancelled from within traverse, e.g. by using force mocking without Mockito. + // So we need to make it throw CancelledException by method below: + currentCoroutineContext().job.ensureActive() + } + globalGraph.visitNode(state) + } + } + } + } + + + /** + * Run fuzzing flow. + * + * @param until is used by fuzzer to cancel all tasks if the current time is over this value + * @param modelProvider provides model values for a method + */ + fun fuzzing(until: Long = Long.MAX_VALUE, modelProvider: (ModelProvider) -> ModelProvider = { it }) = flow { + val executableId = if (methodUnderTest.isConstructor) { + methodUnderTest.javaConstructor!!.executableId + } else { + methodUnderTest.javaMethod!!.executableId + } + + val isFuzzable = executableId.parameters.all { classId -> + classId != Method::class.java.id && // causes the child process crash at invocation + classId != Class::class.java.id // causes java.lang.IllegalAccessException: java.lang.Class at sun.misc.Unsafe.allocateInstance(Native Method) + } + if (!isFuzzable) { + return@flow + } + + val fallbackModelProvider = FallbackModelProvider { nextDefaultModelId++ } + + val thisInstance = when { + methodUnderTest.isStatic -> null + methodUnderTest.isConstructor -> if ( + methodUnderTest.clazz.isAbstract || // can't instantiate abstract class + methodUnderTest.clazz.java.isEnum // can't reflectively create enum objects + ) { + return@flow + } else { + null + } + else -> { + fallbackModelProvider.toModel(methodUnderTest.clazz).apply { + if (this is UtNullModel) { // it will definitely fail because of NPE, + return@flow + } + } + } + } + + val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph)).apply { + compilableName = if (methodUnderTest.isMethod) executableId.name else null + val names = graph.body.method.tags.filterIsInstance().firstOrNull()?.names + parameterNameMap = { index -> names?.getOrNull(index) } + } + val modelProviderWithFallback = modelProvider(defaultModelProviders { nextDefaultModelId++ }).withFallback(fallbackModelProvider::toModel) + val coveredInstructionTracker = mutableSetOf() + var attempts = UtSettings.fuzzingMaxAttempts + fuzz(methodUnderTestDescription, modelProviderWithFallback).forEach { values -> + if (System.currentTimeMillis() >= until) { + logger.info { "Fuzzing overtime: $methodUnderTest" } + return@flow + } + + val initialEnvironmentModels = EnvironmentModels(thisInstance, values.map { it.model }, mapOf()) + + try { + val concreteExecutionResult = + concreteExecutor.executeConcretely(methodUnderTest, initialEnvironmentModels, listOf()) + + workaround(REMOVE_ANONYMOUS_CLASSES) { + concreteExecutionResult.result.onSuccess { + if (it.classId.isAnonymous) { + logger.debug("Anonymous class found as a concrete result, symbolic one will be returned") + return@flow + } + } + } + + if (!coveredInstructionTracker.addAll(concreteExecutionResult.coverage.coveredInstructions)) { + if (--attempts < 0) { + return@flow + } + } + + val nameSuggester = sequenceOf(ModelBasedNameSuggester(), MethodBasedNameSuggester()) + val testMethodName = try { + nameSuggester.flatMap { it.suggest(methodUnderTestDescription, values, concreteExecutionResult.result) }.firstOrNull() + } catch (t: Throwable) { + logger.error(t) { "Cannot create suggested test name for ${methodUnderTest.displayName}" } + null + } + + emit( + UtExecution( + stateBefore = initialEnvironmentModels, + stateAfter = concreteExecutionResult.stateAfter, + result = concreteExecutionResult.result, + instrumentation = emptyList(), + path = mutableListOf(), + fullPath = emptyList(), + coverage = concreteExecutionResult.coverage, + testMethodName = testMethodName?.testName, + displayName = testMethodName?.displayName + ) + ) + } catch (e: CancellationException) { + logger.debug { "Cancelled by timeout" } + } catch (e: ConcreteExecutionFailureException) { + emitFailedConcreteExecutionResult(initialEnvironmentModels, e) + } catch (e: Throwable) { + emit(UtError("Default concrete execution failed", e)) + } + } + } + + private suspend fun FlowCollector.emitFailedConcreteExecutionResult( + stateBefore: EnvironmentModels, + e: ConcreteExecutionFailureException + ) { + val failedConcreteExecution = UtExecution( + stateBefore = stateBefore, + stateAfter = MissingState, + result = UtConcreteExecutionFailure(e), + instrumentation = emptyList(), + path = mutableListOf(), + fullPath = listOf() + ) + + emit(failedConcreteExecution) + } + + private suspend fun FlowCollector.consumeTerminalState( + state: ExecutionState, + ) { + // some checks to be sure the state is correct + require(state.label == StateLabel.TERMINAL) { "Can't process non-terminal state!" } + require(!state.isInNestedMethod()) { "The state has to correspond to the MUT" } + + val memory = state.memory + val solver = state.solver + val parameters = state.parameters.map { it.value } + val symbolicResult = requireNotNull(state.methodResult?.symbolicResult) { "The state must have symbolicResult" } + // it's free to make a check, because in the result is SAT, it should be already cached + val holder = requireNotNull(solver.check(respectSoft = true) as? UtSolverStatusSAT) { "The state must be SAT!" } + + val predictedTestName = Predictors.testName.predict(state.path) + Predictors.testName.provide(state.path, predictedTestName, "") + + // resolving + val resolver = + Resolver(hierarchy, memory, typeRegistry, typeResolver, holder, methodUnderTest, softMaxArraySize) + + val (modelsBefore, modelsAfter, instrumentation) = resolver.resolveModels(parameters) + + val symbolicExecutionResult = resolver.resolveResult(symbolicResult) + + val stateBefore = modelsBefore.constructStateForMethod(methodUnderTest) + val stateAfter = modelsAfter.constructStateForMethod(methodUnderTest) + require(stateBefore.parameters.size == stateAfter.parameters.size) + + val symbolicUtExecution = UtExecution( + stateBefore, + stateAfter, + symbolicExecutionResult, + instrumentation, + entryMethodPath(state), + state.fullPath() + ) + + globalGraph.traversed(state) + + if (!UtSettings.useConcreteExecution || + // Can't execute concretely because overflows do not cause actual exceptions. + // Still, we need overflows to act as implicit exceptions. + (UtSettings.treatOverflowAsError && symbolicExecutionResult is UtOverflowFailure) + ) { + logger.debug { + "processResult<${methodUnderTest}>: no concrete execution allowed, " + + "emit purely symbolic result $symbolicUtExecution" + } + emit(symbolicUtExecution) + return + } + + //It's possible that symbolic and concrete stateAfter/results are diverged. + //So we trust concrete results more. + try { + logger.debug().bracket("processResult<$methodUnderTest>: concrete execution") { + + //this can throw CancellationException + val concreteExecutionResult = concreteExecutor.executeConcretely( + methodUnderTest, + stateBefore, + instrumentation + ) + + workaround(REMOVE_ANONYMOUS_CLASSES) { + concreteExecutionResult.result.onSuccess { + if (it.classId.isAnonymous) { + logger.debug("Anonymous class found as a concrete result, symbolic one will be returned") + emit(symbolicUtExecution) + return + } + } + } + + val concolicUtExecution = symbolicUtExecution.copy( + stateAfter = concreteExecutionResult.stateAfter, + result = concreteExecutionResult.result, + coverage = concreteExecutionResult.coverage + ) + + emit(concolicUtExecution) + logger.debug { "processResult<${methodUnderTest}>: returned $concolicUtExecution" } + } + } catch (e: ConcreteExecutionFailureException) { + emitFailedConcreteExecutionResult(stateBefore, e) + } + } + + /** + * Collects entry method statement path for ML. Eliminates duplicated statements, e.g. assignment with invocation + * in right part. + */ + private fun entryMethodPath(state: ExecutionState): MutableList { + val entryPath = mutableListOf() + state.fullPath().forEach { step -> + // TODO: replace step.stmt in methodUnderAnalysisStmts with step.depth == 0 + // when fix SAT-812: [JAVA] Wrong depth when exception thrown + if (step.stmt in methodUnderAnalysisStmts && step.stmt !== entryPath.lastOrNull()?.stmt) { + entryPath += step + } + } + return entryPath + } +} + +private fun ResolvedModels.constructStateForMethod(methodUnderTest: UtMethod<*>): EnvironmentModels { + val (thisInstanceBefore, paramsBefore) = when { + methodUnderTest.isStatic -> null to parameters + methodUnderTest.isConstructor -> null to parameters.drop(1) + else -> parameters.first() to parameters.drop(1) + } + return EnvironmentModels(thisInstanceBefore, paramsBefore, statics) +} + +private suspend fun ConcreteExecutor.executeConcretely( + methodUnderTest: UtMethod<*>, + stateBefore: EnvironmentModels, + instrumentation: List +): UtConcreteExecutionResult = executeAsync( + methodUnderTest.callable, + arrayOf(), + parameters = UtConcreteExecutionData(stateBefore, instrumentation) +).convertToAssemble(methodUnderTest) + +/** + * Before pushing our states for concrete execution, we have to be sure that every state is consistent. + * For now state could be inconsistent in case MUT parameters are wrappers that are not fully visited. + * For example, not fully visited map can contain duplicate keys that leads to incorrect behaviour. + * To prevent it, we need to add visited constraint for each MUT parameter-wrapper in state. + */ +private fun ExecutionState.withWrapperConsistencyChecks(): ExecutionState { + val visitedConstraints = mutableSetOf() + val methodUnderTestWrapperParameters = methodUnderTestParameters.filterNot { it.asWrapperOrNull == null } + val methodUnderTestWrapperParametersAddresses = methodUnderTestWrapperParameters.map { it.addr }.toSet() + + if (methodUnderTestWrapperParameters.isEmpty()) { + return this + } + + // make consistency checks for parameters-wrappers ... + methodUnderTestWrapperParameters.forEach { symbolicValue -> + symbolicValue.asWrapperOrNull?.let { + makeWrapperConsistencyCheck(symbolicValue, memory, visitedConstraints) + } + } + + // ... and all locals that depends on these parameters-wrappers + val localReferenceValues = localVariableMemory + .localValues + .filterIsInstance() + .filter { it.addr.internal is UtArraySelectExpression } + localReferenceValues.forEach { + val theMostNestedAddr = findTheMostNestedAddr(it.addr.internal as UtArraySelectExpression) + if (theMostNestedAddr in methodUnderTestWrapperParametersAddresses) { + makeWrapperConsistencyCheck(it, memory, visitedConstraints) + } + } + + return copy(symbolicState = symbolicState + visitedConstraints.asHardConstraint()) +} + +private fun makeWrapperConsistencyCheck( + symbolicValue: SymbolicValue, + memory: Memory, + visitedConstraints: MutableSet +) { + val visitedSelectExpression = memory.isVisited(symbolicValue.addr) + visitedConstraints += mkEq(visitedSelectExpression, mkInt(1)) +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt index a0a5bc5a50..d9229b324c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt @@ -8,7 +8,6 @@ import org.utbot.common.trace import org.utbot.engine.EngineController import org.utbot.engine.MockStrategy import org.utbot.engine.Mocker -import org.utbot.engine.Traverser import org.utbot.engine.jimpleBody import org.utbot.engine.pureJavaSignature import org.utbot.framework.TestSelectionStrategyType @@ -48,6 +47,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.yield import mu.KotlinLogging +import org.utbot.engine.UtBotSymbolicEngine import soot.Scene import soot.jimple.JimpleBody import soot.toolkits.graph.ExceptionalUnitGraph @@ -198,10 +198,10 @@ object UtBotTestCaseGenerator : TestCaseGenerator { mockStrategy: MockStrategyApi, chosenClassesToMockAlways: Set, executionTimeEstimator: ExecutionTimeEstimator - ): Traverser { + ): UtBotSymbolicEngine { // TODO: create classLoader from buildDir/classpath and migrate from UtMethod to MethodId? logger.debug("Starting symbolic execution for $method --$mockStrategy--") - return Traverser( + return UtBotSymbolicEngine( controller, method, classpathForEngine, @@ -212,7 +212,7 @@ object UtBotTestCaseGenerator : TestCaseGenerator { ) } - private fun createDefaultFlow(engine: Traverser): Flow { + private fun createDefaultFlow(engine: UtBotSymbolicEngine): Flow { var flow = engine.traverse() if (UtSettings.useFuzzing) { flow = flowOf( @@ -261,7 +261,7 @@ object UtBotTestCaseGenerator : TestCaseGenerator { mockStrategy: MockStrategyApi, chosenClassesToMockAlways: Set = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id }, methodsGenerationTimeout: Long = utBotGenerationTimeoutInMillis, - generate: (engine: Traverser) -> Flow = ::createDefaultFlow + generate: (engine: UtBotSymbolicEngine) -> Flow = ::createDefaultFlow ): List { if (isCanceled()) return methods.map { UtTestCase(it) } From 8ca4645ce92b190ef02242294cb8c83c0c328e4d Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 27 Jun 2022 11:22:56 +0300 Subject: [PATCH 16/19] Change logger back --- utbot-framework/src/test/resources/log4j2.xml | 2 +- utbot-junit-contest/src/main/resources/log4j2.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utbot-framework/src/test/resources/log4j2.xml b/utbot-framework/src/test/resources/log4j2.xml index c3330409ac..11a2d0701c 100644 --- a/utbot-framework/src/test/resources/log4j2.xml +++ b/utbot-framework/src/test/resources/log4j2.xml @@ -17,7 +17,7 @@ - + diff --git a/utbot-junit-contest/src/main/resources/log4j2.xml b/utbot-junit-contest/src/main/resources/log4j2.xml index 98ea5cf5d2..fce466c24d 100644 --- a/utbot-junit-contest/src/main/resources/log4j2.xml +++ b/utbot-junit-contest/src/main/resources/log4j2.xml @@ -19,7 +19,7 @@ - + From c266e98b2c45f72f089fc368b40792c683694378 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 27 Jun 2022 11:53:29 +0300 Subject: [PATCH 17/19] Fix comments --- .../main/java/org/utbot/engine/overrides/UtArrayMock.java | 2 +- .../src/main/kotlin/org/utbot/engine/ExecutionState.kt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java index 12fc304e6e..eb88456a6e 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java @@ -5,7 +5,7 @@ /** * Auxiliary class with static methods without implementation. - * These static methods are just markers for {@link org.utbot.engine.UtBotSymbolicEngine}., + * These static methods are just markers for {@link org.utbot.engine.Traverser}., * to do some corresponding behavior, that can't be represent * with java instructions. *

diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt index 157636ef5f..d56868ec87 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt @@ -17,6 +17,8 @@ import org.utbot.framework.plugin.api.Step import soot.SootMethod import soot.jimple.Stmt import java.util.Objects +import org.utbot.engine.symbolic.Assumption +import org.utbot.framework.plugin.api.UtExecution const val RETURN_DECISION_NUM = -1 const val CALL_DECISION_NUM = -2 @@ -29,11 +31,11 @@ data class Edge(val src: Stmt, val dst: Stmt, val decisionNum: Int) * [INTERMEDIATE] is a label for an intermediate state which is suitable for further symbolic analysis. * * [TERMINAL] is a label for a terminal state from which we might (or might not) execute concretely and construct - * UtExecution. This state represents the final state of the program execution, that is a throw or return from the outer + * [UtExecution]. This state represents the final state of the program execution, that is a throw or return from the outer * method. * * [CONCRETE] is a label for a state which is not suitable for further symbolic analysis and it is also not a terminal - * state. Such states are only suitable for a concrete execution and may appear from Assumptions constraints + * state. Such states are only suitable for a concrete execution and may appear from [Assumption]s. */ enum class StateLabel { INTERMEDIATE, From 3f01700e2594d0ac9f76dfe33d8a46a6851616e2 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Wed, 29 Jun 2022 00:09:01 +0300 Subject: [PATCH 18/19] Fix strange behaviour --- .../main/kotlin/org/utbot/engine/Traverser.kt | 16 +++++++++------- .../org/utbot/engine/UtBotSymbolicEngine.kt | 3 +-- 2 files changed, 10 insertions(+), 9 deletions(-) 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 eab1011f84..b8122d16b7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -3330,12 +3330,12 @@ class Traverser( queuedSymbolicStateUpdates += mkNot(mkEq(symbolicResult.value.addr, nullObjectAddr)).asHardConstraint() } - val state = environment.state.update(queuedSymbolicStateUpdates) - val memory = state.memory - val solver = state.solver + val symbolicState = environment.state.symbolicState + queuedSymbolicStateUpdates + val memory = symbolicState.memory + val solver = symbolicState.solver //no need to respect soft constraints in NestedMethod - val holder = solver.check(respectSoft = !state.isInNestedMethod()) + val holder = solver.check(respectSoft = !environment.state.isInNestedMethod()) if (holder !is UtSolverStatusSAT) { logger.trace { "processResult<${environment.method.signature}> UNSAT" } @@ -3344,7 +3344,7 @@ class Traverser( val methodResult = MethodResult(symbolicResult) //execution frame from level 2 or above - if (state.isInNestedMethod()) { + if (environment.state.isInNestedMethod()) { // static fields substitution // TODO: JIRA:1610 -- better way of working with statics val updates = if (environment.method.name == STATIC_INITIALIZER && substituteStaticsWithSymbolicVariable) { @@ -3355,7 +3355,8 @@ class Traverser( } else { MemoryUpdate() // all memory updates are already added in [environment.state] } - val stateToOffer = state.pop(methodResult.copy(symbolicStateUpdate = updates.asUpdate())) + val methodResultWithUpdates = methodResult.copy(symbolicStateUpdate = queuedSymbolicStateUpdates + updates) + val stateToOffer = environment.state.pop(methodResultWithUpdates) offerState(stateToOffer) logger.trace { "processResult<${environment.method.signature}> return from nested method" } @@ -3363,7 +3364,8 @@ class Traverser( } //toplevel method - val terminalExecutionState = state.copy( + val terminalExecutionState = environment.state.copy( + symbolicState = symbolicState, methodResult = methodResult, // the way to put SymbolicResult into terminal state label = StateLabel.TERMINAL ) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 77cb250611..1eeeab332a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -493,8 +493,7 @@ class UtBotSymbolicEngine( val solver = state.solver val parameters = state.parameters.map { it.value } val symbolicResult = requireNotNull(state.methodResult?.symbolicResult) { "The state must have symbolicResult" } - // it's free to make a check, because in the result is SAT, it should be already cached - val holder = requireNotNull(solver.check(respectSoft = true) as? UtSolverStatusSAT) { "The state must be SAT!" } + val holder = requireNotNull(solver.lastStatus as? UtSolverStatusSAT) { "The state must be SAT!" } val predictedTestName = Predictors.testName.predict(state.path) Predictors.testName.provide(state.path, predictedTestName, "") From 33c61d15f57f96b22d4e6899720a51068b2b2eab Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Wed, 29 Jun 2022 09:49:25 +0300 Subject: [PATCH 19/19] Fix review comments --- .../kotlin/org/utbot/engine/ExecutionState.kt | 4 ++-- .../main/kotlin/org/utbot/engine/Extensions.kt | 1 + .../org/utbot/engine/TraversalContext.kt | 4 ++-- .../main/kotlin/org/utbot/engine/Traverser.kt | 18 ++++++++---------- .../org/utbot/engine/UtBotSymbolicEngine.kt | 2 ++ 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt index d56868ec87..25add73ecd 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt @@ -34,8 +34,8 @@ data class Edge(val src: Stmt, val dst: Stmt, val decisionNum: Int) * [UtExecution]. This state represents the final state of the program execution, that is a throw or return from the outer * method. * - * [CONCRETE] is a label for a state which is not suitable for further symbolic analysis and it is also not a terminal - * state. Such states are only suitable for a concrete execution and may appear from [Assumption]s. + * [CONCRETE] is a label for a state which is not suitable for further symbolic analysis, and it is also not a terminal + * state. Such states are only suitable for a concrete execution and may result from [Assumption]s. */ enum class StateLabel { INTERMEDIATE, diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt index 02a6438230..b760070557 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt @@ -111,6 +111,7 @@ fun SootMethod.canRetrieveBody() = */ fun SootMethod.jimpleBody(): JimpleBody { declaringClass.adjustLevel(BODIES) + require(canRetrieveBody()) { "Can't retrieve body for $this"} return retrieveActiveBody() as JimpleBody } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt index cf70077b3f..22a9c19304 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt @@ -2,7 +2,7 @@ package org.utbot.engine /** * Represents a mutable _Context_ during the [ExecutionState] traversing. This _Context_ consists of all mutable and - * immutable properties and fields which are created and updated during analysis of **one** Jimple instruction. + * immutable properties and fields which are created and updated during analysis of a **single** Jimple instruction. * * Traverser functions should be implemented as an extension functions with [TraversalContext] as a receiver. * @@ -23,7 +23,7 @@ class TraversalContext { } /** - * New states from traversal. + * New states obtained from the traversal. */ val nextStates: Collection get() = states 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 b8122d16b7..1fcd94dcea 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -6,7 +6,6 @@ import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentMap import kotlinx.collections.immutable.toPersistentSet -import kotlinx.coroutines.CancellationException import org.utbot.common.WorkaroundReason.HACK import org.utbot.common.WorkaroundReason.REMOVE_ANONYMOUS_CLASSES import org.utbot.common.findField @@ -213,6 +212,7 @@ class Traverser( private val classLoader: ClassLoader get() = utContext.classLoader + // TODO: move this and other mutable fields to [TraversalContext] lateinit var environment: Environment private val solver: UtSolver get() = environment.state.solver @@ -281,13 +281,9 @@ class Traverser( } catch (ex: Throwable) { environment.state.close() - if (ex !is CancellationException) { - logger.error(ex) { "Test generation failed on stmt $currentStmt, symbolic stack trace:\n$symbolicStackTrace" } - // TODO: enrich with nice description for known issues - throw ex - } else { - logger.debug(ex) { "Cancellation happened" } - } + logger.error(ex) { "Test generation failed on stmt $currentStmt, symbolic stack trace:\n$symbolicStackTrace" } + // TODO: enrich with nice description for known issues + throw ex } queuedSymbolicStateUpdates = SymbolicStateUpdate() return context.nextStates @@ -2991,7 +2987,8 @@ class Traverser( implicitlyThrowException(NullPointerException(), setOf(notMarkedAndNull)) } - queuedSymbolicStateUpdates += canNotBeNull.asHardConstraint() } + queuedSymbolicStateUpdates += canNotBeNull.asHardConstraint() + } private fun TraversalContext.divisionByZeroCheck(denom: PrimitiveValue) { val equalsToZero = Eq(denom, 0) @@ -3363,7 +3360,8 @@ class Traverser( return } - //toplevel method + // toplevel method + // TODO: investigate very strange behavior when some constraints are not added leading to failing CodegenExampleTest::firstExampleTest fails val terminalExecutionState = environment.state.copy( symbolicState = symbolicState, methodResult = methodResult, // the way to put SymbolicResult into terminal state diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 1eeeab332a..19811fb3c5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -351,6 +351,8 @@ class UtBotSymbolicEngine( // So we need to make it throw CancelledException by method below: currentCoroutineContext().job.ensureActive() } + + // TODO: think about concise modifying globalGraph in Traverser and UtBotSymbolicEngine globalGraph.visitNode(state) } }