Skip to content

Commit 026bf3f

Browse files
committed
feat: add set mutator
1 parent 778f780 commit 026bf3f

8 files changed

Lines changed: 420 additions & 4 deletions

File tree

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
@@ -41,6 +41,7 @@
4141
import java.util.Arrays;
4242
import java.util.List;
4343
import java.util.Map;
44+
import java.util.Set;
4445
import java.util.stream.Collectors;
4546

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

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

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,10 +26,11 @@
2626
import java.lang.annotation.Target;
2727
import java.util.List;
2828
import java.util.Map;
29+
import java.util.Set;
2930

3031
@Target(TYPE_USE)
3132
@Retention(RUNTIME)
32-
@AppliesTo({List.class, Map.class})
33+
@AppliesTo({List.class, Map.class, Set.class})
3334
@ValidateContainerDimensions
3435
@PropertyConstraint
3536
public @interface WithSize {

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

Lines changed: 100 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 map 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,59 @@ static <K, V> void overwriteChunk(
117138
hasFixedSize);
118139
}
119140

141+
static <K> void overwriteChunk(
142+
Set<K> set, Set<K> otherSet, PseudoRandom prng, boolean hasFixedSize) {
143+
onCorrespondingChunks(
144+
set,
145+
otherSet,
146+
prng,
147+
(fromIterator, toIterator, chunkSize) -> {
148+
// As keys can not be overwritten, only removed and new ones added, this
149+
// cross over overwrites the values. Removal of keys is handled by the
150+
// removeChunk mutation. Value equality is not checked here.
151+
for (int i = 0; i < chunkSize; i++) {
152+
K from = fromIterator.next();
153+
K to = toIterator.next();
154+
}
155+
},
156+
hasFixedSize);
157+
}
158+
159+
static <K> void crossOverChunk(
160+
Set<K> set, Set<K> otherSet, SerializingMutator<K> keyMutator, PseudoRandom prng) {
161+
onCorrespondingChunks(
162+
set,
163+
otherSet,
164+
prng,
165+
(fromIterator, toIterator, chunkSize) -> {
166+
Set<K> entriesToAdd = new LinkedHashSet<>(chunkSize);
167+
for (int i = 0; i < chunkSize; i++) {
168+
K to = toIterator.next();
169+
K from = fromIterator.next();
170+
171+
// The entry has to be removed from the map before the cross-over, as
172+
// mutating its key could cause problems in subsequent lookups.
173+
// Furthermore, no new entries may be added while using the iterator,
174+
// so crossed-over keys are collected for later addition.
175+
toIterator.remove();
176+
177+
// As cross-overs do not guarantee to mutate the given object, no
178+
// checks if the crossed over key already exists in the map are
179+
// performed. This potentially overwrites existing entries or
180+
// generates equal keys.
181+
// In case of cross over this behavior is acceptable.
182+
K newKey = keyMutator.crossOver(to, from, prng);
183+
184+
// Prevent null keys, as those are not allowed in some map implementations.
185+
if (newKey != null) {
186+
entriesToAdd.add(newKey);
187+
}
188+
}
189+
set.addAll(entriesToAdd);
190+
},
191+
keyMutator.hasFixedSize());
192+
}
193+
120194
static <K, V> void crossOverChunk(
121195
Map<K, V> map,
122196
Map<K, V> otherMap,
@@ -198,6 +272,11 @@ private interface ChunkMapOperation<K, V> {
198272
void apply(Iterator<Entry<K, V>> fromIterator, Iterator<Entry<K, V>> toIterator, int chunkSize);
199273
}
200274

275+
@FunctionalInterface
276+
private interface ChunkSetOperation<K> {
277+
void apply(Iterator<K> fromIterator, Iterator<K> toIterator, int chunkSize);
278+
}
279+
201280
static <K, V> void onCorrespondingChunks(
202281
Map<K, V> map,
203282
Map<K, V> otherMap,
@@ -219,6 +298,27 @@ static <K, V> void onCorrespondingChunks(
219298
operation.apply(fromIterator, toIterator, chunkSize);
220299
}
221300

301+
static <K> void onCorrespondingChunks(
302+
Set<K> set,
303+
Set<K> otherSet,
304+
PseudoRandom prng,
305+
ChunkSetOperation<K> operation,
306+
boolean hasFixedSize) {
307+
int maxChunkSize = Math.min(set.size(), otherSet.size());
308+
int chunkSize = prng.sizeInClosedRange(1, maxChunkSize, hasFixedSize);
309+
int fromChunkOffset = prng.closedRange(0, otherSet.size() - chunkSize);
310+
int toChunkOffset = prng.closedRange(0, set.size() - chunkSize);
311+
Iterator<K> fromIterator = otherSet.iterator();
312+
for (int i = 0; i < fromChunkOffset; i++) {
313+
fromIterator.next();
314+
}
315+
Iterator<K> toIterator = set.iterator();
316+
for (int i = 0; i < toChunkOffset; i++) {
317+
toIterator.next();
318+
}
319+
operation.apply(fromIterator, toIterator, chunkSize);
320+
}
321+
222322
public enum CrossOverAction {
223323
INSERT_CHUNK,
224324
OVERWRITE_CHUNK,

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

Lines changed: 53 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,56 @@ static <K, V, KW, VW> boolean mutateRandomKeysChunk(
162163
return grownBy > 0;
163164
}
164165

166+
static <K, KW> boolean mutateRandomKeysChunk(
167+
Set<K> set, SerializingMutator<K> keyMutator, PseudoRandom prng) {
168+
int originalSize = set.size();
169+
int chunkSize = prng.sizeInClosedRange(1, originalSize, keyMutator.hasFixedSize());
170+
int chunkOffset = prng.closedRange(0, originalSize - chunkSize);
171+
172+
// To ensure that mutating keys actually results in the set of keys changing, we keep the keys
173+
// to mutate in the set, try to add new keys (that are therefore distinct from the keys to
174+
// mutate) and only remove the successfully mutated keys in the end.
175+
ArrayDeque<KW> keysToMutate = new ArrayDeque<>(chunkSize);
176+
ArrayList<K> keysToRemove = new ArrayList<>(chunkSize);
177+
// get the set iterator
178+
Iterator<K> it = set.iterator();
179+
for (int i = 0; i < chunkOffset; i++) {
180+
it.next();
181+
}
182+
for (int i = chunkOffset; i < chunkOffset + chunkSize; i++) {
183+
K entry = it.next();
184+
// ArrayDeque cannot hold null elements, which requires us to replace null with a sentinel.
185+
// Also detach the key as keys may be mutable and mutation could destroy them.
186+
keysToMutate.add(boxNull(keyMutator.detach(entry)));
187+
keysToRemove.add(entry);
188+
}
189+
190+
Consumer<K> addIfNew =
191+
key -> {
192+
int sizeBeforeAdd = set.size();
193+
set.add(key);
194+
// The mutated key was new, try to mutate and add the next in line.
195+
if (set.size() > sizeBeforeAdd) {
196+
keysToMutate.removeFirst();
197+
}
198+
};
199+
Supplier<K> nextCandidate =
200+
() -> {
201+
// Mutate the next candidate in the queue.
202+
K candidate = keyMutator.mutate(unboxNull(keysToMutate.removeFirst()), prng);
203+
keysToMutate.addFirst(boxNull(candidate));
204+
return candidate;
205+
};
206+
207+
growBy(set, addIfNew, chunkSize, nextCandidate);
208+
// Remove the original keys that were successfully mutated into new keys. Since the original
209+
// keys have been kept in the set up to this point, all keys added were successfully mutated to
210+
// be unequal to the original keys.
211+
int grownBy = set.size() - originalSize;
212+
keysToRemove.stream().limit(grownBy).forEach(set::remove);
213+
return grownBy > 0;
214+
}
215+
165216
public static <K, V> void mutateRandomValuesChunk(
166217
Map<K, V> map, ValueMutator<V> valueMutator, PseudoRandom prng) {
167218
Collection<Map.Entry<K, V>> collection = map.entrySet();
@@ -182,7 +233,7 @@ public static <K, V> void mutateRandomValuesChunk(
182233
static <T> boolean growBy(
183234
Set<T> set, Consumer<T> addIfNew, int delta, Supplier<T> candidateSupplier) {
184235
int oldSize = set.size();
185-
Preconditions.require(delta >= 0);
236+
require(delta >= 0);
186237

187238
final int targetSize = oldSize + delta;
188239
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)