1 // Copyright 2021 Code Intelligence GmbH 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.code_intelligence.jazzer.autofuzz; 16 17 import com.code_intelligence.jazzer.api.AutofuzzConstructionException; 18 import com.code_intelligence.jazzer.api.AutofuzzInvocationException; 19 import com.code_intelligence.jazzer.api.Consumer1; 20 import com.code_intelligence.jazzer.api.Consumer2; 21 import com.code_intelligence.jazzer.api.Consumer3; 22 import com.code_intelligence.jazzer.api.Consumer4; 23 import com.code_intelligence.jazzer.api.Consumer5; 24 import com.code_intelligence.jazzer.api.Function1; 25 import com.code_intelligence.jazzer.api.Function2; 26 import com.code_intelligence.jazzer.api.Function3; 27 import com.code_intelligence.jazzer.api.Function4; 28 import com.code_intelligence.jazzer.api.Function5; 29 import com.code_intelligence.jazzer.api.FuzzedDataProvider; 30 import com.code_intelligence.jazzer.utils.Utils; 31 import io.github.classgraph.ClassGraph; 32 import io.github.classgraph.ClassInfoList; 33 import io.github.classgraph.ScanResult; 34 import java.io.ByteArrayInputStream; 35 import java.io.InputStream; 36 import java.lang.reflect.Array; 37 import java.lang.reflect.Constructor; 38 import java.lang.reflect.Executable; 39 import java.lang.reflect.GenericArrayType; 40 import java.lang.reflect.InvocationTargetException; 41 import java.lang.reflect.Method; 42 import java.lang.reflect.Modifier; 43 import java.lang.reflect.ParameterizedType; 44 import java.lang.reflect.Type; 45 import java.lang.reflect.TypeVariable; 46 import java.lang.reflect.WildcardType; 47 import java.util.*; 48 import java.util.stream.Collectors; 49 import java.util.stream.IntStream; 50 import net.jodah.typetools.TypeResolver; 51 import net.jodah.typetools.TypeResolver.Unknown; 52 53 public class Meta { 54 static final WeakHashMap<Class<?>, List<Class<?>>> implementingClassesCache = new WeakHashMap<>(); 55 static final WeakHashMap<Class<?>, List<Class<?>>> nestedBuilderClassesCache = 56 new WeakHashMap<>(); 57 static final WeakHashMap<Class<?>, List<Method>> originalObjectCreationMethodsCache = 58 new WeakHashMap<>(); 59 static final WeakHashMap<Class<?>, List<Method>> cascadingBuilderMethodsCache = 60 new WeakHashMap<>(); 61 autofuzz(FuzzedDataProvider data, Method method)62 public static Object autofuzz(FuzzedDataProvider data, Method method) { 63 return autofuzz(data, method, null); 64 } 65 autofuzz(FuzzedDataProvider data, Method method, AutofuzzCodegenVisitor visitor)66 static Object autofuzz(FuzzedDataProvider data, Method method, AutofuzzCodegenVisitor visitor) { 67 Object result; 68 if (Modifier.isStatic(method.getModifiers())) { 69 if (visitor != null) { 70 // This group will always have two elements: The class name and the method call. 71 visitor.pushGroup( 72 String.format("%s.", method.getDeclaringClass().getCanonicalName()), "", ""); 73 } 74 try { 75 result = autofuzz(data, method, null, visitor); 76 } finally { 77 if (visitor != null) { 78 visitor.popGroup(); 79 } 80 } 81 } else { 82 if (visitor != null) { 83 // This group will always have two elements: The thisObject and the method call. 84 // Since the this object can be a complex expression, wrap it in paranthesis. 85 visitor.pushGroup("(", ").", ""); 86 } 87 Object thisObject = consume(data, method.getDeclaringClass(), visitor); 88 if (thisObject == null) { 89 throw new AutofuzzConstructionException(); 90 } 91 try { 92 result = autofuzz(data, method, thisObject, visitor); 93 } finally { 94 if (visitor != null) { 95 visitor.popGroup(); 96 } 97 } 98 } 99 return result; 100 } 101 autofuzz(FuzzedDataProvider data, Method method, Object thisObject)102 public static Object autofuzz(FuzzedDataProvider data, Method method, Object thisObject) { 103 return autofuzz(data, method, thisObject, null); 104 } 105 autofuzz( FuzzedDataProvider data, Method method, Object thisObject, AutofuzzCodegenVisitor visitor)106 static Object autofuzz( 107 FuzzedDataProvider data, Method method, Object thisObject, AutofuzzCodegenVisitor visitor) { 108 if (visitor != null) { 109 visitor.pushGroup(String.format("%s(", method.getName()), ", ", ")"); 110 } 111 Object[] arguments = consumeArguments(data, method, visitor); 112 if (visitor != null) { 113 visitor.popGroup(); 114 } 115 try { 116 return method.invoke(thisObject, arguments); 117 } catch (IllegalAccessException | IllegalArgumentException | NullPointerException e) { 118 // We should ensure that the arguments fed into the method are always valid. 119 throw new AutofuzzError(getDebugSummary(method, thisObject, arguments), e); 120 } catch (InvocationTargetException e) { 121 throw new AutofuzzInvocationException(e.getCause()); 122 } 123 } 124 autofuzz(FuzzedDataProvider data, Constructor<R> constructor)125 public static <R> R autofuzz(FuzzedDataProvider data, Constructor<R> constructor) { 126 return autofuzz(data, constructor, null); 127 } 128 autofuzz( FuzzedDataProvider data, Constructor<R> constructor, AutofuzzCodegenVisitor visitor)129 static <R> R autofuzz( 130 FuzzedDataProvider data, Constructor<R> constructor, AutofuzzCodegenVisitor visitor) { 131 if (visitor != null) { 132 // getCanonicalName is correct also for nested classes. 133 visitor.pushGroup( 134 String.format("new %s(", constructor.getDeclaringClass().getCanonicalName()), ", ", ")"); 135 } 136 Object[] arguments = consumeArguments(data, constructor, visitor); 137 if (visitor != null) { 138 visitor.popGroup(); 139 } 140 try { 141 return constructor.newInstance(arguments); 142 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) { 143 // This should never be reached as the logic in consume should prevent us from e.g. calling 144 // constructors of abstract classes or private constructors. 145 throw new AutofuzzError(getDebugSummary(constructor, null, arguments), e); 146 } catch (InvocationTargetException e) { 147 throw new AutofuzzInvocationException(e.getCause()); 148 } 149 } 150 151 @SuppressWarnings("unchecked") autofuzz(FuzzedDataProvider data, Consumer1<T1> func)152 public static <T1> void autofuzz(FuzzedDataProvider data, Consumer1<T1> func) { 153 Class<?>[] types = TypeResolver.resolveRawArguments(Consumer1.class, func.getClass()); 154 func.accept((T1) consumeChecked(data, types, 0)); 155 } 156 157 @SuppressWarnings("unchecked") autofuzz(FuzzedDataProvider data, Consumer2<T1, T2> func)158 public static <T1, T2> void autofuzz(FuzzedDataProvider data, Consumer2<T1, T2> func) { 159 Class<?>[] types = TypeResolver.resolveRawArguments(Consumer2.class, func.getClass()); 160 func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1)); 161 } 162 163 @SuppressWarnings("unchecked") autofuzz(FuzzedDataProvider data, Consumer3<T1, T2, T3> func)164 public static <T1, T2, T3> void autofuzz(FuzzedDataProvider data, Consumer3<T1, T2, T3> func) { 165 Class<?>[] types = TypeResolver.resolveRawArguments(Consumer3.class, func.getClass()); 166 func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), 167 (T3) consumeChecked(data, types, 2)); 168 } 169 170 @SuppressWarnings("unchecked") autofuzz( FuzzedDataProvider data, Consumer4<T1, T2, T3, T4> func)171 public static <T1, T2, T3, T4> void autofuzz( 172 FuzzedDataProvider data, Consumer4<T1, T2, T3, T4> func) { 173 Class<?>[] types = TypeResolver.resolveRawArguments(Consumer4.class, func.getClass()); 174 func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), 175 (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3)); 176 } 177 178 @SuppressWarnings("unchecked") autofuzz( FuzzedDataProvider data, Consumer5<T1, T2, T3, T4, T5> func)179 public static <T1, T2, T3, T4, T5> void autofuzz( 180 FuzzedDataProvider data, Consumer5<T1, T2, T3, T4, T5> func) { 181 Class<?>[] types = TypeResolver.resolveRawArguments(Consumer5.class, func.getClass()); 182 func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), 183 (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3), 184 (T5) consumeChecked(data, types, 4)); 185 } 186 187 @SuppressWarnings("unchecked") autofuzz(FuzzedDataProvider data, Function1<T1, R> func)188 public static <T1, R> R autofuzz(FuzzedDataProvider data, Function1<T1, R> func) { 189 Class<?>[] types = TypeResolver.resolveRawArguments(Function1.class, func.getClass()); 190 return func.apply((T1) consumeChecked(data, types, 0)); 191 } 192 193 @SuppressWarnings("unchecked") autofuzz(FuzzedDataProvider data, Function2<T1, T2, R> func)194 public static <T1, T2, R> R autofuzz(FuzzedDataProvider data, Function2<T1, T2, R> func) { 195 Class<?>[] types = TypeResolver.resolveRawArguments(Function2.class, func.getClass()); 196 return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1)); 197 } 198 199 @SuppressWarnings("unchecked") autofuzz(FuzzedDataProvider data, Function3<T1, T2, T3, R> func)200 public static <T1, T2, T3, R> R autofuzz(FuzzedDataProvider data, Function3<T1, T2, T3, R> func) { 201 Class<?>[] types = TypeResolver.resolveRawArguments(Function3.class, func.getClass()); 202 return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), 203 (T3) consumeChecked(data, types, 2)); 204 } 205 206 @SuppressWarnings("unchecked") autofuzz( FuzzedDataProvider data, Function4<T1, T2, T3, T4, R> func)207 public static <T1, T2, T3, T4, R> R autofuzz( 208 FuzzedDataProvider data, Function4<T1, T2, T3, T4, R> func) { 209 Class<?>[] types = TypeResolver.resolveRawArguments(Function4.class, func.getClass()); 210 return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), 211 (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3)); 212 } 213 214 @SuppressWarnings("unchecked") autofuzz( FuzzedDataProvider data, Function5<T1, T2, T3, T4, T5, R> func)215 public static <T1, T2, T3, T4, T5, R> R autofuzz( 216 FuzzedDataProvider data, Function5<T1, T2, T3, T4, T5, R> func) { 217 Class<?>[] types = TypeResolver.resolveRawArguments(Function5.class, func.getClass()); 218 return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), 219 (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3), 220 (T5) consumeChecked(data, types, 4)); 221 } 222 consume(FuzzedDataProvider data, Class<?> type)223 public static Object consume(FuzzedDataProvider data, Class<?> type) { 224 return consume(data, type, null); 225 } 226 227 // Invariant: The Java source code representation of the returned object visited by visitor must 228 // represent an object of the same type as genericType. For example, a null value returned for 229 // the genericType Class<java.lang.String> should lead to the generated code 230 // "(java.lang.String) null", not just "null". This makes it possible to safely use consume in 231 // recursive argument constructions. consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor visitor)232 static Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor visitor) { 233 Class<?> type = getRawType(genericType); 234 if (type == byte.class || type == Byte.class) { 235 byte result = data.consumeByte(); 236 if (visitor != null) 237 visitor.pushElement(String.format("(byte) %s", result)); 238 return result; 239 } else if (type == short.class || type == Short.class) { 240 short result = data.consumeShort(); 241 if (visitor != null) 242 visitor.pushElement(String.format("(short) %s", result)); 243 return result; 244 } else if (type == int.class || type == Integer.class) { 245 int result = data.consumeInt(); 246 if (visitor != null) 247 visitor.pushElement(Integer.toString(result)); 248 return result; 249 } else if (type == long.class || type == Long.class) { 250 long result = data.consumeLong(); 251 if (visitor != null) 252 visitor.pushElement(String.format("%sL", result)); 253 return result; 254 } else if (type == float.class || type == Float.class) { 255 float result = data.consumeFloat(); 256 if (visitor != null) 257 visitor.pushElement(String.format("%sF", result)); 258 return result; 259 } else if (type == double.class || type == Double.class) { 260 double result = data.consumeDouble(); 261 if (visitor != null) 262 visitor.pushElement(Double.toString(result)); 263 return result; 264 } else if (type == boolean.class || type == Boolean.class) { 265 boolean result = data.consumeBoolean(); 266 if (visitor != null) 267 visitor.pushElement(Boolean.toString(result)); 268 return result; 269 } else if (type == char.class || type == Character.class) { 270 char result = data.consumeChar(); 271 if (visitor != null) 272 visitor.addCharLiteral(result); 273 return result; 274 } 275 // Sometimes, but rarely return null for non-primitive and non-boxed types. 276 // TODO: We might want to return null for boxed types sometimes, but this is complicated by the 277 // fact that TypeUtils can't distinguish between a primitive type and its wrapper and may 278 // thus easily cause false-positive NullPointerExceptions. 279 if (!type.isPrimitive() && data.consumeByte() == 0) { 280 if (visitor != null) { 281 if (type == Object.class) { 282 visitor.pushElement("null"); 283 } else { 284 visitor.pushElement(String.format("(%s) null", type.getCanonicalName())); 285 } 286 } 287 return null; 288 } 289 if (type == String.class || type == CharSequence.class) { 290 String result = data.consumeString(consumeArrayLength(data, 1)); 291 if (visitor != null) 292 visitor.addStringLiteral(result); 293 return result; 294 } else if (type.isArray()) { 295 if (type == byte[].class) { 296 byte[] result = data.consumeBytes(consumeArrayLength(data, Byte.BYTES)); 297 if (visitor != null) { 298 visitor.pushElement(IntStream.range(0, result.length) 299 .mapToObj(i -> "(byte) " + result[i]) 300 .collect(Collectors.joining(", ", "new byte[]{", "}"))); 301 } 302 return result; 303 } else if (type == int[].class) { 304 int[] result = data.consumeInts(consumeArrayLength(data, Integer.BYTES)); 305 if (visitor != null) { 306 visitor.pushElement(Arrays.stream(result) 307 .mapToObj(String::valueOf) 308 .collect(Collectors.joining(", ", "new int[]{", "}"))); 309 } 310 return result; 311 } else if (type == short[].class) { 312 short[] result = data.consumeShorts(consumeArrayLength(data, Short.BYTES)); 313 if (visitor != null) { 314 visitor.pushElement(IntStream.range(0, result.length) 315 .mapToObj(i -> "(short) " + result[i]) 316 .collect(Collectors.joining(", ", "new short[]{", "}"))); 317 } 318 return result; 319 } else if (type == long[].class) { 320 long[] result = data.consumeLongs(consumeArrayLength(data, Long.BYTES)); 321 if (visitor != null) { 322 visitor.pushElement(Arrays.stream(result) 323 .mapToObj(e -> e + "L") 324 .collect(Collectors.joining(", ", "new long[]{", "}"))); 325 } 326 return result; 327 } else if (type == boolean[].class) { 328 boolean[] result = data.consumeBooleans(consumeArrayLength(data, 1)); 329 if (visitor != null) { 330 visitor.pushElement( 331 Arrays.toString(result).replace(']', '}').replace("[", "new boolean[]{")); 332 } 333 return result; 334 } else { 335 if (visitor != null) { 336 visitor.pushGroup( 337 String.format("new %s[]{", type.getComponentType().getName()), ", ", "}"); 338 } 339 int remainingBytesBeforeFirstElementCreation = data.remainingBytes(); 340 Object firstElement = consume(data, type.getComponentType(), visitor); 341 int remainingBytesAfterFirstElementCreation = data.remainingBytes(); 342 int sizeOfElementEstimate = 343 remainingBytesBeforeFirstElementCreation - remainingBytesAfterFirstElementCreation; 344 Object array = Array.newInstance( 345 type.getComponentType(), consumeArrayLength(data, sizeOfElementEstimate)); 346 for (int i = 0; i < Array.getLength(array); i++) { 347 if (i == 0) { 348 Array.set(array, i, firstElement); 349 } else { 350 Array.set(array, i, consume(data, type.getComponentType(), visitor)); 351 } 352 } 353 if (visitor != null) { 354 if (Array.getLength(array) == 0) { 355 // We implicitly pushed the first element with the call to consume above, but it is not 356 // part of the array. 357 visitor.popElement(); 358 } 359 visitor.popGroup(); 360 } 361 return array; 362 } 363 } else if (type == ByteArrayInputStream.class || type == InputStream.class) { 364 byte[] array = data.consumeBytes(consumeArrayLength(data, Byte.BYTES)); 365 if (visitor != null) { 366 visitor.pushElement(IntStream.range(0, array.length) 367 .mapToObj(i -> "(byte) " + array[i]) 368 .collect(Collectors.joining( 369 ", ", "new java.io.ByteArrayInputStream(new byte[]{", "})"))); 370 } 371 return new ByteArrayInputStream(array); 372 } else if (type == Map.class) { 373 ParameterizedType mapType = (ParameterizedType) genericType; 374 if (mapType.getActualTypeArguments().length != 2) { 375 throw new AutofuzzError( 376 "Expected Map generic type to have two type parameters: " + mapType); 377 } 378 Type keyType = mapType.getActualTypeArguments()[0]; 379 Type valueType = mapType.getActualTypeArguments()[1]; 380 if (visitor != null) { 381 // Do not use Collectors.toMap() since it cannot handle null values. 382 // Also annotate the type of the entry stream since it might be empty, in which case type 383 // inference on the accumulator could fail. 384 visitor.pushGroup( 385 String.format("java.util.stream.Stream.<java.util.AbstractMap.SimpleEntry<%s, %s>>of(", 386 keyType.getTypeName(), valueType.getTypeName()), 387 ", ", 388 ").collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll)"); 389 } 390 int remainingBytesBeforeFirstEntryCreation = data.remainingBytes(); 391 if (visitor != null) { 392 visitor.pushGroup("new java.util.AbstractMap.SimpleEntry<>(", ", ", ")"); 393 } 394 Object firstKey = consume(data, keyType, visitor); 395 Object firstValue = consume(data, valueType, visitor); 396 if (visitor != null) { 397 visitor.popGroup(); 398 } 399 int remainingBytesAfterFirstEntryCreation = data.remainingBytes(); 400 int sizeOfElementEstimate = 401 remainingBytesBeforeFirstEntryCreation - remainingBytesAfterFirstEntryCreation; 402 int mapSize = consumeArrayLength(data, sizeOfElementEstimate); 403 Map<Object, Object> map = new HashMap<>(mapSize); 404 for (int i = 0; i < mapSize; i++) { 405 if (i == 0) { 406 map.put(firstKey, firstValue); 407 } else { 408 if (visitor != null) { 409 visitor.pushGroup("new java.util.AbstractMap.SimpleEntry<>(", ", ", ")"); 410 } 411 map.put(consume(data, keyType, visitor), consume(data, valueType, visitor)); 412 if (visitor != null) { 413 visitor.popGroup(); 414 } 415 } 416 } 417 if (visitor != null) { 418 if (mapSize == 0) { 419 // We implicitly pushed the first entry with the call to consume above, but it is not 420 // part of the array. 421 visitor.popElement(); 422 } 423 visitor.popGroup(); 424 } 425 return map; 426 } else if (type.isEnum()) { 427 Enum<?> enumValue = (Enum<?>) data.pickValue(type.getEnumConstants()); 428 if (visitor != null) { 429 visitor.pushElement(String.format("%s.%s", type.getName(), enumValue.name())); 430 } 431 return enumValue; 432 } else if (type == Class.class) { 433 if (visitor != null) 434 visitor.pushElement(String.format("%s.class", YourAverageJavaClass.class.getName())); 435 return YourAverageJavaClass.class; 436 } else if (type == Method.class) { 437 if (visitor != null) { 438 throw new AutofuzzError("codegen has not been implemented for Method.class"); 439 } 440 return data.pickValue(sortExecutables(YourAverageJavaClass.class.getMethods())); 441 } else if (type == Constructor.class) { 442 if (visitor != null) { 443 throw new AutofuzzError("codegen has not been implemented for Constructor.class"); 444 } 445 return data.pickValue(sortExecutables(YourAverageJavaClass.class.getConstructors())); 446 } else if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { 447 List<Class<?>> implementingClasses = implementingClassesCache.get(type); 448 if (implementingClasses == null) { 449 ClassGraph classGraph = 450 new ClassGraph().enableClassInfo().enableInterClassDependencies().rejectPackages( 451 "jaz.*"); 452 if (!isTest()) { 453 classGraph.rejectPackages("com.code_intelligence.jazzer.*"); 454 } 455 try (ScanResult result = classGraph.scan()) { 456 ClassInfoList children = 457 type.isInterface() ? result.getClassesImplementing(type) : result.getSubclasses(type); 458 implementingClasses = 459 children.getStandardClasses().filter(cls -> !cls.isAbstract()).loadClasses(); 460 implementingClassesCache.put(type, implementingClasses); 461 } 462 } 463 if (implementingClasses.isEmpty()) { 464 if (isDebug()) { 465 throw new AutofuzzConstructionException(String.format( 466 "Could not find classes implementing %s on the classpath", type.getName())); 467 } else { 468 throw new AutofuzzConstructionException(); 469 } 470 } 471 if (visitor != null) { 472 // This group will always have a single element: The instance of the implementing class. 473 visitor.pushGroup(String.format("(%s) ", type.getName()), "", ""); 474 } 475 Object result = consume(data, data.pickValue(implementingClasses), visitor); 476 if (visitor != null) { 477 visitor.popGroup(); 478 } 479 return result; 480 } else if (type.getConstructors().length > 0) { 481 Constructor<?> constructor = data.pickValue(sortExecutables(type.getConstructors())); 482 boolean applySetters = constructor.getParameterCount() == 0; 483 if (visitor != null && applySetters) { 484 // Embed the instance creation and setters into an immediately invoked lambda expression to 485 // turn them into an expression. 486 String uniqueVariableName = visitor.uniqueVariableName(); 487 visitor.pushGroup(String.format("((java.util.function.Supplier<%1$s>) (() -> {%1$s %2$s = ", 488 type.getCanonicalName(), uniqueVariableName), 489 String.format("; %s.", uniqueVariableName), 490 String.format("; return %s;})).get()", uniqueVariableName)); 491 } 492 Object obj = autofuzz(data, constructor, visitor); 493 if (applySetters) { 494 List<Method> potentialSetters = getPotentialSetters(type); 495 if (!potentialSetters.isEmpty()) { 496 List<Method> pickedSetters = 497 data.pickValues(potentialSetters, data.consumeInt(0, potentialSetters.size())); 498 for (Method setter : pickedSetters) { 499 autofuzz(data, setter, obj, visitor); 500 } 501 } 502 if (visitor != null) { 503 visitor.popGroup(); 504 } 505 } 506 return obj; 507 } 508 // We are out of more or less canonical ways to construct an instance of this class and have to 509 // resort to more heuristic approaches. 510 511 // First, try to find nested classes with names ending in Builder and call a subset of their 512 // chaining methods. 513 List<Class<?>> nestedBuilderClasses = getNestedBuilderClasses(type); 514 if (!nestedBuilderClasses.isEmpty()) { 515 Class<?> pickedBuilder = data.pickValue(nestedBuilderClasses); 516 List<Method> cascadingBuilderMethods = getCascadingBuilderMethods(pickedBuilder); 517 List<Method> originalObjectCreationMethods = getOriginalObjectCreationMethods(pickedBuilder); 518 519 int pickedMethodsNumber = data.consumeInt(0, cascadingBuilderMethods.size()); 520 List<Method> pickedMethods = data.pickValues(cascadingBuilderMethods, pickedMethodsNumber); 521 Method builderMethod = data.pickValue(originalObjectCreationMethods); 522 523 if (visitor != null) { 524 // Group for the chain of builder methods. 525 visitor.pushGroup("", ".", ""); 526 } 527 Object builderObj = 528 autofuzz(data, data.pickValue(sortExecutables(pickedBuilder.getConstructors())), visitor); 529 for (Method method : pickedMethods) { 530 builderObj = autofuzz(data, method, builderObj, visitor); 531 } 532 533 try { 534 Object obj = autofuzz(data, builderMethod, builderObj, visitor); 535 if (visitor != null) { 536 visitor.popGroup(); 537 } 538 return obj; 539 } catch (Exception e) { 540 throw new AutofuzzConstructionException(e); 541 } 542 } 543 544 // We ran out of ways to construct an instance of the requested type. If in debug mode, report 545 // more detailed information. 546 if (!isDebug()) { 547 throw new AutofuzzConstructionException(); 548 } else { 549 String summary = String.format( 550 "Failed to generate instance of %s:%nAccessible constructors: %s%nNested subclasses: %s%n", 551 type.getName(), 552 Arrays.stream(type.getConstructors()) 553 .map(Utils::getReadableDescriptor) 554 .collect(Collectors.joining(", ")), 555 Arrays.stream(type.getClasses()).map(Class::getName).collect(Collectors.joining(", "))); 556 throw new AutofuzzConstructionException(summary); 557 } 558 } 559 rescanClasspath()560 static void rescanClasspath() { 561 implementingClassesCache.clear(); 562 } 563 isTest()564 static boolean isTest() { 565 String value = System.getenv("JAZZER_AUTOFUZZ_TESTING"); 566 return value != null && !value.isEmpty(); 567 } 568 isDebug()569 static boolean isDebug() { 570 String value = System.getenv("JAZZER_AUTOFUZZ_DEBUG"); 571 return value != null && !value.isEmpty(); 572 } 573 consumeArrayLength(FuzzedDataProvider data, int sizeOfElement)574 private static int consumeArrayLength(FuzzedDataProvider data, int sizeOfElement) { 575 // Spend at most half of the fuzzer input bytes so that the remaining arguments that require 576 // construction still have non-trivial data to work with. 577 int bytesToSpend = data.remainingBytes() / 2; 578 return bytesToSpend / Math.max(sizeOfElement, 1); 579 } 580 getDebugSummary( Executable executable, Object thisObject, Object[] arguments)581 private static String getDebugSummary( 582 Executable executable, Object thisObject, Object[] arguments) { 583 return String.format("%nMethod: %s::%s%s%nthis: %s%nArguments: %s", 584 executable.getDeclaringClass().getName(), executable.getName(), 585 Utils.getReadableDescriptor(executable), thisObject, 586 Arrays.stream(arguments) 587 .map(arg -> arg == null ? "null" : arg.toString()) 588 .collect(Collectors.joining(", "))); 589 } 590 sortExecutables(T[] executables)591 private static <T extends Executable> List<T> sortExecutables(T[] executables) { 592 List<T> list = Arrays.asList(executables); 593 sortExecutables(list); 594 return list; 595 } 596 sortExecutables(List<? extends Executable> executables)597 private static void sortExecutables(List<? extends Executable> executables) { 598 executables.sort(Comparator.comparing(Executable::getName).thenComparing(Utils::getDescriptor)); 599 } 600 sortClasses(List<? extends Class<?>> classes)601 private static void sortClasses(List<? extends Class<?>> classes) { 602 classes.sort(Comparator.comparing(Class::getName)); 603 } 604 getNestedBuilderClasses(Class<?> type)605 private static List<Class<?>> getNestedBuilderClasses(Class<?> type) { 606 List<Class<?>> nestedBuilderClasses = nestedBuilderClassesCache.get(type); 607 if (nestedBuilderClasses == null) { 608 nestedBuilderClasses = Arrays.stream(type.getClasses()) 609 .filter(cls -> cls.getName().endsWith("Builder")) 610 .filter(cls -> !getOriginalObjectCreationMethods(cls).isEmpty()) 611 .collect(Collectors.toList()); 612 sortClasses(nestedBuilderClasses); 613 nestedBuilderClassesCache.put(type, nestedBuilderClasses); 614 } 615 return nestedBuilderClasses; 616 } 617 getOriginalObjectCreationMethods(Class<?> builder)618 private static List<Method> getOriginalObjectCreationMethods(Class<?> builder) { 619 List<Method> originalObjectCreationMethods = originalObjectCreationMethodsCache.get(builder); 620 if (originalObjectCreationMethods == null) { 621 originalObjectCreationMethods = 622 Arrays.stream(builder.getMethods()) 623 .filter(m -> m.getReturnType() == builder.getEnclosingClass()) 624 .collect(Collectors.toList()); 625 sortExecutables(originalObjectCreationMethods); 626 originalObjectCreationMethodsCache.put(builder, originalObjectCreationMethods); 627 } 628 return originalObjectCreationMethods; 629 } 630 getCascadingBuilderMethods(Class<?> builder)631 private static List<Method> getCascadingBuilderMethods(Class<?> builder) { 632 List<Method> cascadingBuilderMethods = cascadingBuilderMethodsCache.get(builder); 633 if (cascadingBuilderMethods == null) { 634 cascadingBuilderMethods = Arrays.stream(builder.getMethods()) 635 .filter(m -> m.getReturnType() == builder) 636 .collect(Collectors.toList()); 637 sortExecutables(cascadingBuilderMethods); 638 cascadingBuilderMethodsCache.put(builder, cascadingBuilderMethods); 639 } 640 return cascadingBuilderMethods; 641 } 642 getPotentialSetters(Class<?> type)643 private static List<Method> getPotentialSetters(Class<?> type) { 644 List<Method> potentialSetters = new ArrayList<>(); 645 Method[] methods = type.getMethods(); 646 for (Method method : methods) { 647 if (void.class.equals(method.getReturnType()) && method.getParameterCount() == 1 648 && method.getName().startsWith("set")) { 649 potentialSetters.add(method); 650 } 651 } 652 sortExecutables(potentialSetters); 653 return potentialSetters; 654 } 655 consumeArguments( FuzzedDataProvider data, Executable executable, AutofuzzCodegenVisitor visitor)656 private static Object[] consumeArguments( 657 FuzzedDataProvider data, Executable executable, AutofuzzCodegenVisitor visitor) { 658 Object[] result; 659 try { 660 result = Arrays.stream(executable.getGenericParameterTypes()) 661 .map((type) -> consume(data, type, visitor)) 662 .toArray(); 663 return result; 664 } catch (AutofuzzConstructionException e) { 665 // Do not nest AutofuzzConstructionExceptions. 666 throw e; 667 } catch (AutofuzzInvocationException e) { 668 // If an invocation fails while creating the arguments for another invocation, the exception 669 // should not be reported, so we rewrap it. 670 throw new AutofuzzConstructionException(e.getCause()); 671 } catch (Throwable t) { 672 throw new AutofuzzConstructionException(t); 673 } 674 } 675 consumeChecked(FuzzedDataProvider data, Class<?>[] types, int i)676 private static Object consumeChecked(FuzzedDataProvider data, Class<?>[] types, int i) { 677 if (types[i] == Unknown.class) { 678 throw new AutofuzzError("Failed to determine type of argument " + (i + 1)); 679 } 680 Object result; 681 try { 682 result = consume(data, types[i]); 683 } catch (AutofuzzConstructionException e) { 684 // Do not nest AutofuzzConstructionExceptions. 685 throw e; 686 } catch (AutofuzzInvocationException e) { 687 // If an invocation fails while creating the arguments for another invocation, the exception 688 // should not be reported, so we rewrap it. 689 throw new AutofuzzConstructionException(e.getCause()); 690 } catch (Throwable t) { 691 throw new AutofuzzConstructionException(t); 692 } 693 if (result != null && !types[i].isAssignableFrom(result.getClass())) { 694 throw new AutofuzzError("consume returned " + result.getClass() + ", but need " + types[i]); 695 } 696 return result; 697 } 698 getRawType(Type genericType)699 private static Class<?> getRawType(Type genericType) { 700 if (genericType instanceof Class<?>) { 701 return (Class<?>) genericType; 702 } else if (genericType instanceof ParameterizedType) { 703 return getRawType(((ParameterizedType) genericType).getRawType()); 704 } else if (genericType instanceof WildcardType) { 705 // TODO: Improve this. 706 return Object.class; 707 } else if (genericType instanceof TypeVariable<?>) { 708 throw new AutofuzzError("Did not expect genericType to be a TypeVariable: " + genericType); 709 } else if (genericType instanceof GenericArrayType) { 710 // TODO: Improve this; 711 return Object[].class; 712 } else { 713 throw new AutofuzzError("Got unexpected class implementing Type: " + genericType); 714 } 715 } 716 } 717