mutation: Add support for map proto fields

While map fields in protos are represented as repeated message fields to
the reflection API, this "illusion" wasn't convincing enough for us to
support them automatically: There is a single class that represents all
MapEntry messages, even though there are many different message types
due to the possible key/value type combinations.

In addition, modelling a map as a list of entries means that some
mutations, e.g. duplicating a list entry, would silently not affect the
map.

Instead, we are now mapping a proto map field to an actual map to which
we apply our map mutator.
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdapters.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdapters.java
index 07190cf..693f7d2 100644
--- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdapters.java
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdapters.java
@@ -19,10 +19,14 @@
 import static java.lang.String.format;
 
 import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.MapEntry;
 import com.google.protobuf.Message.Builder;
 import java.util.AbstractList;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 final class BuilderAdapters {
   static <T extends Builder, U> List<U> makeMutableRepeatedFieldView(
@@ -91,5 +95,26 @@
     }
   }
 
+  static <T extends Builder, K, V> Map<K, V> getMapField(T builder, FieldDescriptor field) {
+    int size = builder.getRepeatedFieldCount(field);
+    HashMap<K, V> map = new HashMap<>(size);
+    for (int i = 0; i < size; i++) {
+      MapEntry<K, V> entry = (MapEntry) builder.getRepeatedField(field, i);
+      map.put(entry.getKey(), entry.getValue());
+    }
+    return map;
+  }
+
+  static <T extends Builder, K, V> void setMapField(
+      Builder builder, FieldDescriptor field, Map<K, V> map) {
+    builder.clearField(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());
+      builder.addRepeatedField(field, entryBuilder.build());
+    }
+  }
+
   private BuilderAdapters() {}
 }
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorFactory.java
index a46e8b0..1fe131f 100644
--- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorFactory.java
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorFactory.java
@@ -24,9 +24,11 @@
 import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateSumInPlace;
 import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateThenMapToImmutable;
 import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateViaView;
+import static com.code_intelligence.jazzer.mutation.mutator.proto.BuilderAdapters.getMapField;
 import static com.code_intelligence.jazzer.mutation.mutator.proto.BuilderAdapters.getPresentFieldOrNull;
 import static com.code_intelligence.jazzer.mutation.mutator.proto.BuilderAdapters.makeMutableRepeatedFieldView;
 import static com.code_intelligence.jazzer.mutation.mutator.proto.BuilderAdapters.setFieldWithPresence;
+import static com.code_intelligence.jazzer.mutation.mutator.proto.BuilderAdapters.setMapField;
 import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.cap;
 import static com.code_intelligence.jazzer.mutation.support.Preconditions.check;
 import static com.code_intelligence.jazzer.mutation.support.TypeSupport.asSubclassOrEmpty;
@@ -63,6 +65,7 @@
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Stream;
 
@@ -73,7 +76,13 @@
     AnnotatedType typeToMutate = TypeLibrary.getTypeToMutate(field, builderInstance);
     requireNonNull(typeToMutate, () -> "Java class not specified for " + field);
 
-    if (field.isRepeated()) {
+    if (field.isMapField()) {
+      ValueMutator<Map> underlyingMutator =
+          (ValueMutator<Map>) factory.createInPlaceOrThrow(typeToMutate);
+      return mutateProperty(builder
+          -> getMapField(builder, field),
+          underlyingMutator, (builder, value) -> setMapField(builder, field, value));
+    } else if (field.isRepeated()) {
       InPlaceMutator<List<U>> underlyingMutator =
           (InPlaceMutator<List<U>>) factory.createInPlaceOrThrow(typeToMutate);
       return mutateViaView(
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/TypeLibrary.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/TypeLibrary.java
index 508f6ec..633e3a4 100644
--- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/TypeLibrary.java
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/TypeLibrary.java
@@ -28,16 +28,27 @@
 import com.google.protobuf.Message.Builder;
 import java.lang.reflect.AnnotatedType;
 import java.util.List;
+import java.util.Map;
 
 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();
 
   static <T extends Builder> AnnotatedType getTypeToMutate(FieldDescriptor field, T builder) {
-    if (field.isMapField()) {
-      throw new UnsupportedOperationException("Map fields haven't been implemented yet");
-    }
     if (field.isRequired()) {
       return getBaseType(field, builder);
+    } 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);
+      FieldDescriptor valueField = field.getMessageType().getFields().get(1);
+      AnnotatedType valueType = getBaseType(valueField, entryBuilder);
+      return withTypeArguments(RAW_MAP, keyType, valueType);
     } else if (field.isRepeated()) {
       return withTypeArguments(RAW_LIST, getBaseType(field, builder));
     } else if (field.hasPresence()) {
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 1f97574..9dae0db 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
@@ -44,6 +44,8 @@
 import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldRepeated3;
 import com.code_intelligence.jazzer.protobuf.Proto3.EnumFieldRepeated3.TestEnumRepeated;
 import com.code_intelligence.jazzer.protobuf.Proto3.IntegralField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.MapField3;
+import com.code_intelligence.jazzer.protobuf.Proto3.MessageMapField3;
 import com.code_intelligence.jazzer.protobuf.Proto3.OptionalPrimitiveField3;
 import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedIntegralField3;
 import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedRecursiveMessageField3;
@@ -216,7 +218,13 @@
                 EnumFieldRepeated3.newBuilder().addSomeField(TestEnumRepeated.UNASSIGNED).build(),
                 EnumFieldRepeated3.newBuilder().addSomeField(TestEnumRepeated.VAL1).build(),
                 EnumFieldRepeated3.newBuilder().addSomeField(TestEnumRepeated.VAL2).build()),
-            manyDistinctElements()));
+            manyDistinctElements()),
+        arguments(new TypeHolder<@NotNull MapField3>() {}.annotatedType(),
+            "{Builder.Map<Integer,String>} -> Message", distinctElementsRatio(0.49),
+            manyDistinctElements()),
+        arguments(new TypeHolder<@NotNull MessageMapField3>() {}.annotatedType(),
+            "{Builder.Map<String,{Builder.Map<Integer,String>} -> Message>} -> Message",
+            manyDistinctElements(), manyDistinctElements()));
   }
 
   @SafeVarargs
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto
index a43870d..27e78a3 100644
--- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/proto3.proto
@@ -71,7 +71,6 @@
   string some_field = 1;
 }
 
-
 message EnumField3 {
   enum TestEnum {
     VAL1 = 0;
@@ -105,3 +104,11 @@
   }
   repeated TestEnumRepeated some_field = 1;
 }
+
+message MapField3 {
+  map<int32, string> some_field = 1;
+}
+
+message MessageMapField3 {
+  map<string, MapField3> some_field =1;
+}