xref: /aosp_15_r20/external/jazzer-api/src/main/java/com/code_intelligence/jazzer/mutation/ArgumentsMutator.java (revision 33edd6723662ea34453766bfdca85dbfdd5342b8)
1 /*
2  * Copyright 2023 Code Intelligence GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.code_intelligence.jazzer.mutation;
18 
19 import static com.code_intelligence.jazzer.mutation.mutator.Mutators.validateAnnotationUsage;
20 import static com.code_intelligence.jazzer.mutation.support.InputStreamSupport.extendWithReadExactly;
21 import static com.code_intelligence.jazzer.mutation.support.Preconditions.require;
22 import static com.code_intelligence.jazzer.mutation.support.StreamSupport.toArrayOrEmpty;
23 import static java.lang.String.format;
24 import static java.util.Arrays.stream;
25 import static java.util.Objects.requireNonNull;
26 import static java.util.stream.Collectors.joining;
27 
28 import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
29 import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
30 import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
31 import com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators;
32 import com.code_intelligence.jazzer.mutation.combinator.ProductMutator;
33 import com.code_intelligence.jazzer.mutation.engine.SeededPseudoRandom;
34 import com.code_intelligence.jazzer.mutation.mutator.Mutators;
35 import com.code_intelligence.jazzer.mutation.support.InputStreamSupport.ReadExactlyInputStream;
36 import com.code_intelligence.jazzer.mutation.support.Preconditions;
37 import java.io.ByteArrayInputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.OutputStream;
41 import java.io.UncheckedIOException;
42 import java.lang.reflect.AnnotatedType;
43 import java.lang.reflect.InvocationTargetException;
44 import java.lang.reflect.Method;
45 import java.lang.reflect.Modifier;
46 import java.util.Optional;
47 
48 public final class ArgumentsMutator {
49   private final Object instance;
50   private final Method method;
51   private final ProductMutator productMutator;
52   private Object[] arguments;
53 
54   /**
55    * True if the arguments array has already been passed to a user-provided function or exposed
56    * via {@link #getArguments()} without going through {@link ProductMutator#detach(Object[])}.
57    * In this case the arguments may have been modified externally, which interferes with mutation,
58    * or could have been stored in static state that would be affected by future mutations.
59    * Arguments should either be detached or not be reused after being exposed, which is enforced by
60    * this variable.
61    */
62   private boolean argumentsExposed;
63 
ArgumentsMutator(Object instance, Method method, ProductMutator productMutator)64   private ArgumentsMutator(Object instance, Method method, ProductMutator productMutator) {
65     this.instance = instance;
66     this.method = method;
67     this.productMutator = productMutator;
68   }
69 
prettyPrintMethod(Method method)70   private static String prettyPrintMethod(Method method) {
71     return format("%s.%s(%s)", method.getDeclaringClass().getName(), method.getName(),
72         stream(method.getAnnotatedParameterTypes()).map(Object::toString).collect(joining(", ")));
73   }
74 
forInstanceMethodOrThrow(Object instance, Method method)75   public static ArgumentsMutator forInstanceMethodOrThrow(Object instance, Method method) {
76     return forInstanceMethod(Mutators.newFactory(), instance, method)
77         .orElseThrow(()
78                          -> new IllegalArgumentException(
79                              "Failed to construct mutator for " + prettyPrintMethod(method)));
80   }
81 
forStaticMethodOrThrow(Method method)82   public static ArgumentsMutator forStaticMethodOrThrow(Method method) {
83     return forStaticMethod(Mutators.newFactory(), method)
84         .orElseThrow(()
85                          -> new IllegalArgumentException(
86                              "Failed to construct mutator for " + prettyPrintMethod(method)));
87   }
88 
forMethod(Method method)89   public static Optional<ArgumentsMutator> forMethod(Method method) {
90     return forMethod(Mutators.newFactory(), null, method);
91   }
92 
forInstanceMethod( MutatorFactory mutatorFactory, Object instance, Method method)93   public static Optional<ArgumentsMutator> forInstanceMethod(
94       MutatorFactory mutatorFactory, Object instance, Method method) {
95     require(!isStatic(method), "method must not be static");
96     requireNonNull(instance, "instance must not be null");
97     require(method.getDeclaringClass().isInstance(instance),
98         format("instance is a %s, expected %s", instance.getClass(), method.getDeclaringClass()));
99     return forMethod(mutatorFactory, instance, method);
100   }
101 
forStaticMethod( MutatorFactory mutatorFactory, Method method)102   public static Optional<ArgumentsMutator> forStaticMethod(
103       MutatorFactory mutatorFactory, Method method) {
104     require(isStatic(method), "method must be static");
105     return forMethod(mutatorFactory, null, method);
106   }
107 
forMethod( MutatorFactory mutatorFactory, Object instance, Method method)108   public static Optional<ArgumentsMutator> forMethod(
109       MutatorFactory mutatorFactory, Object instance, Method method) {
110     require(method.getParameterCount() > 0, "Can't fuzz method without parameters: " + method);
111     for (AnnotatedType parameter : method.getAnnotatedParameterTypes()) {
112       validateAnnotationUsage(parameter);
113     }
114     return toArrayOrEmpty(
115         stream(method.getAnnotatedParameterTypes()).map(mutatorFactory::tryCreate),
116         SerializingMutator<?>[] ::new)
117         .map(MutatorCombinators::mutateProduct)
118         .map(productMutator -> ArgumentsMutator.create(instance, method, productMutator));
119   }
120 
create( Object instance, Method method, ProductMutator productMutator)121   private static ArgumentsMutator create(
122       Object instance, Method method, ProductMutator productMutator) {
123     method.setAccessible(true);
124 
125     return new ArgumentsMutator(instance, method, productMutator);
126   }
127 
isStatic(Method method)128   private static boolean isStatic(Method method) {
129     return Modifier.isStatic(method.getModifiers());
130   }
131 
132   /**
133    * @throws UncheckedIOException if the underlying InputStream throws
134    */
crossOver(InputStream data1, InputStream data2, long seed)135   public void crossOver(InputStream data1, InputStream data2, long seed) {
136     try {
137       Object[] objects1 = productMutator.readExclusive(data1);
138       Object[] objects2 = productMutator.readExclusive(data2);
139       PseudoRandom prng = new SeededPseudoRandom(seed);
140       arguments = productMutator.crossOver(objects1, objects2, prng);
141       argumentsExposed = false;
142     } catch (IOException e) {
143       throw new UncheckedIOException(e);
144     }
145   }
146 
147   /**
148    * @return if the given input stream was consumed exactly
149    * @throws UncheckedIOException if the underlying InputStream throws
150    */
read(ByteArrayInputStream data)151   public boolean read(ByteArrayInputStream data) {
152     try {
153       ReadExactlyInputStream is = extendWithReadExactly(data);
154       arguments = productMutator.readExclusive(is);
155       argumentsExposed = false;
156       return is.isConsumedExactly();
157     } catch (IOException e) {
158       throw new UncheckedIOException(e);
159     }
160   }
161 
162   /**
163    * @throws UncheckedIOException if the underlying OutputStream throws
164    */
write(OutputStream data)165   public void write(OutputStream data) {
166     failIfArgumentsExposed();
167     writeAny(data, arguments);
168   }
169 
170   /**
171    * @throws UncheckedIOException if the underlying OutputStream throws
172    */
writeAny(OutputStream data, Object[] args)173   public void writeAny(OutputStream data, Object[] args) throws UncheckedIOException {
174     try {
175       productMutator.writeExclusive(args, data);
176     } catch (IOException e) {
177       throw new UncheckedIOException(e);
178     }
179   }
180 
init(long seed)181   public void init(long seed) {
182     init(new SeededPseudoRandom(seed));
183   }
184 
init(PseudoRandom prng)185   void init(PseudoRandom prng) {
186     arguments = productMutator.init(prng);
187     argumentsExposed = false;
188   }
189 
mutate(long seed)190   public void mutate(long seed) {
191     mutate(new SeededPseudoRandom(seed));
192   }
193 
mutate(PseudoRandom prng)194   void mutate(PseudoRandom prng) {
195     failIfArgumentsExposed();
196     // TODO: Sometimes mutate the entire byte representation of the current value with libFuzzer's
197     //  dictionary and TORC mutations.
198     productMutator.mutate(arguments, prng);
199   }
200 
invoke(boolean detach)201   public void invoke(boolean detach) throws Throwable {
202     Object[] invokeArguments;
203     if (detach) {
204       invokeArguments = productMutator.detach(arguments);
205     } else {
206       invokeArguments = arguments;
207       argumentsExposed = true;
208     }
209     try {
210       method.invoke(instance, invokeArguments);
211     } catch (IllegalAccessException e) {
212       throw new IllegalStateException("method should have been made accessible", e);
213     } catch (InvocationTargetException e) {
214       throw e.getCause();
215     }
216   }
217 
getArguments()218   public Object[] getArguments() {
219     argumentsExposed = true;
220     return arguments;
221   }
222 
223   @Override
toString()224   public String toString() {
225     return "Arguments" + productMutator;
226   }
227 
failIfArgumentsExposed()228   private void failIfArgumentsExposed() {
229     Preconditions.check(!argumentsExposed,
230         "Arguments have previously been exposed to user-provided code without calling #detach and may have been modified");
231   }
232 }
233