diff options
Diffstat (limited to 'src/main/java/com/code_intelligence/jazzer/mutation/api')
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); +} |