Skip to content

Commit 43b5a02

Browse files
committed
feat: add set mutator
1 parent 0bfb60e commit 43b5a02

File tree

10 files changed

+963
-5
lines changed

10 files changed

+963
-5
lines changed

selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.Arrays;
4343
import java.util.List;
4444
import java.util.Map;
45+
import java.util.Set;
4546
import java.util.stream.Collectors;
4647

4748
public class ArgumentsMutatorFuzzTest {
@@ -109,6 +110,13 @@ void fuzzListOfMaps(@WithSize(max = 4) Map<String, Integer> nullableMap) {
109110
}
110111
}
111112

113+
@SelfFuzzTest
114+
void fuzzListOfSets(@WithSize(max = 10) @NotNull Set<@NotNull Integer> setWithSize) {
115+
if (setWithSize != null) {
116+
assertThat(setWithSize.size()).isAtMost(10);
117+
}
118+
}
119+
112120
@SelfFuzzTest
113121
void fuzzListOfLists(List<@NotNull List<String>> nullableMap, List<List<Integer>> nullableList) {}
114122

src/main/java/com/code_intelligence/jazzer/mutation/annotation/WithSize.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.lang.annotation.Target;
2727
import java.util.List;
2828
import java.util.Map;
29+
import java.util.Set;
2930

