Skip to content

Commit 3045e33

Browse files
committed
feat: mutate lists using user-provided dictionaries
1 parent 73ef0d0 commit 3045e33

2 files changed

Lines changed: 127 additions & 2 deletions

File tree

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

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@
2727
import static com.code_intelligence.jazzer.mutation.mutator.collection.ChunkMutations.mutateRandomChunk;
2828
import static com.code_intelligence.jazzer.mutation.support.Preconditions.require;
2929
import static com.code_intelligence.jazzer.mutation.support.PropertyConstraintSupport.propagatePropertyConstraints;
30+
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.canBeAssignedTo;
3031
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.parameterTypeIfParameterized;
3132
import static java.lang.Math.min;
3233
import static java.lang.String.format;
3334

35+
import com.code_intelligence.jazzer.mutation.annotation.DictionaryObject;
36+
import com.code_intelligence.jazzer.mutation.annotation.DictionaryProvider;
3437
import com.code_intelligence.jazzer.mutation.annotation.WithSize;
3538
import com.code_intelligence.jazzer.mutation.api.Debuggable;
3639
import com.code_intelligence.jazzer.mutation.api.ExtendedMutatorFactory;
@@ -44,10 +47,12 @@
4447
import java.io.IOException;
4548
import java.lang.reflect.AnnotatedType;
4649
import java.util.ArrayList;
50+
import java.util.Arrays;
4751
import java.util.List;
4852
import java.util.Optional;
4953
import java.util.function.Predicate;
5054
import java.util.stream.Collectors;
55+
import java.util.stream.Stream;
5156

5257
final class ListMutatorFactory implements MutatorFactory {
5358
@Override
@@ -61,7 +66,7 @@ public Optional<SerializingMutator<?>> tryCreate(
6166
Optional<WithSize> withSize = Optional.ofNullable(type.getAnnotation(WithSize.class));
6267
int minSize = withSize.map(WithSize::min).orElse(ListMutator.DEFAULT_MIN_SIZE);
6368
int maxSize = withSize.map(WithSize::max).orElse(ListMutator.DEFAULT_MAX_SIZE);
64-
return new ListMutator<>(elementMutator, minSize, maxSize);
69+
return new ListMutator<>(type, elementMutator, minSize, maxSize);
6570
});
6671
}
6772

@@ -72,8 +77,10 @@ private static final class ListMutator<T> extends SerializingInPlaceMutator<List
7277
private final SerializingMutator<T> elementMutator;
7378
private final int minSize;
7479
private final int maxSize;
80+
public final List<List<T>> dictionaryValues;
7581

76-
ListMutator(SerializingMutator<T> elementMutator, int minSize, int maxSize) {
82+
ListMutator(
83+
AnnotatedType type, SerializingMutator<T> elementMutator, int minSize, int maxSize) {
7784
this.elementMutator = elementMutator;
7885
this.minSize = minSize;
7986
this.maxSize = maxSize;
@@ -84,6 +91,29 @@ private static final class ListMutator<T> extends SerializingInPlaceMutator<List
8491
format(
8592
"WithSize#min=%d needs to be smaller or equal than WithSize#max=%d",
8693
minSize, maxSize));
94+
95+
DictionaryObject[] dictObj = type.getAnnotationsByType(DictionaryObject.class);
96+
System.out.println(
97+
"DICT OBJECTS for List : " + type + " ------ " + Arrays.toString(dictObj));
98+
99+
dictionaryValues =
100+
(List<List<T>>)
101+
Arrays.stream(dictObj)
102+
.flatMap(
103+
o -> {
104+
Class<? extends DictionaryProvider> providerClass = o.value();
105+
try {
106+
DictionaryProvider provider =
107+
providerClass.getDeclaredConstructor().newInstance();
108+
return provider.value();
109+
} catch (ReflectiveOperationException e) {
110+
return Stream.empty();
111+
}
112+
})
113+
.filter(v -> canBeAssignedTo(v, type))
114+
.toList();
115+
116+
System.out.println(" ------ " + this.dictionaryValues);
87117
}
88118

