diff options
14 files changed, 120 insertions, 32 deletions
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/Debuggable.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/Debuggable.java new file mode 100644 index 00000000..df6f8288 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/Debuggable.java @@ -0,0 +1,46 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.api; + +import static java.util.Collections.newSetFromMap; +import static java.util.Objects.requireNonNull; + +import java.util.IdentityHashMap; +import java.util.Set; +import java.util.function.Predicate; + +public interface Debuggable { + /** + * Returns a string representation of the object that is meant to be used to make assertions about + * its structure in tests. + * + * @param isInCycle evaluates to {@code true} if a cycle has been detected during recursive calls + * of this function. Must be called at most once with {@code this} as the single + * argument. Implementing classes that know that their current instance can never + * be contained in recursive substructures need not call this method. + */ + String toDebugString(Predicate<Debuggable> isInCycle); + + /** + * Returns a string representation of the given {@link Debuggable} that is meant to be used to + * make assertions about its structure in tests. + */ + static String getDebugString(Debuggable debuggable) { + Set<Debuggable> seen = newSetFromMap(new IdentityHashMap<>()); + return debuggable.toDebugString(child -> !seen.add(requireNonNull(child))); + } +} diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/InPlaceMutator.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/InPlaceMutator.java index 725be5c8..13513afb 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/api/InPlaceMutator.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/InPlaceMutator.java @@ -31,13 +31,13 @@ package com.code_intelligence.jazzer.mutation.api; * <li>SHOULD use {@link com.code_intelligence.jazzer.mutation.support.WeakIdentityHashMap} for * this purpose; * <li>MUST otherwise be deeply immutable; - * <li>SHOULD override {@link Object#toString()} to return a stable but otherwise unspecified - * debug representation of their subtree of mutators. + * <li>SHOULD override {@link Object#toString()} to return {@code + * Debuggable.getDebugString(this)}. * </ul> * * @param <T> the reference type this mutator operates on */ -public interface InPlaceMutator<T> { +public interface InPlaceMutator<T> extends Debuggable { /** * Implementations * <ul> diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingInPlaceMutator.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingInPlaceMutator.java index 29971271..61adbe10 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingInPlaceMutator.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingInPlaceMutator.java @@ -30,7 +30,7 @@ import java.io.InputStream; * <p>Implementing classes SHOULD be declared final. */ public abstract class SerializingInPlaceMutator<T> - implements SerializingMutator<T>, InPlaceMutator<T> { + extends SerializingMutator<T> implements InPlaceMutator<T> { // ByteArrayInputStream#close is documented as being a no-op, so it is safe to reuse an instance // here. // TODO: Introduce a dedicated empty InputStream implementation. diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingMutator.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingMutator.java index 18579f7f..58b2a49b 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingMutator.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingMutator.java @@ -27,4 +27,9 @@ import com.google.errorprone.annotations.DoNotMock; * consider implementing the more versatile {@link SerializingInPlaceMutator} instead. */ @DoNotMock("Use TestSupport#mockMutator instead") -public interface SerializingMutator<T> extends Serializer<T>, ValueMutator<T> {} +public abstract class SerializingMutator<T> implements Serializer<T>, ValueMutator<T> { + @Override + public final String toString() { + return Debuggable.getDebugString(this); + } +} diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/ValueMutator.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/ValueMutator.java index cf9dc412..6eef0ab2 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/api/ValueMutator.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/ValueMutator.java @@ -32,13 +32,13 @@ import com.google.errorprone.annotations.CheckReturnValue; * <li>SHOULD use {@link com.code_intelligence.jazzer.mutation.support.WeakIdentityHashMap} for * this purpose; * <li>MUST otherwise be deeply immutable; - * <li>SHOULD override {@link Object#toString()} to return a stable but otherwise unspecified - * debug representation of their subtree of mutators. + * <li>SHOULD override {@link Object#toString()} to return {@code + * Debuggable.getDebugString(this)}. * </ul> * * @param <T> the type this mutator operates on */ -public interface ValueMutator<T> { +public interface ValueMutator<T> extends Debuggable { /** * Implementations * <ul> diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinators.java b/src/main/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinators.java index b131b8e4..ef033729 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinators.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinators.java @@ -22,6 +22,7 @@ import static java.util.Arrays.stream; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; +import com.code_intelligence.jazzer.mutation.api.Debuggable; import com.code_intelligence.jazzer.mutation.api.InPlaceMutator; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.Serializer; @@ -36,6 +37,7 @@ import java.io.OutputStream; import java.util.Arrays; import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import net.jodah.typetools.TypeResolver; @@ -59,10 +61,15 @@ public final class MutatorCombinators { } @Override - public String toString() { + public String toDebugString(Predicate<Debuggable> isInCycle) { Class<?> owningType = TypeResolver.resolveRawArguments(Function.class, getter.getClass())[0]; - return owningType.getSimpleName() + "." + mutator; + return owningType.getSimpleName() + "." + mutator.toDebugString(isInCycle); + } + + @Override + public String toString() { + return Debuggable.getDebugString(this); } }; } @@ -83,9 +90,14 @@ public final class MutatorCombinators { } @Override - public String toString() { + public String toDebugString(Predicate<Debuggable> isInCycle) { Class<?> owningType = TypeResolver.resolveRawArguments(Function.class, map.getClass())[0]; - return owningType.getSimpleName() + " via " + mutator; + return owningType.getSimpleName() + " via " + mutator.toDebugString(isInCycle); + } + + @Override + public String toString() { + return Debuggable.getDebugString(this); } }; } @@ -115,8 +127,15 @@ public final class MutatorCombinators { } @Override + public String toDebugString(Predicate<Debuggable> isInCycle) { + return stream(mutators) + .map(mutator -> mutator.toDebugString(isInCycle)) + .collect(joining(", ", "{", "}")); + } + + @Override public String toString() { - return stream(mutators).map(Object::toString).collect(joining(", ", "{", "}")); + return Debuggable.getDebugString(this); } }; } @@ -153,8 +172,8 @@ public final class MutatorCombinators { } @Override - public String toString() { - return mutatorDelegate.toString(); + public String toDebugString(Predicate<Debuggable> isInCycle) { + return mutatorDelegate.toDebugString(isInCycle); } @Override diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/combinator/PostComposedMutator.java b/src/main/java/com/code_intelligence/jazzer/mutation/combinator/PostComposedMutator.java index 2de93cbc..71a69661 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/combinator/PostComposedMutator.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/combinator/PostComposedMutator.java @@ -18,6 +18,7 @@ package com.code_intelligence.jazzer.mutation.combinator; import static java.util.Objects.requireNonNull; +import com.code_intelligence.jazzer.mutation.api.Debuggable; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; import java.io.DataInputStream; @@ -26,9 +27,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.function.Function; +import java.util.function.Predicate; import net.jodah.typetools.TypeResolver; -abstract class PostComposedMutator<T, R> implements SerializingMutator<R> { +abstract class PostComposedMutator<T, R> extends SerializingMutator<R> { private final SerializingMutator<T> mutator; private final Function<T, R> map; private final Function<R, T> inverse; @@ -75,8 +77,8 @@ abstract class PostComposedMutator<T, R> implements SerializingMutator<R> { } @Override - public final String toString() { + public String toDebugString(Predicate<Debuggable> isInCycle) { Class<?> returnType = TypeResolver.resolveRawArguments(Function.class, map.getClass())[1]; - return mutator + " -> " + returnType.getSimpleName(); + return mutator.toDebugString(isInCycle) + " -> " + returnType.getSimpleName(); } } diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/combinator/ProductMutator.java b/src/main/java/com/code_intelligence/jazzer/mutation/combinator/ProductMutator.java index be4fa4bd..8d9226c3 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/combinator/ProductMutator.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/combinator/ProductMutator.java @@ -22,6 +22,7 @@ import static com.code_intelligence.jazzer.mutation.support.Preconditions.requir import static java.util.Arrays.stream; import static java.util.stream.Collectors.joining; +import com.code_intelligence.jazzer.mutation.api.Debuggable; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingInPlaceMutator; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; @@ -31,6 +32,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; +import java.util.function.Predicate; public final class ProductMutator extends SerializingInPlaceMutator<Object[]> { private final SerializingMutator[] mutators; @@ -119,7 +121,9 @@ public final class ProductMutator extends SerializingInPlaceMutator<Object[]> { } @Override - public String toString() { - return stream(mutators).map(Object::toString).collect(joining(", ", "[", "]")); + public String toDebugString(Predicate<Debuggable> isInCycle) { + return stream(mutators) + .map(mutator -> mutator.toDebugString(isInCycle)) + .collect(joining(", ", "[", "]")); } } diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorFactory.java index fb8ebf25..ef1ff0f8 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ListMutatorFactory.java @@ -19,6 +19,7 @@ package com.code_intelligence.jazzer.mutation.mutator.collection; import static com.code_intelligence.jazzer.mutation.support.TypeSupport.parameterTypeIfParameterized; import static java.lang.Math.min; +import com.code_intelligence.jazzer.mutation.api.Debuggable; import com.code_intelligence.jazzer.mutation.api.MutatorFactory; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingInPlaceMutator; @@ -32,6 +33,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.RandomAccess; +import java.util.function.Predicate; import java.util.stream.Collectors; final class ListMutatorFactory extends MutatorFactory { @@ -118,8 +120,8 @@ final class ListMutatorFactory extends MutatorFactory { } @Override - public String toString() { - return "List<" + elementMutator + ">"; + public String toDebugString(Predicate<Debuggable> isInCycle) { + return "List<" + elementMutator.toDebugString(isInCycle) + ">"; } private int minInitialSize() { diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorFactory.java index d5819928..5c78e0f4 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/BooleanMutatorFactory.java @@ -18,6 +18,7 @@ package com.code_intelligence.jazzer.mutation.mutator.lang; import static com.code_intelligence.jazzer.mutation.support.TypeSupport.findFirstParentIfClass; +import com.code_intelligence.jazzer.mutation.api.Debuggable; import com.code_intelligence.jazzer.mutation.api.MutatorFactory; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; @@ -27,6 +28,7 @@ import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.AnnotatedType; import java.util.Optional; +import java.util.function.Predicate; final class BooleanMutatorFactory extends MutatorFactory { @Override @@ -36,7 +38,7 @@ final class BooleanMutatorFactory extends MutatorFactory { } @Immutable - private static final class BooleanMutator implements SerializingMutator<Boolean> { + private static final class BooleanMutator extends SerializingMutator<Boolean> { private static final BooleanMutator INSTANCE = new BooleanMutator(); @Override @@ -60,7 +62,7 @@ final class BooleanMutatorFactory extends MutatorFactory { } @Override - public String toString() { + public String toDebugString(Predicate<Debuggable> isInLoop) { return "Boolean"; } diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorFactory.java index 1edc9d2c..7f10078e 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorFactory.java @@ -19,6 +19,7 @@ package com.code_intelligence.jazzer.mutation.mutator.lang; import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.readAllBytes; import static com.code_intelligence.jazzer.mutation.support.TypeSupport.findFirstParentIfClass; +import com.code_intelligence.jazzer.mutation.api.Debuggable; import com.code_intelligence.jazzer.mutation.api.MutatorFactory; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; @@ -31,6 +32,7 @@ import java.io.OutputStream; import java.lang.reflect.AnnotatedType; import java.util.Arrays; import java.util.Optional; +import java.util.function.Predicate; final class ByteArrayMutatorFactory extends MutatorFactory { @Override @@ -39,7 +41,7 @@ final class ByteArrayMutatorFactory extends MutatorFactory { } @Immutable - private static final class ByteArrayMutator implements SerializingMutator<byte[]> { + private static final class ByteArrayMutator extends SerializingMutator<byte[]> { private static final ByteArrayMutator INSTANCE = new ByteArrayMutator(); private ByteArrayMutator() {} @@ -83,7 +85,7 @@ final class ByteArrayMutatorFactory extends MutatorFactory { } @Override - public String toString() { + public String toDebugString(Predicate<Debuggable> isInCycle) { return "ByteArray"; } } diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorFactory.java index 3b44faed..8f83c989 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/NullableMutatorFactory.java @@ -21,6 +21,7 @@ import static com.code_intelligence.jazzer.mutation.support.TypeSupport.withExtr import static java.util.Arrays.stream; import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.api.Debuggable; import com.code_intelligence.jazzer.mutation.api.MutatorFactory; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; @@ -31,6 +32,7 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedType; import java.util.Optional; +import java.util.function.Predicate; final class NullableMutatorFactory extends MutatorFactory { private static final Annotation NOT_NULL = @@ -52,7 +54,7 @@ final class NullableMutatorFactory extends MutatorFactory { .map(NullableMutator::new); } - private static final class NullableMutator<T> implements SerializingMutator<T> { + private static final class NullableMutator<T> extends SerializingMutator<T> { private static final int INVERSE_FREQUENCY_NULL = 100; private final SerializingMutator<T> mutator; @@ -108,8 +110,8 @@ final class NullableMutatorFactory extends MutatorFactory { } @Override - public String toString() { - return "Nullable<" + mutator + ">"; + public String toDebugString(Predicate<Debuggable> isInCycle) { + return "Nullable<" + mutator.toDebugString(isInCycle) + ">"; } } } diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinatorsTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinatorsTest.java index eae088f7..a8424ecb 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinatorsTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/combinator/MutatorCombinatorsTest.java @@ -27,6 +27,7 @@ import static com.code_intelligence.jazzer.mutation.support.TestSupport.nullData import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.code_intelligence.jazzer.mutation.api.Debuggable; import com.code_intelligence.jazzer.mutation.api.InPlaceMutator; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; @@ -35,6 +36,7 @@ import java.io.DataInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; import org.junit.jupiter.api.Test; class MutatorCombinatorsTest { @@ -77,7 +79,7 @@ class MutatorCombinatorsTest { } @Override - public String toString() { + public String toDebugString(Predicate<Debuggable> isInCycle) { return "List<Integer>"; } }); @@ -120,7 +122,7 @@ class MutatorCombinatorsTest { } @Override - public String toString() { + public String toDebugString(Predicate<Debuggable> isInCycle) { return "List<Integer>"; } }); diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java b/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java index 9a4c1572..c7744cec 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toCollection; +import com.code_intelligence.jazzer.mutation.api.Debuggable; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; import com.code_intelligence.jazzer.mutation.engine.SeededPseudoRandom; @@ -32,6 +33,7 @@ import java.io.OutputStream; import java.util.ArrayDeque; import java.util.Queue; import java.util.function.Function; +import java.util.function.Predicate; public final class TestSupport { private static final DataOutputStream nullDataOutputStream = @@ -88,7 +90,7 @@ public final class TestSupport { } @Override - public String toString() { + public String toDebugString(Predicate<Debuggable> isInCycle) { if (initialValue == null) { return "null"; } |