3031
/**
3132
* Generates a {@link List} or {@link Map} with the specified size.
@@ -35,7 +36,7 @@
3536
*/
3637
@Target(TYPE_USE)
3738
@Retention(RUNTIME)
38-
@AppliesTo({List.class, Map.class})
39+
@AppliesTo({List.class, Map.class, Set.class})
3940
@ValidateContainerDimensions
4041
@PropertyConstraint
4142
public @interface WithSize {

src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkCrossOvers.java

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
import java.util.Collection;
2323
import java.util.Iterator;
2424
import java.util.LinkedHashMap;
25+
import java.util.LinkedHashSet;
2526
import java.util.List;
2627
import java.util.Map;
2728
import java.util.Map.Entry;
29+
import java.util.Set;
2830

2931
final class ChunkCrossOvers {
3032
private ChunkCrossOvers() {}
@@ -98,6 +100,25 @@ static <K, V> void insertChunk(
98100
}
99101
}
100102

103+
static <K> void insertChunk(
104+
Set<K> set, Set<K> otherSet, int maxSize, PseudoRandom prng, boolean hasFixedSize) {
105+
int originalSize = set.size();
106+
int maxChunkSize = Math.min(maxSize - originalSize, otherSet.size());
107+
int chunkSize = prng.sizeInClosedRange(1, maxChunkSize, hasFixedSize);
108+
int fromChunkOffset = prng.closedRange(0, otherSet.size() - chunkSize);
109+
Iterator<K> fromIterator = otherSet.iterator();
110+
for (int i = 0; i < fromChunkOffset; i++) {
111+
fromIterator.next();
112+
}
113+
// insertChunk only inserts new entries and does not overwrite existing
114+
// ones. As skipping those entries would lead to fewer insertions than
115+
// requested, loop over the rest of the set to fill the chunk if possible.
116+
while (set.size() < originalSize + chunkSize && fromIterator.hasNext()) {
117+
K key = fromIterator.next();
118+
set.add(key);
119+
}
120+
}
121+
101122
static <K, V> void overwriteChunk(
102123
Map<K, V> map, Map<K, V> otherMap, PseudoRandom prng, boolean hasFixedSize) {
103124
onCorrespondingChunks(
@@ -117,6 +138,24 @@ static <K, V> void overwriteChunk(
117138
hasFixedSize);
118139
}
119140

141+
static <E> void overwriteChunk(
142+
Set<E> set, Set<E> otherSet, PseudoRandom prng, boolean hasFixedSize) {
143+
onCorrespondingChunks(
144+
set,
145+
otherSet,
146+
prng,
147+
(fromIterator, toIterator, chunkSize) -> {
148+
Set<E> elementsToAdd = new LinkedHashSet<>(chunkSize);
149+
for (int i = 0; i < chunkSize; i++) {
150+
toIterator.next();
151+
toIterator.remove();
152+
elementsToAdd.add(fromIterator.next());
153+
}
154+
set.addAll(elementsToAdd);
155+
},
156+
hasFixedSize);
157+
}
158+
120159
static <K, V> void crossOverChunk(
121160
Map<K, V> map,
122161
Map<K, V> otherMap,
@@ -130,6 +169,23 @@ static <K, V> void crossOverChunk(
130169
}
131170
}
132171

172+
static <E> void crossOverChunk(
173+
Set<E> set, Set<E> otherSet, SerializingMutator<E> mutator, PseudoRandom prng) {
174+
onCorrespondingChunks(
175+
set,
176+
otherSet,
177+
prng,
178+
(fromIterator, toIterator, chunkSize) -> {
179+
Set<E> elementsToAdd = new LinkedHashSet<>(chunkSize);
180+
for (int i = 0; i < chunkSize; i++) {
181+
elementsToAdd.add(mutator.crossOver(toIterator.next(), fromIterator.next(), prng));
182+
toIterator.remove();
183+
}
184+
set.addAll(elementsToAdd);
185+
},
186+
mutator.hasFixedSize());
187+
}
188+
133189
private static <K, V> void crossOverChunkKeys(
134190
Map<K, V> map, Map<K, V> otherMap, SerializingMutator<K> keyMutator, PseudoRandom prng) {
135191
onCorrespondingChunks(
@@ -198,6 +254,11 @@ private interface ChunkMapOperation<K, V> {
198254
void apply(Iterator<Entry<K, V>> fromIterator, Iterator<Entry<K, V>> toIterator, int chunkSize);
199255
}
200256

257+
@FunctionalInterface
258+
private interface ChunkSetOperation<E> {
259+
void apply(Iterator<E> fromIterator, Iterator<E> toIterator, int chunkSize);
260+
}
261+
201262
static <K, V> void onCorrespondingChunks(
202263
Map<K, V> map,
203264
Map<K, V> otherMap,
@@ -219,6 +280,32 @@ static <K, V> void onCorrespondingChunks(
219280
operation.apply(fromIterator, toIterator, chunkSize);
220281
}
221282

283+
static <K> void onCorrespondingChunks(
284+
Set<K> set,
285+
Set<K> otherSet,
286+
PseudoRandom prng,
287+
ChunkSetOperation<K> operation,
288+
boolean hasFixedSize) {
289+
290+
if (set.isEmpty() || otherSet.isEmpty()) {
291+
return;
292+
}
293+
294+
int maxChunkSize = Math.min(set.size(), otherSet.size());
295+
int chunkSize = prng.sizeInClosedRange(1, maxChunkSize, hasFixedSize);
296+
int fromChunkOffset = prng.closedRange(0, otherSet.size() - chunkSize);
297+
int toChunkOffset = prng.closedRange(0, set.size() - chunkSize);
298+
Iterator<K> fromIterator = otherSet.iterator();
299+
for (int i = 0; i < fromChunkOffset; i++) {
300+
fromIterator.next();
301+
}
302+
Iterator<K> toIterator = set.iterator();
303+
for (int i = 0; i < toChunkOffset; i++) {
304+
toIterator.next();
305+
}
306+
operation.apply(fromIterator, toIterator, chunkSize);
307+
}
308+
222309
public enum CrossOverAction {
223310
INSERT_CHUNK,
224311
OVERWRITE_CHUNK,

src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/ChunkMutations.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

1717
package com.code_intelligence.jazzer.mutation.mutator.collection;
1818

19+
import static com.code_intelligence.jazzer.mutation.support.Preconditions.require;
20+
1921
import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
2022
import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
2123
import com.code_intelligence.jazzer.mutation.api.ValueMutator;
22-
import com.code_intelligence.jazzer.mutation.support.Preconditions;
2324
import java.util.AbstractList;
2425
import java.util.ArrayDeque;
2526
import java.util.ArrayList;
@@ -162,6 +163,52 @@ static <K, V, KW, VW> boolean mutateRandomKeysChunk(
162163
return grownBy > 0;
163164
}
164165

166+
static <E> boolean mutateRandomChunk(
167+
Set<E> set, SerializingMutator<E> elementMutator, PseudoRandom prng) {
168+
int originalSize = set.size();
169+
require(originalSize > 0, "mutateRandomSetChunk requires set size > 0");
170+
int chunkSize = prng.sizeInClosedRange(1, originalSize, elementMutator.hasFixedSize());
171+
int chunkOffset = prng.closedRange(0, originalSize - chunkSize);
172+
173+
Iterator<E> iterator = set.iterator();
174+
175+
// Skip to chunk offset
176+
for (int i = 0; i < chunkOffset; i++) {
177+
iterator.next();
178+
}
179+
180+
// Collect elements to mutate and remove
181+
List<E> originalElements = new ArrayList<>(chunkSize);
182+
List<E> elementsToMutate = new ArrayList<>(chunkSize);
183+
for (int i = 0; i < chunkSize; i++) {
184+
E element = iterator.next();
185+
originalElements.add(element);
186+
elementsToMutate.add(elementMutator.detach(element));
187+
}
188+
189+
// Try mutating each chunk element into a yet-not-present element in the set.
190+
// Abort after MAX_FAILED_INSERTION_ATTEMPTS failed mutations in total.
191+
int successCount = 0;
192+
int failedAttemptsCount = 0;
193+
for (E element : elementsToMutate) {
194+
while (failedAttemptsCount < MAX_FAILED_INSERTION_ATTEMPTS) {
195+
// Each element keeps getting mutated until we get a completely novel element.
196+
element = elementMutator.mutate(element, prng);
197+
if (set.add(element)) {
198+
successCount++;
199+
break;
200+
} else {
201+
failedAttemptsCount++;
202+
}
203+
}
204+
}
205+
206+
for (int i = 0; i < successCount; i++) {
207+
set.remove(originalElements.get(i));
208+
}
209+
return successCount > 0;
210+
}
211+
165212
public static <K, V> void mutateRandomValuesChunk(
166213
Map<K, V> map, ValueMutator<V> valueMutator, PseudoRandom prng) {
167214
Collection<Map.Entry<K, V>> collection = map.entrySet();
@@ -182,7 +229,7 @@ public static <K, V> void mutateRandomValuesChunk(
182229
static <T> boolean growBy(
183230
Set<T> set, Consumer<T> addIfNew, int delta, Supplier<T> candidateSupplier) {
184231
int oldSize = set.size();
185-
Preconditions.require(delta >= 0);
232+
require(delta >= 0);
186233

187234
final int targetSize = oldSize + delta;
188235
int remainingAttempts = MAX_FAILED_INSERTION_ATTEMPTS;

src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/CollectionMutators.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ public final class CollectionMutators {
2323
private CollectionMutators() {}
2424

2525
public static Stream<MutatorFactory> newFactories() {
26-
return Stream.of(new ListMutatorFactory(), new MapMutatorFactory(), new ArrayMutatorFactory());
26+
return Stream.of(
27+
new ListMutatorFactory(),
28+
new MapMutatorFactory(),
29+
new SetMutatorFactory(),
30+
new ArrayMutatorFactory());
2731
}
2832
}

0 commit comments

Comments
 (0)