aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/code_intelligence/jazzer/mutation/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/code_intelligence/jazzer/mutation/api')
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/BUILD.bazel9
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/ChainedMutatorFactory.java48
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/Debuggable.java46
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/Detacher.java44
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/InPlaceMutator.java74
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/MutatorFactory.java80
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/PseudoRandom.java136
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/Serializer.java116
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingInPlaceMutator.java76
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingMutator.java35
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/ValueMutator.java75
11 files changed, 739 insertions, 0 deletions
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/mutation/api/BUILD.bazel
new file mode 100644
index 00000000..cd5fe60e
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/BUILD.bazel
@@ -0,0 +1,9 @@
+java_library(
+ name = "api",
+ srcs = glob(["*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/mutation/support",
+ "@com_google_errorprone_error_prone_annotations//jar",
+ ],
+)
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/ChainedMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/ChainedMutatorFactory.java
new file mode 100644
index 00000000..bf27e81b
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/ChainedMutatorFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.code_intelligence.jazzer.mutation.support.StreamSupport.findFirstPresent;
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableList;
+
+import com.google.errorprone.annotations.CheckReturnValue;
+import java.lang.reflect.AnnotatedType;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A {@link MutatorFactory} that delegates to the given factories in order.
+ */
+public final class ChainedMutatorFactory extends MutatorFactory {
+ private final List<MutatorFactory> factories;
+
+ /**
+ * Creates a {@link MutatorFactory} that delegates to the given factories in order.
+ *
+ * @param factories a possibly empty collection of factories
+ */
+ public ChainedMutatorFactory(MutatorFactory... factories) {
+ this.factories = unmodifiableList(asList(factories));
+ }
+
+ @Override
+ @CheckReturnValue
+ public Optional<SerializingMutator<?>> tryCreate(AnnotatedType type, MutatorFactory parent) {
+ return findFirstPresent(factories.stream().map(factory -> factory.tryCreate(type, parent)));
+ }
+}
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/Detacher.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/Detacher.java
new file mode 100644
index 00000000..d927e505
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/Detacher.java
@@ -0,0 +1,44 @@
+/*
+ * 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 com.google.errorprone.annotations.CheckReturnValue;
+
+/**
+ * Knows how to clone a {@code T} such that it shares no mutable state with the original.
+ */
+@FunctionalInterface
+public interface Detacher<T> {
+ /**
+ * Returns an equal instance that shares no mutable state with {@code value}.
+ *
+ * <p>Implementations
+ * <ul>
+ * <li>MUST return an instance that {@link Object#equals(Object)} the argument;
+ * <li>MUST return an instance that cannot be used to mutate the state of the argument through
+ * its API (ignoring uses of {@link sun.misc.Unsafe});
+ * <li>MUST return an instance that is not affected by any changes to the original value made
+ * by any mutator;</li>
+ * <li>MUST be accepted by mutator methods just like the original value;</li>
+ * <li>MAY return the argument itself if it is deeply immutable.
+ * </ul>
+ *
+ * @param value the instance to detach
+ * @return an equal instance that shares no mutable state with {@code value}
+ */
+ @CheckReturnValue T detach(T value);
+}
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
new file mode 100644
index 00000000..cf1b243d
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/InPlaceMutator.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+/**
+ * Knows how to initialize and mutate (parts of) an existing object of type {@code T} in place and
+ * how to incorporate (cross over) parts of another object of the same type.
+ *
+ * <p>Certain types, such as immutable and primitive types, can not be mutated in place. For
+ * example, {@link java.util.List} can be mutated in place whereas {@link String} and {@code int}
+ * can't. In such cases, use {@link ValueMutator} instead.
+ *
+ * <p>Implementations
+ * <ul>
+ * <li>MAY weakly associate mutable state with the identity (not equality class) of objects they
+ * have been passed as arguments or returned from initialization functions;
+ * <li>MAY assume that they are only passed arguments that they have initialized or mutated;</li>
+ * <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 {@code
+ * Debuggable.getDebugString(this)}.
+ * </ul>
+ *
+ * @param <T> the reference type this mutator operates on
+ */
+public interface InPlaceMutator<T> extends Debuggable {
+ /**
+ * Implementations
+ * <ul>
+ * <li>MUST accept any mutable instance of {@code T}, not just those it creates itself.
+ * <li>SHOULD, when called repeatedly, initialize the object in ways that are likely to be
+ * distinct.
+ * </ul>
+ */
+ void initInPlace(T reference, PseudoRandom prng);
+
+ /**
+ * Implementations
+ * <ul>
+ * <li>MUST ensure that {@code reference} does not {@link Object#equals(Object)} the state it
+ * had prior to the call (if possible);
+ * <li>MUST accept any mutable instance of {@code T}, not just those it creates itself.
+ * <li>SHOULD, when called repeatedly, be able to eventually reach any valid state of the part
+ * of {@code T} governed by this mutator;
+ * </ul>
+ */
+ void mutateInPlace(T reference, PseudoRandom prng);
+
+ /**
+ * Implementations
+ * <ul>
+ * <li>MUST ensure that {@code reference} does not {@link Object#equals(Object)} the state it
+ * had prior to the call (if possible);
+ * <li>MUST accept any mutable instance of {@code T}, not just those it creates itself.
+ * <li>MUST NOT mutate {@code otherReference}</li>
+ * </ul>
+ */
+ void crossOverInPlace(T reference, T otherReference, PseudoRandom prng);
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/MutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/MutatorFactory.java
new file mode 100644
index 00000000..64771285
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/MutatorFactory.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.code_intelligence.jazzer.mutation.support.Preconditions.require;
+import static com.code_intelligence.jazzer.mutation.support.TypeSupport.asAnnotatedType;
+import static java.lang.String.format;
+
+import com.google.errorprone.annotations.CheckReturnValue;
+import java.lang.reflect.AnnotatedType;
+import java.util.Optional;
+
+/**
+ * Instances of this class are not required to be thread safe, but are generally lightweight and can
+ * thus be created as needed.
+ */
+public abstract class MutatorFactory {
+ public final boolean canMutate(AnnotatedType type) {
+ return tryCreate(type).isPresent();
+ }
+
+ public final <T> SerializingMutator<T> createOrThrow(Class<T> clazz) {
+ return (SerializingMutator<T>) createOrThrow(asAnnotatedType(clazz));
+ }
+
+ public final SerializingMutator<?> createOrThrow(AnnotatedType type) {
+ Optional<SerializingMutator<?>> maybeMutator = tryCreate(type);
+ require(maybeMutator.isPresent(), "Failed to create mutator for " + type);
+ return maybeMutator.get();
+ }
+
+ public final SerializingInPlaceMutator<?> createInPlaceOrThrow(AnnotatedType type) {
+ Optional<SerializingInPlaceMutator<?>> maybeMutator = tryCreateInPlace(type);
+ require(maybeMutator.isPresent(), "Failed to create mutator for " + type);
+ return maybeMutator.get();
+ }
+
+ /**
+ * Tries to create a mutator for {@code type} and, if successful, asserts that it is an instance
+ * of {@link SerializingInPlaceMutator}.
+ */
+ public final Optional<SerializingInPlaceMutator<?>> tryCreateInPlace(AnnotatedType type) {
+ return tryCreate(type).map(mutator -> {
+ require(mutator instanceof InPlaceMutator<?>,
+ format("Mutator for %s is not in-place: %s", type, mutator.getClass()));
+ return (SerializingInPlaceMutator<?>) mutator;
+ });
+ }
+
+ @CheckReturnValue
+ public final Optional<SerializingMutator<?>> tryCreate(AnnotatedType type) {
+ return tryCreate(type, this);
+ }
+
+ /**
+ * Attempt to create a {@link SerializingMutator} for the given type.
+ *
+ * @param type the type to mutate
+ * @param factory the factory to use when creating submutators
+ * @return a {@link SerializingMutator} for the given {@code type}, or {@link Optional#empty()}
+ * if this factory can't create such mutators
+ */
+ @CheckReturnValue
+ public abstract Optional<SerializingMutator<?>> tryCreate(
+ AnnotatedType type, MutatorFactory factory);
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/PseudoRandom.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/PseudoRandom.java
new file mode 100644
index 00000000..3755a7b1
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/PseudoRandom.java
@@ -0,0 +1,136 @@
+/*
+ * 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 com.google.errorprone.annotations.DoNotMock;
+import java.util.List;
+import java.util.function.Supplier;
+
+@DoNotMock("Use TestSupport#mockPseudoRandom instead")
+public interface PseudoRandom {
+ /**
+ * @return a uniformly random {@code boolean}
+ */
+ boolean choice();
+
+ /**
+ * @return a {@code boolean} that is {@code true} with probability {@code 1/inverseFrequencyTrue}
+ */
+ boolean trueInOneOutOf(int inverseFrequencyTrue);
+
+ /**
+ * @throws IllegalArgumentException if {@code array.length == 0}
+ * @return an element from the given array at uniformly random index
+ */
+ <T> T pickIn(T[] array);
+
+ /**
+ * @throws IllegalArgumentException if {@code array.length == 0}
+ * @return an element from the given List at uniformly random index
+ */
+ <T> T pickIn(List<T> array);
+
+ /**
+ * @throws IllegalArgumentException if {@code array.length == 0}
+ * @return a uniformly random index valid for the given array
+ */
+ <T> int indexIn(T[] array);
+
+ /**
+ * @throws IllegalArgumentException if {@code list.size() == 0}
+ * @return a uniformly random index valid for the given list
+ */
+ <T> int indexIn(List<T> list);
+
+ /**
+ * Prefer {@link #indexIn(Object[])} and {@link #indexIn(List)}.
+ *
+ * @throws IllegalArgumentException if {@code range < 1}
+ * @return a uniformly random index in the range {@code [0, range-1]}
+ */
+ int indexIn(int range);
+
+ /**
+ * @throws IllegalArgumentException if {@code array.length < 2}
+ * @return a uniformly random index valid for the given array and different from
+ * {@code currentIndex}
+ */
+ <T> int otherIndexIn(T[] array, int currentIndex);
+
+ /**
+ * @throws IllegalArgumentException if {@code length < 2}
+ * @return a uniformly random {@code int} in the closed range {@code [0, length)} that is
+ * different from {@code currentIndex}
+ */
+ int otherIndexIn(int range, int currentIndex);
+
+ /**
+ * @return a uniformly random {@code int} in the closed range
+ * {@code [lowerInclusive, upperInclusive]}.
+ */
+ int closedRange(int lowerInclusive, int upperInclusive);
+
+ /**
+ * @return a uniformly random {@code long} in the closed range
+ * {@code [lowerInclusive, upperInclusive]}.
+ */
+ long closedRange(long lowerInclusive, long upperInclusive);
+
+ /**
+ * @return a uniformly random {@code float} in the closed range
+ * {@code [lowerInclusive, upperInclusive]}.
+ */
+ float closedRange(float lowerInclusive, float upperInclusive);
+
+ /**
+ * @return a uniformly random {@code double} in the closed range
+ * {@code [lowerInclusive, upperInclusive]}.
+ */
+ double closedRange(double lowerInclusive, double upperInclusive);
+
+ /**
+ * @return a random value in the closed range [0, upperInclusive] that is heavily biased towards
+ * being small
+ */
+ int closedRangeBiasedTowardsSmall(int upperInclusive);
+
+ /**
+ * @return a random value in the closed range [lowerInclusive, upperInclusive] that is heavily
+ * biased towards being small
+ */
+ int closedRangeBiasedTowardsSmall(int lowerInclusive, int upperInclusive);
+
+ /**
+ * Fills the given array with random bytes.
+ */
+ void bytes(byte[] bytes);
+
+ /**
+ * Use the given supplier to produce a value with probability {@code 1/inverseSupplierFrequency},
+ * otherwise randomly return one of the given values.
+ *
+ * @return value produced by the supplier or one of the given values
+ */
+ <T> T pickValue(T value, T otherValue, Supplier<T> supplier, int inverseSupplierFrequency);
+
+ /**
+ * Returns a pseudorandom {@code long} value.
+ *
+ * @return a pseudorandom {@code long} value
+ */
+ long nextLong();
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/Serializer.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/Serializer.java
new file mode 100644
index 00000000..b948b177
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/Serializer.java
@@ -0,0 +1,116 @@
+/*
+ * 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 com.code_intelligence.jazzer.mutation.support.InputStreamSupport.extendWithZeros;
+
+import com.google.errorprone.annotations.CheckReturnValue;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Serializes and deserializes values of type {@code T>} to and from (in-memory or on disk) corpus
+ * entries.
+ *
+ * <p>Binary representations must by default be self-delimiting. For variable-length types, the
+ * {@link #readExclusive(InputStream)} and {@link #writeExclusive(Object, OutputStream)} methods can
+ * optionally be overriden to implement more compact representations that align with existing binary
+ * corpus entries. For example, a {@code Serializer<byte[]>} could implement these optional methods
+ * to read and write the raw bytes without preceding length information whenever it is used in an
+ * already delimited context.
+ */
+public interface Serializer<T> extends Detacher<T> {
+ /**
+ * Reads a {@code T} from an endless stream that is eventually 0.
+ *
+ * <p>Implementations
+ * <ul>
+ * <li>MUST not attempt to consume the entire stream;
+ * <li>MUST return a valid {@code T} and not throw for any (even garbage) stream;
+ * <li>SHOULD short-circuit the creation of nested structures upon reading null bytes.
+ * </ul>
+ *
+ * @param in an endless stream that eventually only reads null bytes
+ * @return a {@code T} constructed from the bytes read
+ * @throws IOException declared, but must not be thrown by implementations unless methods called
+ * on {@code in} do
+ */
+ @CheckReturnValue T read(DataInputStream in) throws IOException;
+
+ /**
+ * Writes a {@code T} to a stream in such a way that an equal object can be recovered from the
+ * written bytes via {@link #read(DataInputStream)}.
+ *
+ * <p>Since {@link #read(DataInputStream)} is called with an endless stream, the binary
+ * representation MUST be self-delimiting. For example, when writing out a list, first write its
+ * length.
+ *
+ * @param value the value to write
+ * @param out the stream to write to
+ * @throws IOException declared, but must not be thrown by implementations unless methods called
+ * on {@code out} do
+ */
+ void write(T value, DataOutputStream out) throws IOException;
+
+ /**
+ * Reads a {@code T} from a finite stream, potentially using a simpler representation than that
+ * read by {@link #read(DataInputStream)}.
+ *
+ * <p>The default implementations call extends the stream with null bytes and then calls
+ * {@link #read(DataInputStream)}.
+ *
+ * <p>Implementations
+ * <ul>
+ * <li>MUST return a valid {@code T} and not throw for any (even garbage) stream;
+ * <li>SHOULD short-circuit the creation of nested structures upon reading null bytes;
+ * <li>SHOULD naturally consume the entire stream.
+ * </ul>
+ *
+ * @param in a finite stream
+ * @return a {@code T} constructed from the bytes read
+ * @throws IOException declared, but must not be thrown by implementations unless methods called
+ * on {@code in} do
+ */
+ @CheckReturnValue
+ default T readExclusive(InputStream in) throws IOException {
+ return read(new DataInputStream(extendWithZeros(in)));
+ }
+
+ /**
+ * Writes a {@code T} to a stream in such a way that an equal object can be recovered from the
+ * written bytes via {@link #readExclusive(InputStream)}.
+ *
+ * <p>The default implementations calls through to {@link #read(DataInputStream)} and should only
+ * be overriden if {@link #readExclusive(InputStream)} is.
+ *
+ * <p>As opposed to {@link #read(DataInputStream)}, {@link #readExclusive(InputStream)} is called
+ * with a finite stream. The binary representation of a {@code T} value thus does not have to be
+ * self-delimiting, which can allow for simpler representations. For example, a {@code byte[]} can
+ * be written to the stream without prepending its length.
+ *
+ * @param value the value to write
+ * @param out the stream to write to
+ * @throws IOException declared, but must not be thrown by implementations unless methods called
+ * on {@code out} do
+ */
+ default void writeExclusive(T value, OutputStream out) throws IOException {
+ write(value, new DataOutputStream(out));
+ }
+}
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
new file mode 100644
index 00000000..b7bc4d4c
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingInPlaceMutator.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.code_intelligence.jazzer.mutation.support.ExceptionSupport.asUnchecked;
+
+import com.google.errorprone.annotations.ForOverride;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Combines an {@link InPlaceMutator} with a {@link Serializer} for objects of type {@code T}.
+ *
+ * <p>If {@code T} can't be mutated in place, implement {@link SerializingMutator} instead.
+ *
+ * <p>Implementing classes SHOULD be declared final.
+ */
+public abstract class SerializingInPlaceMutator<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.
+ private static final InputStream emptyInputStream = new ByteArrayInputStream(new byte[0]);
+
+ /**
+ * Constructs a default instance of {@code T}.
+ *
+ * <p>The returned value is immediately passed to {@link #initInPlace(Object, PseudoRandom)}.
+ *
+ * <p>Implementing classes SHOULD provide a more efficient implementation.
+ *
+ * @return a default instance of {@code T}
+ */
+ @ForOverride
+ protected T makeDefaultInstance() {
+ try {
+ return readExclusive(emptyInputStream);
+ } catch (IOException e) {
+ throw asUnchecked(e);
+ }
+ }
+
+ @Override
+ public final T init(PseudoRandom prng) {
+ T value = makeDefaultInstance();
+ initInPlace(value, prng);
+ return value;
+ }
+
+ @Override
+ public final T mutate(T value, PseudoRandom prng) {
+ mutateInPlace(value, prng);
+ return value;
+ }
+
+ @Override
+ public final T crossOver(T value, T otherValue, PseudoRandom prng) {
+ crossOverInPlace(value, otherValue, prng);
+ return value;
+ }
+}
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
new file mode 100644
index 00000000..58b2a49b
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/SerializingMutator.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.google.errorprone.annotations.DoNotMock;
+
+/**
+ * Combines a {@link ValueMutator} with a {@link Serializer} for objects of type {@code T}.
+ *
+ * <p>Implementing classes SHOULD be declared final.
+ *
+ * <p>This is the default fully-featured mutator type. If {@code T} can be mutated fully in place,
+ * consider implementing the more versatile {@link SerializingInPlaceMutator} instead.
+ */
+@DoNotMock("Use TestSupport#mockMutator instead")
+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
new file mode 100644
index 00000000..aa2b551e
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/ValueMutator.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.google.errorprone.annotations.CheckReturnValue;
+
+/**
+ * Knows how to initialize and mutate objects of type {@code T} and how to incorporate
+ * (cross over) parts of another object of the same type.
+ *
+ * <p>Certain types can be mutated fully in place. In such cases, prefer implementing the more
+ * versatile {@link InPlaceMutator} instead.
+ *
+ * <p>Implementations
+ * <ul>
+ * <li>MAY weakly associate mutable state with the identity (not equality class) of objects they
+ * have been passed as arguments or returned from initialization functions;
+ * <li>MAY assume that they are only passed arguments that they have initialized or mutated;</li>
+ * <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 {@code
+ * Debuggable.getDebugString(this)}.
+ * </ul>
+ *
+ * @param <T> the type this mutator operates on
+ */
+public interface ValueMutator<T> extends Debuggable {
+ /**
+ * Implementations
+ * <ul>
+ * <li>SHOULD, when called repeatedly, return a low amount of duplicates.
+ * </ul>
+ *
+ * @return an instance of {@code T}
+ */
+ @CheckReturnValue T init(PseudoRandom prng);
+
+ /**
+ * Implementations
+ * <ul>
+ * <li>MUST return a value that does not {@link Object#equals(Object)} the argument (if
+ * possible);
+ * <li>SHOULD, when called repeatedly, be able to eventually return any valid value of
+ * type {@code T};
+ * <li>MAY mutate the argument.
+ * </ul>
+ */
+ @CheckReturnValue T mutate(T value, PseudoRandom prng);
+
+ /**
+ * Implementations
+ * <ul>
+ * <li>MUST return a value that does not {@link Object#equals(Object)} the arguments (if
+ * possible);
+ * <li>MAY mutate {@code value}.
+ * <li>MUST NOT mutate {@code otherValue}.
+ * </ul>
+ */
+ @CheckReturnValue T crossOver(T value, T otherValue, PseudoRandom prng);
+}