From 778f780a626dfc670f3bdd425f6f84b6f67d27ba Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Thu, 25 Sep 2025 17:35:58 +0200 Subject: [PATCH 1/2] test: use shadowed annotations; assert that max length is respected --- .../mutation/ArgumentsMutatorFuzzTest.java | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java index d95e3f60a..b11745573 100644 --- a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java @@ -20,12 +20,12 @@ import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.junit.FuzzTest; -import com.code_intelligence.jazzer.mutation.annotation.WithSize; -import com.code_intelligence.jazzer.mutation.annotation.WithUtf8Length; import com.code_intelligence.jazzer.protobuf.Proto3; import com.code_intelligence.selffuzz.jazzer.mutation.ArgumentsMutator; import com.code_intelligence.selffuzz.jazzer.mutation.annotation.NotNull; import com.code_intelligence.selffuzz.jazzer.mutation.annotation.WithLength; +import com.code_intelligence.selffuzz.jazzer.mutation.annotation.WithSize; +import com.code_intelligence.selffuzz.jazzer.mutation.annotation.WithUtf8Length; import com.code_intelligence.selffuzz.jazzer.mutation.mutator.Mutators; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -101,21 +101,32 @@ void fuzzStrings( @NotNull String s1, @NotNull @WithUtf8Length(min = 10, max = 20) String s2) {} - @SelfFuzzTest // BUG: null pointer exception - void fuzzListOfMaps(Map nullableMap) {} + @SelfFuzzTest + void fuzzListOfMaps(@WithSize(max = 4) Map nullableMap) { + if (nullableMap != null) { + assertThat(nullableMap.size()).isAtMost(4); + } + } @SelfFuzzTest void fuzzListOfLists(List<@NotNull List> nullableMap, List> nullableList) {} @SelfFuzzTest - void fuzzPPrimitiveArrays( - int @WithLength(max = 10) [] a0, boolean[] a2, int @WithLength(max = 8193) [] a3) {} + void fuzzPrimitiveArrays( + int @WithLength(max = 10) [] a0, boolean[] a2, int @WithLength(max = 8193) [] a3) { + if (a0 != null) assertThat(a0.length).isAtMost(10); + if (a3 != null) assertThat(a3.length).isAtMost(8193); + } @SelfFuzzTest void fuzzBean(@NotNull ConstructorPropertiesAnnotatedBean bean, BeanWithParent beanWithParent) {} @SelfFuzzTest - void fuzzListOfBeans(@WithSize(max = 4) List beanWithParent) {} + void fuzzListOfBeans(@WithSize(max = 4) List beanWithParent) { + if (beanWithParent != null) { + assertThat(beanWithParent.size()).isAtMost(4); + } + } @SelfFuzzTest void fuzzListOfListOfBeans( @@ -184,7 +195,15 @@ void fuzzPrimitiveArrays( Byte @WithLength(max = 3) [] by0, byte[] by1, Short @WithLength(max = 3) [] s0, - short[] s1) {} + short[] s1) { + if (i0 != null) assertThat(i0.length).isAtMost(3); + if (b0 != null) assertThat(b0.length).isAtMost(3); + if (d0 != null) assertThat(d0.length).isAtMost(3); + if (f0 != null) assertThat(f0.length).isAtMost(3); + if (l0 != null) assertThat(l0.length).isAtMost(3); + if (by0 != null) assertThat(by0.length).isAtMost(3); + if (s0 != null) assertThat(s0.length).isAtMost(3); + } enum MyEnum { A, From 026bf3fe0406e44809b181c4d9d079253a2eded3 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Thu, 25 Sep 2025 18:04:58 +0200 Subject: [PATCH 2/2] feat: add set mutator --- .../mutation/ArgumentsMutatorFuzzTest.java | 8 + .../jazzer/mutation/annotation/WithSize.java | 3 +- .../mutator/collection/ChunkCrossOvers.java | 100 +++++++++ .../mutator/collection/ChunkMutations.java | 55 ++++- .../collection/CollectionMutators.java | 6 +- .../mutator/collection/SetMutatorFactory.java | 208 ++++++++++++++++++ .../jazzer/mutation/mutator/StressTest.java | 38 ++++ .../jazzer/mutation/support/TestSupport.java | 6 + 8 files changed, 420 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/SetMutatorFactory.java diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java index b11745573..8d0845bae 100644 --- a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java @@ -41,6 +41,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; public class ArgumentsMutatorFuzzTest { @@ -108,6 +109,13 @@ void fuzzListOfMaps(@WithSize(max = 4) Map nullableMap) { } } + @SelfFuzzTest + void fuzzListOfSets(@WithSize(max = 10) @NotNull Set<@NotNull Integer> setWithSize) { + if (setWithSize != null) { + assertThat(setWithSize.size()).isAtMost(10); + } + } + @SelfFuzzTest void fuzzListOfLists(List<@NotNull List> nullableMap, List> nullableList) {} diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/annotation/WithSize.java b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/WithSize.java index e8e72888a..e699b44f3 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/annotation/WithSize.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/WithSize.java @@ -26,10 +26,11 @@ import java.lang.annotation.Target; import java.util.List; import java.util.Map; +import java.util.Set; @Target(TYPE_USE) @Retention(RUNTIME) -@AppliesTo({List.class, Map.class}) +@AppliesTo({List.class, Map.class, Set.class}) @ValidateContainerDimensions @PropertyConstraint public @interface WithSize { diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkCrossOvers.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkCrossOvers.java index 40321cb66..8a5ca9b7f 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkCrossOvers.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkCrossOvers.java @@ -22,9 +22,11 @@ import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; final class ChunkCrossOvers { private ChunkCrossOvers() {} @@ -98,6 +100,25 @@ static void insertChunk( } } + static void insertChunk( + Set set, Set otherSet, int maxSize, PseudoRandom prng, boolean hasFixedSize) { + int originalSize = set.size(); + int maxChunkSize = Math.min(maxSize - originalSize, otherSet.size()); + int chunkSize = prng.sizeInClosedRange(1, maxChunkSize, hasFixedSize); + int fromChunkOffset = prng.closedRange(0, otherSet.size() - chunkSize); + Iterator fromIterator = otherSet.iterator(); + for (int i = 0; i < fromChunkOffset; i++) { + fromIterator.next(); + } + // insertChunk only inserts new entries and does not overwrite existing + // ones. As skipping those entries would lead to fewer insertions than + // requested, loop over the rest of the map to fill the chunk if possible. + while (set.size() < originalSize + chunkSize && fromIterator.hasNext()) { + K key = fromIterator.next(); + set.add(key); + } + } + static void overwriteChunk( Map map, Map otherMap, PseudoRandom prng, boolean hasFixedSize) { onCorrespondingChunks( @@ -117,6 +138,59 @@ static void overwriteChunk( hasFixedSize); } + static void overwriteChunk( + Set set, Set otherSet, PseudoRandom prng, boolean hasFixedSize) { + onCorrespondingChunks( + set, + otherSet, + prng, + (fromIterator, toIterator, chunkSize) -> { + // As keys can not be overwritten, only removed and new ones added, this + // cross over overwrites the values. Removal of keys is handled by the + // removeChunk mutation. Value equality is not checked here. + for (int i = 0; i < chunkSize; i++) { + K from = fromIterator.next(); + K to = toIterator.next(); + } + }, + hasFixedSize); + } + + static void crossOverChunk( + Set set, Set otherSet, SerializingMutator keyMutator, PseudoRandom prng) { + onCorrespondingChunks( + set, + otherSet, + prng, + (fromIterator, toIterator, chunkSize) -> { + Set entriesToAdd = new LinkedHashSet<>(chunkSize); + for (int i = 0; i < chunkSize; i++) { + K to = toIterator.next(); + K from = fromIterator.next(); + + // The entry has to be removed from the map before the cross-over, as + // mutating its key could cause problems in subsequent lookups. + // Furthermore, no new entries may be added while using the iterator, + // so crossed-over keys are collected for later addition. + toIterator.remove(); + + // As cross-overs do not guarantee to mutate the given object, no + // checks if the crossed over key already exists in the map are + // performed. This potentially overwrites existing entries or + // generates equal keys. + // In case of cross over this behavior is acceptable. + K newKey = keyMutator.crossOver(to, from, prng); + + // Prevent null keys, as those are not allowed in some map implementations. + if (newKey != null) { + entriesToAdd.add(newKey); + } + } + set.addAll(entriesToAdd); + }, + keyMutator.hasFixedSize()); + } + static void crossOverChunk( Map map, Map otherMap, @@ -198,6 +272,11 @@ private interface ChunkMapOperation { void apply(Iterator> fromIterator, Iterator> toIterator, int chunkSize); } + @FunctionalInterface + private interface ChunkSetOperation { + void apply(Iterator fromIterator, Iterator toIterator, int chunkSize); + } + static void onCorrespondingChunks( Map map, Map otherMap, @@ -219,6 +298,27 @@ static void onCorrespondingChunks( operation.apply(fromIterator, toIterator, chunkSize); } + static void onCorrespondingChunks( + Set set, + Set otherSet, + PseudoRandom prng, + ChunkSetOperation operation, + boolean hasFixedSize) { + int maxChunkSize = Math.min(set.size(), otherSet.size()); + int chunkSize = prng.sizeInClosedRange(1, maxChunkSize, hasFixedSize); + int fromChunkOffset = prng.closedRange(0, otherSet.size() - chunkSize); + int toChunkOffset = prng.closedRange(0, set.size() - chunkSize); + Iterator fromIterator = otherSet.iterator(); + for (int i = 0; i < fromChunkOffset; i++) { + fromIterator.next(); + } + Iterator toIterator = set.iterator(); + for (int i = 0; i < toChunkOffset; i++) { + toIterator.next(); + } + operation.apply(fromIterator, toIterator, chunkSize); + } + public enum CrossOverAction { INSERT_CHUNK, OVERWRITE_CHUNK, diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutations.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutations.java index b32d2e6af..47d701bad 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutations.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutations.java @@ -16,10 +16,11 @@ package com.code_intelligence.jazzer.mutation.mutator.collection; +import static com.code_intelligence.jazzer.mutation.support.Preconditions.require; + import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; import com.code_intelligence.jazzer.mutation.api.ValueMutator; -import com.code_intelligence.jazzer.mutation.support.Preconditions; import java.util.AbstractList; import java.util.ArrayDeque; import java.util.ArrayList; @@ -162,6 +163,56 @@ static boolean mutateRandomKeysChunk( return grownBy > 0; } + static boolean mutateRandomKeysChunk( + Set set, SerializingMutator keyMutator, PseudoRandom prng) { + int originalSize = set.size(); + int chunkSize = prng.sizeInClosedRange(1, originalSize, keyMutator.hasFixedSize()); + int chunkOffset = prng.closedRange(0, originalSize - chunkSize); + + // To ensure that mutating keys actually results in the set of keys changing, we keep the keys + // to mutate in the set, try to add new keys (that are therefore distinct from the keys to + // mutate) and only remove the successfully mutated keys in the end. + ArrayDeque keysToMutate = new ArrayDeque<>(chunkSize); + ArrayList keysToRemove = new ArrayList<>(chunkSize); + // get the set iterator + Iterator it = set.iterator(); + for (int i = 0; i < chunkOffset; i++) { + it.next(); + } + for (int i = chunkOffset; i < chunkOffset + chunkSize; i++) { + K entry = it.next(); + // ArrayDeque cannot hold null elements, which requires us to replace null with a sentinel. + // Also detach the key as keys may be mutable and mutation could destroy them. + keysToMutate.add(boxNull(keyMutator.detach(entry))); + keysToRemove.add(entry); + } + + Consumer addIfNew = + key -> { + int sizeBeforeAdd = set.size(); + set.add(key); + // The mutated key was new, try to mutate and add the next in line. + if (set.size() > sizeBeforeAdd) { + keysToMutate.removeFirst(); + } + }; + Supplier nextCandidate = + () -> { + // Mutate the next candidate in the queue. + K candidate = keyMutator.mutate(unboxNull(keysToMutate.removeFirst()), prng); + keysToMutate.addFirst(boxNull(candidate)); + return candidate; + }; + + growBy(set, addIfNew, chunkSize, nextCandidate); + // Remove the original keys that were successfully mutated into new keys. Since the original + // keys have been kept in the set up to this point, all keys added were successfully mutated to + // be unequal to the original keys. + int grownBy = set.size() - originalSize; + keysToRemove.stream().limit(grownBy).forEach(set::remove); + return grownBy > 0; + } + public static void mutateRandomValuesChunk( Map map, ValueMutator valueMutator, PseudoRandom prng) { Collection> collection = map.entrySet(); @@ -182,7 +233,7 @@ public static void mutateRandomValuesChunk( static boolean growBy( Set set, Consumer addIfNew, int delta, Supplier candidateSupplier) { int oldSize = set.size(); - Preconditions.require(delta >= 0); + require(delta >= 0); final int targetSize = oldSize + delta; int remainingAttempts = MAX_FAILED_INSERTION_ATTEMPTS; diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/CollectionMutators.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/CollectionMutators.java index 88152589e..e8ba34d60 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/CollectionMutators.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/CollectionMutators.java @@ -23,6 +23,10 @@ public final class CollectionMutators { private CollectionMutators() {} public static Stream newFactories() { - return Stream.of(new ListMutatorFactory(), new MapMutatorFactory(), new ArrayMutatorFactory()); + return Stream.of( + new ListMutatorFactory(), + new MapMutatorFactory(), + new SetMutatorFactory(), + new ArrayMutatorFactory()); } } diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/SetMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/SetMutatorFactory.java new file mode 100644 index 000000000..0df923f6a --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/SetMutatorFactory.java @@ -0,0 +1,208 @@ +/* + * Copyright 2025 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.mutator.collection; + +import static com.code_intelligence.jazzer.mutation.mutator.collection.ChunkCrossOvers.CrossOverAction.pickRandomCrossOverAction; +import static com.code_intelligence.jazzer.mutation.mutator.collection.ChunkCrossOvers.crossOverChunk; +import static com.code_intelligence.jazzer.mutation.mutator.collection.ChunkCrossOvers.insertChunk; +import static com.code_intelligence.jazzer.mutation.mutator.collection.ChunkCrossOvers.overwriteChunk; +import static com.code_intelligence.jazzer.mutation.mutator.collection.ChunkMutations.MutationAction.pickRandomMutationAction; +import static com.code_intelligence.jazzer.mutation.mutator.collection.ChunkMutations.deleteRandomChunk; +import static com.code_intelligence.jazzer.mutation.mutator.collection.ChunkMutations.growBy; +import static com.code_intelligence.jazzer.mutation.mutator.collection.ChunkMutations.insertRandomChunk; +import static com.code_intelligence.jazzer.mutation.mutator.collection.ChunkMutations.mutateRandomKeysChunk; +import static com.code_intelligence.jazzer.mutation.support.Preconditions.require; +import static com.code_intelligence.jazzer.mutation.support.PropertyConstraintSupport.propagatePropertyConstraints; +import static com.code_intelligence.jazzer.mutation.support.TypeSupport.parameterTypesIfParameterized; +import static java.lang.Math.min; +import static java.lang.String.format; +import static java.util.stream.Collectors.toSet; + +import com.code_intelligence.jazzer.mutation.annotation.WithSize; +import com.code_intelligence.jazzer.mutation.api.Debuggable; +import com.code_intelligence.jazzer.mutation.api.ExtendedMutatorFactory; +import com.code_intelligence.jazzer.mutation.api.MutatorFactory; +import com.code_intelligence.jazzer.mutation.api.PseudoRandom; +import com.code_intelligence.jazzer.mutation.api.SerializingInPlaceMutator; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.RandomSupport; +import com.code_intelligence.jazzer.mutation.support.StreamSupport; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedType; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +final class SetMutatorFactory implements MutatorFactory { + @Override + public Optional> tryCreate( + AnnotatedType type, ExtendedMutatorFactory factory) { + return parameterTypesIfParameterized(type, Set.class) + .map( + parameterTypes -> + parameterTypes.stream() + .map(innerType -> propagatePropertyConstraints(type, innerType)) + .map(factory::tryCreate) + .flatMap(StreamSupport::getOrEmpty) + .collect(Collectors.toList())) + .filter(elementMutator -> elementMutator.size() == 1) + .map( + elementMutator -> { + int min = SetMutator.DEFAULT_MIN_SIZE; + int max = SetMutator.DEFAULT_MAX_SIZE; + for (Annotation annotation : type.getDeclaredAnnotations()) { + if (annotation instanceof WithSize) { + WithSize withSize = (WithSize) annotation; + min = withSize.min(); + max = withSize.max(); + } + } + + return new SetMutator<>(elementMutator.get(0), min, max); + }); + } + + private static final class SetMutator extends SerializingInPlaceMutator> { + private static final int DEFAULT_MIN_SIZE = 0; + private static final int DEFAULT_MAX_SIZE = 1000; + + private final SerializingMutator keyMutator; + private final int minSize; + private final int maxSize; + + SetMutator(SerializingMutator keyMutator, int minSize, int maxSize) { + this.keyMutator = keyMutator; + this.minSize = Math.max(minSize, DEFAULT_MIN_SIZE); + this.maxSize = Math.min(maxSize, DEFAULT_MAX_SIZE); + + require(maxSize >= 1, format("WithSize#max=%d needs to be greater than 0", maxSize)); + // TODO: Add support for min > 0 to set. If min > 0, then #read can fail to construct + // sufficiently many distinct keys, but the mutation framework currently doesn't offer + // a way to handle this situation gracefully. It is also not clear what behavior users + // could reasonably expect in this situation in both regression test and fuzzing mode. + require(minSize == 0, "@WithSize#min != 0 is not yet supported for Set"); + } + + @Override + public Set read(DataInputStream in) throws IOException { + int size = RandomSupport.clamp(in.readInt(), minSize, maxSize); + Set set = new LinkedHashSet<>(size); + for (int i = 0; i < size; i++) { + set.add(keyMutator.read(in)); + } + // set may have less than size entries due to the potential for duplicates, but this is fine + // as we currently assert that minSize == 0. + return set; + } + + @Override + public void write(Set set, DataOutputStream out) throws IOException { + out.writeInt(set.size()); + for (K entry : set) { + keyMutator.write(entry, out); + } + } + + @Override + protected Set makeDefaultInstance() { + return new LinkedHashSet<>(maxInitialSize()); + } + + @Override + public void initInPlace(Set set, PseudoRandom prng) { + int targetSize = prng.closedRange(minInitialSize(), maxInitialSize()); + set.clear(); + growBy(set, set::add, targetSize, () -> keyMutator.init(prng)); + if (set.size() < minSize) { + throw new IllegalStateException( + String.format( + "Failed to create %d distinct elements of type %s to satisfy the @WithSize#minSize" + + " constraint on Set", + minSize, keyMutator)); + } + } + + @Override + public void mutateInPlace(Set set, PseudoRandom prng) { + switch (pickRandomMutationAction(set, minSize, maxSize, prng)) { + case DELETE_CHUNK: + deleteRandomChunk(set, minSize, prng, entriesHaveFixedSize()); + break; + case INSERT_CHUNK: + insertRandomChunk(set, set::add, maxSize, keyMutator, prng); + break; + case MUTATE_CHUNK: + mutateRandomKeysChunk(set, keyMutator, prng); + break; + default: + throw new IllegalStateException("unsupported action"); + } + } + + @Override + public void crossOverInPlace(Set reference, Set otherReference, PseudoRandom prng) { + switch (pickRandomCrossOverAction(reference, otherReference, maxSize, prng)) { + case INSERT_CHUNK: + insertChunk(reference, otherReference, maxSize, prng, entriesHaveFixedSize()); + break; + case OVERWRITE_CHUNK: + overwriteChunk(reference, otherReference, prng, entriesHaveFixedSize()); + break; + case CROSS_OVER_CHUNK: + crossOverChunk(reference, otherReference, keyMutator, prng); + break; + default: + // Both maps are empty or could otherwise not be crossed over. + } + } + + private boolean entriesHaveFixedSize() { + return keyMutator.hasFixedSize(); + } + + @Override + public boolean hasFixedSize() { + return false; + } + + @Override + public Set detach(Set value) { + return value.stream().map(keyMutator::detach).collect(toSet()); + } + + @Override + public String toDebugString(Predicate isInCycle) { + return "Set<" + keyMutator.toDebugString(isInCycle) + ">"; + } + + private int minInitialSize() { + return minSize; + } + + private int maxInitialSize() { + if (keyMutator.requiresRecursionBreaking()) { + return minInitialSize(); + } + return min(maxSize, minSize + 1); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java index 5c59ed8ad..87d5f932a 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java @@ -94,6 +94,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; @@ -634,6 +635,25 @@ null, emptyList(), singletonList(null), singletonList(false), singletonList(true asMap(false, false, true, true), asMap(false, true, true, false), asMap(false, true, true, true))), + arguments( + new TypeHolder<@NotNull Set<@NotNull String>>() {}.annotatedType(), + "Set", + false, + distinctElementsRatio(0.45), + distinctElementsRatio(0.45)), + arguments( + new TypeHolder>() {}.annotatedType(), + "Nullable>", + false, + distinctElementsRatio(0.46), + distinctElementsRatio(0.48)), + arguments( + new TypeHolder<@WithSize(max = 3) @NotNull Set<@NotNull Integer>>() {}.annotatedType(), + "Set", + false, + // Half of all sets are empty, the other half is heavily biased towards special values. + all(setSizeInClosedRange(0, 3), distinctElementsRatio(0.09)), + all(setSizeInClosedRange(0, 3), manyDistinctElements())), arguments( new ParameterHolder() { void singleParam(byte parameter) {} @@ -1344,6 +1364,24 @@ public void close() {} }; } + private static CloseableConsumer setSizeInClosedRange(int min, int max) { + return new CloseableConsumer() { + @Override + public void accept(Object set) { + if (set instanceof Set) { + assertThat(((Set) set).size()).isAtLeast(min); + assertThat(((Set) set).size()).isAtMost(max); + } else { + throw new IllegalArgumentException( + "Expected a list of sets, got list of" + set.getClass().getName()); + } + } + + @Override + public void close() {} + }; + } + interface CloseableConsumer extends AutoCloseable, Consumer {} @SuppressWarnings("rawtypes") 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 8417ed238..b9ea5e8c2 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 @@ -42,6 +42,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Queue; import java.util.function.BiConsumer; @@ -428,6 +429,11 @@ public static LinkedHashMap asMap(Object... objs) { return map; } + @SuppressWarnings("unchecked") + public static LinkedHashSet asSet(K... objs) { + return new LinkedHashSet<>(Arrays.asList(objs)); + } + @SafeVarargs public static ArrayList asMutableList(T... objs) { return stream(objs).collect(toCollection(ArrayList::new));