89119
@Override
@@ -120,6 +150,17 @@ public void initInPlace(List<T> list, PseudoRandom prng) {
120150

121151
@Override
122152
public void mutateInPlace(List<T> list, PseudoRandom prng) {
153+
154+
// With a small probability, replace the entire list with a dictionary value
155+
if (!dictionaryValues.isEmpty() && prng.indexIn(20) == 0) {
156+
list.clear();
157+
// copy the list from the dictionary to avoid mutating the dictionary itself
158+
List<T> ref = dictionaryValues.get(prng.indexIn(dictionaryValues.size()));
159+
list.addAll(ref);
160+
System.out.println("LIST MUTATE IN PLACE - DICTIONARY VALUE: " + ref);
161+
return;
162+
}
163+
123164
switch (pickRandomMutationAction(list, minSize, maxSize, prng)) {
124165
case DELETE_CHUNK:
125166
deleteRandomChunk(list, minSize, prng, elementMutator.hasFixedSize());

src/main/java/com/code_intelligence/jazzer/mutation/support/TypeSupport.java

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@
4242
import java.lang.reflect.Type;
4343
import java.util.ArrayDeque;
4444
import java.util.Arrays;
45+
import java.util.Collection;
4546
import java.util.Collections;
4647
import java.util.HashSet;
4748
import java.util.List;
49+
import java.util.Map;
4850
import java.util.Optional;
4951
import java.util.Set;
5052
import java.util.function.BiConsumer;
@@ -154,6 +156,88 @@ public boolean equals(Object obj) {
154156
};
155157
}
156158

159+
public static boolean canBeAssignedTo(Object obj, AnnotatedType annotatedType) {
160+
if (obj == null) {
161+
return true; // null can be assigned to any reference type
162+
}
163+
164+
return isAssignableFrom(obj, annotatedType);
165+
}
166+
167+
private static boolean isAssignableFrom(Object obj, AnnotatedType targetType) {
168+
Type type = targetType.getType();
169+
170+
if (type instanceof Class<?>) {
171+
Class<?> targetClass = (Class<?>) type;
172+
return targetClass.isInstance(obj);
173+
} else if (type instanceof ParameterizedType) {
174+
ParameterizedType parameterizedType = (ParameterizedType) type;
175+
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
176+
177+
// First check if the raw type matches
178+
if (!rawType.isInstance(obj)) {
179+
return false;
180+
}
181+
182+
// For collections, try to check the element types
183+
if (targetType instanceof AnnotatedParameterizedType) {
184+
return checkParameterizedType(obj, (AnnotatedParameterizedType) targetType);
185+
}
186+
}
187+
188+
return false;
189+
}
190+
191+
private static boolean checkParameterizedType(
192+
Object obj, AnnotatedParameterizedType annotatedParamType) {
193+
ParameterizedType paramType = (ParameterizedType) annotatedParamType.getType();
194+
Class<?> rawType = (Class<?>) paramType.getRawType();
195+
AnnotatedType[] typeArguments = annotatedParamType.getAnnotatedActualTypeArguments();
196+
197+
// Handle common collection types
198+
if (Collection.class.isAssignableFrom(rawType) && obj instanceof Collection<?>) {
199+
return checkCollection((Collection<?>) obj, typeArguments);
200+
} else if (Map.class.isAssignableFrom(rawType) && obj instanceof Map<?, ?>) {
201+
return checkMap((Map<?, ?>) obj, typeArguments);
202+
}
203+
204+
// For other parameterized types, we can't easily check at runtime
205+
// This is a limitation of type erasure
206+
return true; // Assume compatible if we can't check
207+
}
208+
209+
private static boolean checkCollection(Collection<?> collection, AnnotatedType[] typeArguments) {
210+
if (typeArguments.length != 1) {
211+
return false; // Collections should have exactly one type parameter
212+
}
213+
214+
AnnotatedType elementType = typeArguments[0];
215+
216+
// Check a sample of elements (checking all could be expensive for large collections)
217+
int sampleSize = Math.min(10, collection.size());
218+
return collection.stream()
219+
.limit(sampleSize)
220+
.allMatch(element -> canBeAssignedTo(element, elementType));
221+
}
222+
223+
private static boolean checkMap(Map<?, ?> map, AnnotatedType[] typeArguments) {
224+
if (typeArguments.length != 2) {
225+
return false; // Maps should have exactly two type parameters
226+
}
227+
228+
AnnotatedType keyType = typeArguments[0];
229+
AnnotatedType valueType = typeArguments[1];
230+
231+
// Check a sample of entries
232+
int sampleSize = Math.min(10, map.size());
233+
return map.entrySet().stream()
234+
.limit(sampleSize)
235+
.allMatch(
236+
entry ->
237+
canBeAssignedTo(entry.getKey(), keyType)
238+
&& canBeAssignedTo(entry.getValue(), valueType));
239+
}
240+
157241
/**
158242
* Visits the individual classes and their directly present annotations that make up the given
159243
* type.

0 commit comments

Comments
 (0)