Skip to content

Commit

Permalink
mutation: Use DynamicMessage#getDefaultInstance to simplify TypeLibrary
Browse files Browse the repository at this point in the history
Centralizes message subfield mutator creation in `BuilderMutatorFactory`
and makes it so that `TypeLibrary` no longer needs a concrete `Builder`
instance or knowledge of annotations.
  • Loading branch information
fmeum committed Apr 21, 2023
1 parent fc7c9e5 commit 854f59a
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,12 @@ static <T extends Builder, K, V> Map<K, V> getMapField(T builder, FieldDescripto
static <T extends Builder, K, V> void setMapField(
Builder builder, FieldDescriptor field, Map<K, V> map) {
builder.clearField(field);
FieldDescriptor keyField = field.getMessageType().getFields().get(0);
FieldDescriptor valueField = field.getMessageType().getFields().get(1);
Builder entryBuilder = builder.newBuilderForField(field);
for (Entry<K, V> entry : map.entrySet()) {
MapEntry.Builder<K, V> entryBuilder = (MapEntry.Builder) builder.newBuilderForField(field);
entryBuilder.setKey(entry.getKey());
entryBuilder.setValue(entry.getValue());
entryBuilder.setField(keyField, entry.getKey());
entryBuilder.setField(valueField, entry.getValue());
builder.addRepeatedField(field, entryBuilder.build());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,16 @@
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.withExtraAnnotations;
import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import static java.util.function.UnaryOperator.identity;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

import com.code_intelligence.jazzer.mutation.annotation.proto.AnySource;
import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory;
import com.code_intelligence.jazzer.mutation.api.InPlaceMutator;
import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
import com.code_intelligence.jazzer.mutation.api.Serializer;
import com.code_intelligence.jazzer.mutation.api.SerializingInPlaceMutator;
import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
import com.code_intelligence.jazzer.mutation.api.ValueMutator;
import com.code_intelligence.jazzer.mutation.support.Preconditions;
Expand All @@ -57,6 +58,7 @@
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
import com.google.protobuf.Descriptors.OneofDescriptor;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
Expand All @@ -75,14 +77,14 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public final class BuilderMutatorFactory extends MutatorFactory {
private static <T extends Builder, U> InPlaceMutator<T> mutatorForField(FieldDescriptor field,
T builderInstance, Annotation[] messageFieldAnnotations, MutatorFactory factory) {
factory = withEnumValueDescriptorMutatorFactoryIfNeeded(factory, field);
AnnotatedType typeToMutate =
TypeLibrary.getTypeToMutate(field, builderInstance, messageFieldAnnotations);
private <T extends Builder, U> InPlaceMutator<T> mutatorForField(
FieldDescriptor field, Annotation[] annotations, MutatorFactory factory) {
factory = withDescriptorDependentMutatorFactoryIfNeeded(factory, field, annotations);
AnnotatedType typeToMutate = TypeLibrary.getTypeToMutate(field);
requireNonNull(typeToMutate, () -> "Java class not specified for " + field);

if (field.isMapField()) {
Expand All @@ -109,38 +111,71 @@ private static <T extends Builder, U> InPlaceMutator<T> mutatorForField(FieldDes
}
}

private static MutatorFactory withEnumValueDescriptorMutatorFactoryIfNeeded(
MutatorFactory factory, FieldDescriptor field) {
if (field.getJavaType() != JavaType.ENUM) {
return factory;
}
// Proto enum fields are special as their type (EnumValueDescriptor) does not encode their
// domain - we need the actual EnumDescriptor instance.
return new ChainedMutatorFactory(factory, new MutatorFactory() {
@Override
public Optional<SerializingMutator<?>> tryCreate(AnnotatedType type, MutatorFactory factory) {
return findFirstParentIfClass(type, EnumValueDescriptor.class).map(parent -> {
EnumDescriptor enumType = field.getEnumType();
List<EnumValueDescriptor> values = enumType.getValues();
String name = enumType.getName();
if (values.size() == 1) {
// While we generally prefer to error out instead of creating a mutator that can't
// actually mutate its domain, we can't do that for proto enum fields as the user
// creating the fuzz test may not be in a position to modify the existing proto
// definition.
return fixedValue(values.get(0));
} else {
return mutateThenMapToImmutable(mutateIndices(values.size()), values::get,
EnumValueDescriptor::getIndex, unused -> "Enum<" + name + ">");
}
});
private MutatorFactory withDescriptorDependentMutatorFactoryIfNeeded(
MutatorFactory originalFactory, FieldDescriptor field, Annotation[] annotations) {
if (field.getJavaType() == JavaType.ENUM) {
// Proto enum fields are special as their type (EnumValueDescriptor) does not encode their
// domain - we need the actual EnumDescriptor instance.
return new ChainedMutatorFactory(originalFactory, new MutatorFactory() {
@Override
public Optional<SerializingMutator<?>> tryCreate(
AnnotatedType type, MutatorFactory factory) {
return findFirstParentIfClass(type, EnumValueDescriptor.class).map(parent -> {
EnumDescriptor enumType = field.getEnumType();
List<EnumValueDescriptor> values = enumType.getValues();
String name = enumType.getName();
if (values.size() == 1) {
// While we generally prefer to error out instead of creating a mutator that can't
// actually mutate its domain, we can't do that for proto enum fields as the user
// creating the fuzz test may not be in a position to modify the existing proto
// definition.
return fixedValue(values.get(0));
} else {
return mutateThenMapToImmutable(mutateIndices(values.size()), values::get,
EnumValueDescriptor::getIndex, unused -> "Enum<" + name + ">");
}
});
}
});
} else if (field.getJavaType() == JavaType.MESSAGE) {
Descriptor messageDescriptor;
if (field.isMapField()) {
// Map fields are represented as messages, but we mutate them as actual Java Maps. In case
// the values of the proto map are themselves messages, we need to mutate their type.
FieldDescriptor valueField = field.getMessageType().getFields().get(1);
if (valueField.getJavaType() != JavaType.MESSAGE) {
return originalFactory;
}
messageDescriptor = valueField.getMessageType();
} else {
messageDescriptor = field.getMessageType();
}
});
return new ChainedMutatorFactory(originalFactory, new MutatorFactory() {
@Override
public Optional<SerializingMutator<?>> tryCreate(
AnnotatedType type, MutatorFactory factory) {
return asSubclassOrEmpty(type, Message.Builder.class).flatMap(clazz -> {
// BuilderMutatorFactory only handles subclasses of Message.Builder and requests
// Message.Builder itself for message fields, which we handle here.
if (clazz != Message.Builder.class) {
return Optional.empty();
}
// It is important that we use originalFactory here instead of factory: factory has this
// field-specific message mutator appended, but this mutator should only be used for
// this particular field and not any message subfields.
return Optional.of(makeBuilderMutator(originalFactory,
DynamicMessage.getDefaultInstance(messageDescriptor), annotations));
});
}
});
} else {
return originalFactory;
}
}

private static <T extends Builder> Stream<InPlaceMutator<T>> mutatorsForFields(
Optional<OneofDescriptor> oneofField, List<FieldDescriptor> fields, T builderInstance,
Annotation[] messageFieldAnnotations, MutatorFactory factory) {
private <T extends Builder> Stream<InPlaceMutator<T>> mutatorsForFields(
Optional<OneofDescriptor> oneofField, List<FieldDescriptor> fields, Annotation[] annotations,
MutatorFactory factory) {
if (oneofField.isPresent()) {
// oneof fields are mutated as one as mutating them independently would cause the mutator to
// erratically switch between the different states. The individual fields are kept in the
Expand All @@ -166,14 +201,12 @@ private static <T extends Builder> Stream<InPlaceMutator<T>> mutatorsForFields(
// Mutating to the unset (-1) state is handled by the individual field mutators, which
// are created nullable as oneof fields report that they track presence.
fields.stream()
.map(field
-> mutatorForField(field, builderInstance, messageFieldAnnotations, factory))
.map(field -> mutatorForField(field, annotations, factory))
.toArray(InPlaceMutator[] ::new)));
} else {
// All non-oneof fields are mutated independently, using the order in which they are declared
// in the .proto file (which may not coincide with the order by field number).
return fields.stream().map(
field -> mutatorForField(field, builderInstance, messageFieldAnnotations, factory));
return fields.stream().map(field -> mutatorForField(field, annotations, factory));
}
}

Expand Down Expand Up @@ -238,11 +271,10 @@ public B detach(Builder builder) {

private SerializingMutator<Any.Builder> mutatorForAny(
AnySource anySource, MutatorFactory factory) {
HashMap<String, Integer> typeUrlToIndex = new HashMap<>(anySource.value().length);
for (int i = 0; i < anySource.value().length; i++) {
Message defaultInstance = getDefaultInstance(anySource.value()[i]);
typeUrlToIndex.put(getTypeUrl(defaultInstance), i);
}
Map<String, Integer> typeUrlToIndex =
IntStream.range(0, anySource.value().length)
.boxed()
.collect(toMap(i -> getTypeUrl(getDefaultInstance(anySource.value()[i])), identity()));

return assemble(mutator
-> internedMutators.put(new CacheKey(Any.getDescriptor(), anySource), mutator),
Expand Down Expand Up @@ -286,6 +318,9 @@ private static String getTypeUrl(Message message) {
@Override
public Optional<SerializingMutator<?>> tryCreate(AnnotatedType type, MutatorFactory factory) {
return asSubclassOrEmpty(type, Builder.class).flatMap(builderClass -> {
// Handled by a custom mutator factory for message fields that is created in
// withDescriptorDependentMutatorFactoryIfNeeded. BuilderMutatorFactory only handles proper
// subclasses, which correspond to generated message types.
if (builderClass == Message.Builder.class) {
return Optional.empty();
}
Expand Down Expand Up @@ -342,7 +377,6 @@ private SerializingMutator<?> makeBuilderMutator(
.stream()
.flatMap(entry
-> mutatorsForFields(entry.getKey(), entry.getValue(),
defaultInstance.toBuilder(),
anySource == null ? new Annotation[0] : new Annotation[] {anySource},
factory))
.toArray(InPlaceMutator[] ::new)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,89 +17,66 @@
package com.code_intelligence.jazzer.mutation.mutator.proto;

import static com.code_intelligence.jazzer.mutation.support.Preconditions.check;
import static com.code_intelligence.jazzer.mutation.support.StreamSupport.entry;
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.asAnnotatedType;
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.notNull;
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.withExtraAnnotations;
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.withTypeArguments;
import static java.lang.String.format;
import static java.util.Collections.unmodifiableMap;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toMap;

import com.code_intelligence.jazzer.mutation.annotation.NotNull;
import com.code_intelligence.jazzer.mutation.support.TypeHolder;
import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Stream;

final class TypeLibrary {
private static final AnnotatedType RAW_LIST = new TypeHolder<@NotNull List>() {}.annotatedType();
private static final AnnotatedType RAW_MAP = new TypeHolder<@NotNull Map>() {}.annotatedType();
private static final Map<JavaType, AnnotatedType> BASE_TYPE_WITH_PRESENCE =
Stream
.of(entry(JavaType.BOOLEAN, Boolean.class), entry(JavaType.BYTE_STRING, ByteString.class),
entry(JavaType.DOUBLE, Double.class), entry(JavaType.ENUM, EnumValueDescriptor.class),
entry(JavaType.FLOAT, Float.class), entry(JavaType.INT, Integer.class),
entry(JavaType.LONG, Long.class), entry(JavaType.MESSAGE, Message.class),
entry(JavaType.STRING, String.class))
.collect(collectingAndThen(toMap(Entry::getKey, e -> asAnnotatedType(e.getValue())),
map -> unmodifiableMap(new EnumMap<>(map))));

static <T extends Builder> AnnotatedType getTypeToMutate(
FieldDescriptor field, T builder, Annotation[] messageFieldAnnotations) {
static <T extends Builder> AnnotatedType getTypeToMutate(FieldDescriptor field) {
if (field.isRequired()) {
return getBaseType(field, builder, messageFieldAnnotations);
return getBaseType(field);
} else if (field.isMapField()) {
// Map fields are represented as repeated message fields, so this check has to come before the
// one for regular repeated fields.
//
// Get a builder for the synthetic MapEntry message used to represent a single entry in the
// repeated message field representation of a map field.
Builder entryBuilder = builder.newBuilderForField(field);
FieldDescriptor keyField = field.getMessageType().getFields().get(0);
AnnotatedType keyType = getBaseType(keyField, entryBuilder, messageFieldAnnotations);
FieldDescriptor valueField = field.getMessageType().getFields().get(1);
AnnotatedType valueType = getBaseType(valueField, entryBuilder, messageFieldAnnotations);
AnnotatedType keyType = getBaseType(field.getMessageType().getFields().get(0));
AnnotatedType valueType = getBaseType(field.getMessageType().getFields().get(1));
return withTypeArguments(RAW_MAP, keyType, valueType);
} else if (field.isRepeated()) {
return withTypeArguments(RAW_LIST, getBaseType(field, builder, messageFieldAnnotations));
return withTypeArguments(RAW_LIST, getBaseType(field));
} else if (field.hasPresence()) {
return getBaseTypeWithPresence(field, builder, messageFieldAnnotations);
return BASE_TYPE_WITH_PRESENCE.get(field.getJavaType());
} else {
return getBaseType(field, builder, messageFieldAnnotations);
return getBaseType(field);
}
}

private static <T extends Builder> AnnotatedType getBaseType(
FieldDescriptor field, T builder, Annotation[] messageFieldAnnotations) {
return notNull(getBaseTypeWithPresence(field, builder, messageFieldAnnotations));
}

@SuppressWarnings("DuplicateBranchesInSwitch") /* False positives caused by TypeHolder */
private static <T extends Builder> AnnotatedType getBaseTypeWithPresence(
FieldDescriptor field, T builder, Annotation[] messageFieldAnnotations) {
switch (field.getJavaType()) {
case BOOLEAN:
return new TypeHolder<Boolean>() {}.annotatedType();
case MESSAGE:
return withExtraAnnotations(
asAnnotatedType(
builder.newBuilderForField(field).getDefaultInstanceForType().getClass()),
messageFieldAnnotations);
case INT:
return new TypeHolder<Integer>() {}.annotatedType();
case LONG:
return new TypeHolder<Long>() {}.annotatedType();
case BYTE_STRING:
return new TypeHolder<ByteString>() {}.annotatedType();
case STRING:
return new TypeHolder<String>() {}.annotatedType();
case ENUM:
return new TypeHolder<EnumValueDescriptor>() {}.annotatedType();
case FLOAT:
return new TypeHolder<Float>() {}.annotatedType();
case DOUBLE:
return new TypeHolder<Double>() {}.annotatedType();
default:
throw new IllegalStateException("Unexpected type: " + field.getType());
}
private static <T extends Builder> AnnotatedType getBaseType(FieldDescriptor field) {
return notNull(BASE_TYPE_WITH_PRESENCE.get(field.getJavaType()));
}

private TypeLibrary() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static java.util.stream.Collectors.toList;

import java.util.AbstractMap.SimpleEntry;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
Expand Down Expand Up @@ -64,4 +65,8 @@ public static <T> Optional<T[]> toArrayOrEmpty(
public static <T> Stream<T> getOrEmpty(Optional<T> optional) {
return optional.isPresent() ? Stream.of(optional.get()) : Stream.empty();
}

public static <K, V> SimpleEntry<K, V> entry(K key, V value) {
return new SimpleEntry<>(key, value);
}
}

0 comments on commit 854f59a

Please sign in to comment.