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