blob: 1fe131fee1b42fc3f372027a28d9ba578ffb9708 [file] [log] [blame]
/*
* Copyright 2023 Code Intelligence GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.code_intelligence.jazzer.mutation.mutator.proto;
import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.assemble;
import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.combine;
import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.fixedValue;
import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateIndices;
import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateProperty;
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;
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.findFirstParentIfClass;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
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.SerializingMutator;
import com.code_intelligence.jazzer.mutation.api.ValueMutator;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
import com.google.protobuf.Descriptors.OneofDescriptor;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
import com.google.protobuf.UnknownFieldSet;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
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;
public final class BuilderMutatorFactory extends MutatorFactory {
private static <T extends Builder, U> InPlaceMutator<T> mutatorForField(
FieldDescriptor field, T builderInstance, MutatorFactory factory) {
factory = withEnumValueDescriptorMutatorFactoryIfNeeded(factory, field);
AnnotatedType typeToMutate = TypeLibrary.getTypeToMutate(field, builderInstance);
requireNonNull(typeToMutate, () -> "Java class not specified for " + field);
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(
builder -> makeMutableRepeatedFieldView(builder, field), underlyingMutator);
} else if (field.hasPresence()) {
ValueMutator<U> underlyingMutator = (ValueMutator<U>) factory.createOrThrow(typeToMutate);
return mutateProperty(builder
-> getPresentFieldOrNull(builder, field),
underlyingMutator, (builder, value) -> setFieldWithPresence(builder, field, value));
} else {
ValueMutator<U> underlyingMutator = (ValueMutator<U>) factory.createOrThrow(typeToMutate);
return mutateProperty(builder
-> (U) builder.getField(field),
underlyingMutator, (builder, value) -> builder.setField(field, value));
}
}
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 static <T extends Builder> Stream<InPlaceMutator<T>> mutatorsForFields(
Optional<OneofDescriptor> oneofField, List<FieldDescriptor> fields, T builderInstance,
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
// order in which they are defined in the .proto file.
return Stream.of(mutateSumInPlace(
(T builder)
-> {
FieldDescriptor setField = builder.getOneofFieldDescriptor(oneofField.get());
if (setField == null) {
return -1;
} else {
// The index of the field within the oneof is 1-based otherwise, so we need to
// subtract 1 to fulfill the contract of mutateSumInPlace.
return setField.getIndex() - 1;
}
},
// 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, 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, factory));
}
}
private static <T extends Builder> Message getDefaultInstance(Class<T> builderClass) {
Class<?> messageClass = builderClass.getEnclosingClass();
Method getDefaultInstance;
try {
getDefaultInstance = messageClass.getMethod("getDefaultInstance");
check(Modifier.isStatic(getDefaultInstance.getModifiers()));
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
format("Message class for builder type %s does not have a getDefaultInstance method",
builderClass),
e);
}
try {
return (Message) getDefaultInstance.invoke(null);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(
format(getDefaultInstance + " isn't accessible or threw an exception"), e);
}
}
private static Serializer<Builder> makeBuilderSerializer(Message defaultInstance) {
return new Serializer<Builder>() {
@Override
public Builder read(DataInputStream in) throws IOException {
int length = Math.max(in.readInt(), 0);
return parseLeniently(cap(in, length));
}
@Override
public Builder readExclusive(InputStream in) throws IOException {
return parseLeniently(in);
}
private Builder parseLeniently(InputStream in) throws IOException {
Builder builder = defaultInstance.toBuilder();
try {
builder.mergeFrom(in);
} catch (InvalidProtocolBufferException ignored) {
// builder has been partially modified with what could be decoded before the parser error.
}
// We never want the fuzz test to see unknown fields and our mutations should never produce
// them.
builder.setUnknownFields(UnknownFieldSet.getDefaultInstance());
return builder;
}
@Override
public void write(Builder builder, DataOutputStream out) throws IOException {
Message message = builder.build();
out.writeInt(message.getSerializedSize());
message.writeTo(out);
}
@Override
public void writeExclusive(Builder builder, OutputStream out) throws IOException {
builder.build().writeTo(out);
}
@Override
public Builder detach(Builder builder) {
return builder.build().toBuilder();
}
};
}
/*
* Ensures that only a single instance is created per builder class and shared among all mutators
* that need it. This ensures that arbitrarily nested recursive structures such as a Protobuf
* message type that contains itself as a message field are representable as fixed-size mutator
* structures.
*
* Note: The resulting mutator structures may no longer form a tree: If A is a protobuf message
* type with a message field B and B in turn has a message field of type A, then the mutators for
* A and B will reference each other, forming a cycle.
*/
private final HashMap<Class<? extends Builder>, SerializingMutator<Builder>> internedMutators =
new HashMap<>();
@Override
public Optional<SerializingMutator<?>> tryCreate(AnnotatedType type, MutatorFactory factory) {
return asSubclassOrEmpty(type, Builder.class).map(builderClass -> {
if (internedMutators.containsKey(builderClass)) {
return internedMutators.get(builderClass);
}
Message defaultInstance = getDefaultInstance(builderClass);
// assemble inserts the instance of the newly created builder mutator into the
// internedMutators map *before* recursively creating the mutators for its fields, which
// ensures that the recursion is finite (bounded by the total number of distinct message types
// that transitively occur as field types on the current message type).
return assemble(mutator
-> internedMutators.put(builderClass, mutator),
defaultInstance::toBuilder, makeBuilderSerializer(defaultInstance),
()
-> combine(
defaultInstance.getDescriptorForType()
.getFields()
.stream()
// Keep oneofs sorted by the first appearance of their fields in the
// .proto file.
.collect(groupingBy(
// groupingBy does not support null keys. We use getRealContainingOneof()
// instead of getContainingOneof() as the latter also reports oneofs for
// proto3 optional fields, which we handle separately.
fieldDescriptor
-> Optional.ofNullable(fieldDescriptor.getRealContainingOneof()),
LinkedHashMap::new, toList()))
.entrySet()
.stream()
.flatMap(entry
-> mutatorsForFields(entry.getKey(), entry.getValue(),
defaultInstance.toBuilder(), factory))
.toArray(InPlaceMutator[] ::new)));
});
}
}