diff options
author | Fabian Meumertzheim <fabian@meumertzhe.im> | 2021-10-29 10:14:06 +0200 |
---|---|---|
committer | Fabian Meumertzheim <fabian@meumertzhe.im> | 2021-11-18 11:13:53 +0100 |
commit | 70e9992f37217426952ff48c952bc95e8fc56e34 (patch) | |
tree | 5f1837a11559255074814cb4fd8c5a6f36f3f563 /agent/src | |
parent | 3041449418bcbcc03362ef91dfeb0d5febaf134c (diff) | |
download | jazzer-api-70e9992f37217426952ff48c952bc95e8fc56e34.tar.gz |
Implement code generation for consume and autofuzz
Method/Constructor are not yet implemented.
Diffstat (limited to 'agent/src')
9 files changed, 581 insertions, 126 deletions
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java new file mode 100644 index 00000000..2fbed971 --- /dev/null +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java @@ -0,0 +1,117 @@ +// Copyright 2021 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.autofuzz; + +import java.util.Stack; +import java.util.stream.Collectors; + +public class AutofuzzCodegenVisitor { + private final Stack<Group> groups = new Stack<>(); + private int variableCounter = 0; + + AutofuzzCodegenVisitor() { + init(); + } + + private void init() { + pushGroup("", "", ""); + } + + public void pushGroup(String prefix, String delimiter, String suffix) { + groups.push(new Group(prefix, delimiter, suffix)); + } + + public void pushElement(String element) { + groups.peek().push(element); + } + + public void popElement() { + groups.peek().pop(); + } + + public void popGroup() { + if (groups.size() == 1) { + throw new AutofuzzError( + "popGroup must be called exactly once for every pushGroup: " + toDebugString()); + } + pushElement(groups.pop().toString()); + } + + public String generate() { + if (groups.size() != 1) { + throw new AutofuzzError( + "popGroup must be called exactly once for every pushGroup: " + toDebugString()); + } + return groups.pop().toString(); + } + + public void addCharLiteral(char c) { + pushElement("'" + escapeForLiteral(Character.toString(c)) + "'"); + } + + public void addStringLiteral(String string) { + pushElement('"' + escapeForLiteral(string) + '"'); + } + + public String uniqueVariableName() { + return String.format("autofuzzVariable%s", variableCounter++); + } + + private String escapeForLiteral(String string) { + // The list of escape sequences is taken from: + // https://docs.oracle.com/javase/tutorial/java/data/characters.html + return string.replace("\t", "\\t") + .replace("\b", "\\b") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\f", "\\f") + .replace("\f", "\\f") + .replace("\"", "\\\"") + .replace("'", "\\'") + .replace("\\", "\\\\"); + } + + private String toDebugString() { + return groups.stream() + .map(group -> group.elements.stream().collect(Collectors.joining(", ", "[", "]"))) + .collect(Collectors.joining(", ", "[", "]")); + } + + private static class Group { + private final String prefix; + private final String delimiter; + private final String suffix; + private final Stack<String> elements = new Stack<>(); + + Group(String prefix, String delimiter, String suffix) { + this.prefix = prefix; + this.delimiter = delimiter; + this.suffix = suffix; + } + + public void push(String element) { + elements.push(element); + } + + public void pop() { + elements.pop(); + } + + @Override + public String toString() { + return elements.stream().collect(Collectors.joining(delimiter, prefix, suffix)); + } + } +} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java index 9c1c93ed..75f2b327 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java @@ -187,9 +187,9 @@ public class FuzzTarget { Object returnValue = null; try { if (targetExecutable instanceof Method) { - returnValue = Meta.autofuzz(data, (Method) targetExecutable); + returnValue = Meta.autofuzz(data, (Method) targetExecutable, null); } else { - returnValue = Meta.autofuzz(data, (Constructor<?>) targetExecutable); + returnValue = Meta.autofuzz(data, (Constructor<?>) targetExecutable, null); } executionsSinceLastInvocation = 0; } catch (AutofuzzConstructionException e) { diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java index 38919625..96980530 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java @@ -41,6 +41,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.IntStream; import net.jodah.typetools.TypeResolver; import net.jodah.typetools.TypeResolver.Unknown; @@ -52,19 +53,51 @@ public class Meta { static WeakHashMap<Class<?>, List<Method>> cascadingBuilderMethodsCache = new WeakHashMap<>(); public static Object autofuzz(FuzzedDataProvider data, Method method) { + return autofuzz(data, method, null); + } + + static Object autofuzz(FuzzedDataProvider data, Method method, AutofuzzCodegenVisitor visitor) { + Object result; if (Modifier.isStatic(method.getModifiers())) { - return autofuzz(data, method, null); + if (visitor != null) { + // This group will always have two elements: The class name and the method call. + visitor.pushGroup( + String.format("%s.", method.getDeclaringClass().getCanonicalName()), "", ""); + } + result = autofuzz(data, method, null, visitor); + if (visitor != null) { + visitor.popGroup(); + } } else { - Object thisObject = consume(data, method.getDeclaringClass()); + if (visitor != null) { + // This group will always have two elements: The thisObject and the method call. + visitor.pushGroup("", ".", ""); + } + Object thisObject = consume(data, method.getDeclaringClass(), visitor); if (thisObject == null) { throw new AutofuzzConstructionException(); } - return autofuzz(data, method, thisObject); + result = autofuzz(data, method, thisObject, visitor); + if (visitor != null) { + visitor.popGroup(); + } } + return result; } public static Object autofuzz(FuzzedDataProvider data, Method method, Object thisObject) { - Object[] arguments = consumeArguments(data, method); + return autofuzz(data, method, thisObject, null); + } + + static Object autofuzz( + FuzzedDataProvider data, Method method, Object thisObject, AutofuzzCodegenVisitor visitor) { + if (visitor != null) { + visitor.pushGroup(String.format("%s(", method.getName()), ", ", ")"); + } + Object[] arguments = consumeArguments(data, method, visitor); + if (visitor != null) { + visitor.popGroup(); + } try { return method.invoke(thisObject, arguments); } catch (IllegalAccessException | IllegalArgumentException | NullPointerException e) { @@ -76,7 +109,20 @@ public class Meta { } public static <R> R autofuzz(FuzzedDataProvider data, Constructor<R> constructor) { - Object[] arguments = consumeArguments(data, constructor); + return autofuzz(data, constructor, null); + } + + static <R> R autofuzz( + FuzzedDataProvider data, Constructor<R> constructor, AutofuzzCodegenVisitor visitor) { + if (visitor != null) { + // getCanonicalName is correct also for nested classes. + visitor.pushGroup( + String.format("new %s(", constructor.getDeclaringClass().getCanonicalName()), ", ", ")"); + } + Object[] arguments = consumeArguments(data, constructor, visitor); + if (visitor != null) { + visitor.popGroup(); + } try { return constructor.newInstance(arguments); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) { @@ -161,46 +207,112 @@ public class Meta { } public static Object consume(FuzzedDataProvider data, Class<?> type) { + return consume(data, type, null); + } + + static Object consume(FuzzedDataProvider data, Class<?> type, AutofuzzCodegenVisitor visitor) { if (type == byte.class || type == Byte.class) { - return data.consumeByte(); + byte result = data.consumeByte(); + if (visitor != null) + visitor.pushElement(String.format("(byte) %s", result)); + return result; } else if (type == short.class || type == Short.class) { - return data.consumeShort(); + short result = data.consumeShort(); + if (visitor != null) + visitor.pushElement(String.format("(short) %s", result)); + return result; } else if (type == int.class || type == Integer.class) { - return data.consumeInt(); + int result = data.consumeInt(); + if (visitor != null) + visitor.pushElement(Integer.toString(result)); + return result; } else if (type == long.class || type == Long.class) { - return data.consumeLong(); + long result = data.consumeLong(); + if (visitor != null) + visitor.pushElement(String.format("%sL", result)); + return result; } else if (type == float.class || type == Float.class) { - return data.consumeFloat(); + float result = data.consumeFloat(); + if (visitor != null) + visitor.pushElement(String.format("%sF", result)); + return result; } else if (type == double.class || type == Double.class) { - return data.consumeDouble(); + double result = data.consumeDouble(); + if (visitor != null) + visitor.pushElement(Double.toString(result)); + return result; } else if (type == boolean.class || type == Boolean.class) { - return data.consumeBoolean(); + boolean result = data.consumeBoolean(); + if (visitor != null) + visitor.pushElement(Boolean.toString(result)); + return result; } else if (type == char.class || type == Character.class) { - return data.consumeChar(); + char result = data.consumeChar(); + if (visitor != null) + visitor.addCharLiteral(result); + return result; } // Return null for non-primitive and non-boxed types in ~5% of the cases. // TODO: We might want to return null for boxed types sometimes, but this is complicated by the // fact that TypeUtils can't distinguish between a primitive type and its wrapper and may // thus easily cause false-positive NullPointerExceptions. if (!type.isPrimitive() && data.consumeByte((byte) 0, (byte) 19) == 0) { + if (visitor != null) + visitor.pushElement("null"); return null; } if (type == String.class || type == CharSequence.class) { - return data.consumeString(consumeArrayLength(data, 1)); + String result = data.consumeString(consumeArrayLength(data, 1)); + if (visitor != null) + visitor.addStringLiteral(result); + return result; } else if (type.isArray()) { if (type == byte[].class) { - return data.consumeBytes(consumeArrayLength(data, Byte.BYTES)); + byte[] result = data.consumeBytes(consumeArrayLength(data, Byte.BYTES)); + if (visitor != null) { + visitor.pushElement(IntStream.range(0, result.length) + .mapToObj(i -> "(byte) " + result[i]) + .collect(Collectors.joining(", ", "new byte[]{", "}"))); + } + return result; } else if (type == int[].class) { - return data.consumeInts(consumeArrayLength(data, Integer.BYTES)); + int[] result = data.consumeInts(consumeArrayLength(data, Integer.BYTES)); + if (visitor != null) { + visitor.pushElement(Arrays.stream(result) + .mapToObj(String::valueOf) + .collect(Collectors.joining(", ", "new int[]{", "}"))); + } + return result; } else if (type == short[].class) { - return data.consumeShorts(consumeArrayLength(data, Short.BYTES)); + short[] result = data.consumeShorts(consumeArrayLength(data, Short.BYTES)); + if (visitor != null) { + visitor.pushElement(IntStream.range(0, result.length) + .mapToObj(i -> "(short) " + result[i]) + .collect(Collectors.joining(", ", "new short[]{", "}"))); + } + return result; } else if (type == long[].class) { - return data.consumeLongs(consumeArrayLength(data, Long.BYTES)); + long[] result = data.consumeLongs(consumeArrayLength(data, Long.BYTES)); + if (visitor != null) { + visitor.pushElement(Arrays.stream(result) + .mapToObj(e -> e + "L") + .collect(Collectors.joining(", ", "new long[]{", "}"))); + } + return result; } else if (type == boolean[].class) { - return data.consumeBooleans(consumeArrayLength(data, 1)); + boolean[] result = data.consumeBooleans(consumeArrayLength(data, 1)); + if (visitor != null) { + visitor.pushElement( + Arrays.toString(result).replace(']', '}').replace("[", "new boolean[]{")); + } + return result; } else { + if (visitor != null) { + visitor.pushGroup( + String.format("new %s[]{", type.getComponentType().getName()), ", ", "}"); + } int remainingBytesBeforeFirstElementCreation = data.remainingBytes(); - Object firstElement = consume(data, type.getComponentType()); + Object firstElement = consume(data, type.getComponentType(), visitor); int remainingBytesAfterFirstElementCreation = data.remainingBytes(); int sizeOfElementEstimate = remainingBytesBeforeFirstElementCreation - remainingBytesAfterFirstElementCreation; @@ -210,20 +322,47 @@ public class Meta { if (i == 0) { Array.set(array, i, firstElement); } else { - Array.set(array, i, consume(data, type.getComponentType())); + Array.set(array, i, consume(data, type.getComponentType(), visitor)); + } + } + if (visitor != null) { + if (Array.getLength(array) == 0) { + // We implicitly pushed the first element with the call to consume above, but it is not + // part of the array. + visitor.popElement(); } + visitor.popGroup(); } return array; } } else if (type == ByteArrayInputStream.class || type == InputStream.class) { - return new ByteArrayInputStream(data.consumeBytes(data.remainingBytes() / 2)); + byte[] array = data.consumeBytes(consumeArrayLength(data, Byte.BYTES)); + if (visitor != null) { + visitor.pushElement(IntStream.range(0, array.length) + .mapToObj(i -> "(byte) " + array[i]) + .collect(Collectors.joining( + ", ", "new java.io.ByteArrayInputStream(new byte[]{", "})"))); + } + return new ByteArrayInputStream(array); } else if (type.isEnum()) { - return data.pickValue(type.getEnumConstants()); + Enum<?> enumValue = (Enum<?>) data.pickValue(type.getEnumConstants()); + if (visitor != null) { + visitor.pushElement(String.format("%s.%s", type.getName(), enumValue.name())); + } + return enumValue; } else if (type == Class.class) { + if (visitor != null) + visitor.pushElement(String.format("%s.class", YourAverageJavaClass.class.getName())); return YourAverageJavaClass.class; } else if (type == Method.class) { + if (visitor != null) { + throw new AutofuzzError("codegen has not been implemented for Method.class"); + } return data.pickValue(sortExecutables(YourAverageJavaClass.class.getMethods())); } else if (type == Constructor.class) { + if (visitor != null) { + throw new AutofuzzError("codegen has not been implemented for Constructor.class"); + } return data.pickValue(sortExecutables(YourAverageJavaClass.class.getConstructors())); } else if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { List<Class<?>> implementingClasses = implementingClassesCache.get(type); @@ -250,19 +389,40 @@ public class Meta { throw new AutofuzzConstructionException(); } } - return consume(data, data.pickValue(implementingClasses)); + if (visitor != null) { + // This group will always have a single element: The instance of the implementing class. + visitor.pushGroup(String.format("(%s) ", type.getName()), "", ""); + } + Object result = consume(data, data.pickValue(implementingClasses), visitor); + if (visitor != null) { + visitor.popGroup(); + } + return result; } else if (type.getConstructors().length > 0) { Constructor<?> constructor = data.pickValue(sortExecutables(type.getConstructors())); - Object obj = autofuzz(data, constructor); - if (constructor.getParameterCount() == 0) { + boolean applySetters = constructor.getParameterCount() == 0; + if (visitor != null && applySetters) { + // Embed the instance creation and setters into an immediately invoked lambda expression to + // turn them into an expression. + String uniqueVariableName = visitor.uniqueVariableName(); + visitor.pushGroup(String.format("((java.util.function.Supplier<%1$s>) (() -> {%1$s %2$s = ", + type.getCanonicalName(), uniqueVariableName), + String.format("; %s.", uniqueVariableName), + String.format("; return %s;})).get()", uniqueVariableName)); + } + Object obj = autofuzz(data, constructor, visitor); + if (applySetters) { List<Method> potentialSetters = getPotentialSetters(type); if (!potentialSetters.isEmpty()) { List<Method> pickedSetters = data.pickValues(potentialSetters, data.consumeInt(0, potentialSetters.size())); for (Method setter : pickedSetters) { - autofuzz(data, setter, obj); + autofuzz(data, setter, obj, visitor); } } + if (visitor != null) { + visitor.popGroup(); + } } return obj; } @@ -281,14 +441,22 @@ public class Meta { List<Method> pickedMethods = data.pickValues(cascadingBuilderMethods, pickedMethodsNumber); Method builderMethod = data.pickValue(originalObjectCreationMethods); + if (visitor != null) { + // Group for the chain of builder methods. + visitor.pushGroup("", ".", ""); + } Object builderObj = - autofuzz(data, data.pickValue(sortExecutables(pickedBuilder.getConstructors()))); + autofuzz(data, data.pickValue(sortExecutables(pickedBuilder.getConstructors())), visitor); for (Method method : pickedMethods) { - builderObj = autofuzz(data, method, builderObj); + builderObj = autofuzz(data, method, builderObj, visitor); } try { - return autofuzz(data, builderMethod, builderObj); + Object obj = autofuzz(data, builderMethod, builderObj, visitor); + if (visitor != null) { + visitor.popGroup(); + } + return obj; } catch (Exception e) { throw new AutofuzzConstructionException(e); } @@ -406,11 +574,12 @@ public class Meta { return potentialSetters; } - private static Object[] consumeArguments(FuzzedDataProvider data, Executable executable) { + private static Object[] consumeArguments( + FuzzedDataProvider data, Executable executable, AutofuzzCodegenVisitor visitor) { Object[] result; try { result = Arrays.stream(executable.getParameterTypes()) - .map((type) -> consume(data, type)) + .map((type) -> consume(data, type, visitor)) .toArray(); return result; } catch (AutofuzzConstructionException e) { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel index 82de3bc9..f8448f01 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel @@ -6,6 +6,7 @@ java_test( ], test_class = "com.code_intelligence.jazzer.autofuzz.MetaTest", deps = [ + ":test_helpers", "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", "@maven//:com_mikesamuel_json_sanitizer", @@ -25,8 +26,7 @@ java_test( }, test_class = "com.code_intelligence.jazzer.autofuzz.InterfaceCreationTest", deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/api", - "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", + ":test_helpers", "@maven//:junit_junit", ], ) @@ -39,8 +39,7 @@ java_test( ], test_class = "com.code_intelligence.jazzer.autofuzz.BuilderPatternTest", deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/api", - "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", + ":test_helpers", "@maven//:junit_junit", ], ) @@ -53,9 +52,18 @@ java_test( ], test_class = "com.code_intelligence.jazzer.autofuzz.SettersTest", deps = [ + ":test_helpers", + "//agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata:test_data", + "@maven//:junit_junit", + ], +) + +java_library( + name = "test_helpers", + srcs = ["TestHelpers.java"], + deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", - "//agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata:test_data", "@maven//:junit_junit", ], ) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java index 4f59832f..a602d712 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java @@ -14,10 +14,8 @@ package com.code_intelligence.jazzer.autofuzz; -import static org.junit.Assert.assertEquals; +import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase; -import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; import java.util.Arrays; import java.util.Objects; import org.junit.Test; @@ -79,29 +77,27 @@ class Employee { } public class BuilderPatternTest { - FuzzedDataProvider data = - CannedFuzzedDataProvider.create(Arrays.asList((byte) 1, // do not return null - 0, // Select the first Builder - 2, // Select two Builder methods returning a builder object (fluent design) - 0, // Select the first build method - 0, // pick the first remaining builder method (withAge) - 0, // pick the first remaining builder method (withJobTitle) - 0, // pick the first build method - (byte) 1, // do not return null - 6, // remaining bytes - "foo", // firstName - (byte) 1, // do not return null - 6, // remaining bytes - "bar", // lastName - 20, // age - (byte) 1, // do not return null - 6, // remaining bytes - "baz" // jobTitle - )); - @Test public void testBuilderPattern() { - assertEquals(Meta.consume(data, Employee.class), - new Employee.Builder("foo", "bar").withAge(20).withJobTitle("baz").build()); + consumeTestCase(new Employee.Builder("foo", "bar").withAge(20).withJobTitle("baz").build(), + "new com.code_intelligence.jazzer.autofuzz.Employee.Builder(\"foo\", \"bar\").withAge(20).withJobTitle(\"baz\").build()", + Arrays.asList((byte) 1, // do not return null + 0, // Select the first Builder + 2, // Select two Builder methods returning a builder object (fluent design) + 0, // Select the first build method + 0, // pick the first remaining builder method (withAge) + 0, // pick the first remaining builder method (withJobTitle) + 0, // pick the first build method + (byte) 1, // do not return null + 6, // remaining bytes + "foo", // firstName + (byte) 1, // do not return null + 6, // remaining bytes + "bar", // lastName + 20, // age + (byte) 1, // do not return null + 6, // remaining bytes + "baz" // jobTitle + )); } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java index 2858d68d..4d85ca6c 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java @@ -14,10 +14,8 @@ package com.code_intelligence.jazzer.autofuzz; -import static org.junit.Assert.assertEquals; +import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase; -import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; import java.util.Arrays; import java.util.Objects; import org.junit.Test; @@ -89,24 +87,25 @@ class ClassB2 implements InterfaceA { } public class InterfaceCreationTest { - FuzzedDataProvider data = - CannedFuzzedDataProvider.create(Arrays.asList((byte) 1, // do not return null - 0, // pick ClassB1 - (byte) 1, // do not return null - 0, // pick first constructor - 5, // arg for ClassB1 constructor - (byte) 1, // do not return null - 1, // pick ClassB2 - (byte) 1, // do not return null - 0, // pick first constructor - (byte) 1, // do not return null - 8, // remaining bytes - "test" // arg for ClassB2 constructor - )); - @Test public void testConsumeInterface() { - assertEquals(Meta.consume(data, InterfaceA.class), new ClassB1(5)); - assertEquals(Meta.consume(data, InterfaceA.class), new ClassB2("test")); + consumeTestCase(InterfaceA.class, new ClassB1(5), + "(com.code_intelligence.jazzer.autofuzz.InterfaceA) new com.code_intelligence.jazzer.autofuzz.ClassB1(5)", + Arrays.asList((byte) 1, // do not return null + 0, // pick ClassB1 + (byte) 1, // do not return null + 0, // pick first constructor + 5 // arg for ClassB1 constructor + )); + consumeTestCase(InterfaceA.class, new ClassB2("test"), + "(com.code_intelligence.jazzer.autofuzz.InterfaceA) new com.code_intelligence.jazzer.autofuzz.ClassB2(\"test\")", + Arrays.asList((byte) 1, // do not return null + 1, // pick ClassB2 + (byte) 1, // do not return null + 0, // pick first constructor + (byte) 1, // do not return null + 8, // remaining bytes + "test" // arg for ClassB2 constructor + )); } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index 25891aa5..0615e9ae 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -14,13 +14,14 @@ package com.code_intelligence.jazzer.autofuzz; +import static com.code_intelligence.jazzer.autofuzz.TestHelpers.autofuzzTestCase; +import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; -import com.code_intelligence.jazzer.api.Function1; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.google.json.JsonSanitizer; +import java.io.ByteArrayInputStream; import java.util.Arrays; import java.util.Collections; import org.junit.Test; @@ -30,35 +31,117 @@ public class MetaTest { return arg == 5; } + public static boolean intEquals(int arg1, int arg2) { + return arg1 == arg2; + } + + public enum TestEnum { + FOO, + BAR, + BAZ, + } + @Test public void testConsume() { - FuzzedDataProvider data = CannedFuzzedDataProvider.create(Collections.singletonList(5)); - assertEquals(5, Meta.consume(data, int.class)); + consumeTestCase(5, "5", Collections.singletonList(5)); + consumeTestCase((short) 5, "(short) 5", Collections.singletonList((short) 5)); + consumeTestCase(5L, "5L", Collections.singletonList(5L)); + consumeTestCase(5.0F, "5.0F", Collections.singletonList(5.0F)); + consumeTestCase('\n', "'\\\\n'", Collections.singletonList('\n')); + consumeTestCase('\'', "'\\\\''", Collections.singletonList('\'')); + consumeTestCase('\\', "'\\\\'", Collections.singletonList('\\')); + + String testString = "foo\n\t\\\"bar"; + // The expected string is obtained from testString by escaping, wrapping into quotes and + // escaping again. + consumeTestCase(testString, "\"foo\\\\n\\\\t\\\\\\\\\"bar\"", + Arrays.asList((byte) 1, // do not return null + testString.length(), testString)); + + consumeTestCase(null, "null", Collections.singletonList((byte) 0)); + + boolean[] testBooleans = new boolean[] {true, false, true}; + consumeTestCase(testBooleans, "new boolean[]{true, false, true}", + Arrays.asList((byte) 1, // do not return null for the array + 2 * 3, testBooleans)); + + char[] testChars = new char[] {'a', '\n', '\''}; + consumeTestCase(testChars, "new char[]{'a', '\\\\n', '\\\\''}", + Arrays.asList((byte) 1, // do not return null for the array + 2 * 3 * Character.BYTES + Character.BYTES, testChars[0], 2 * 3 * Character.BYTES, + 2 * 3 * Character.BYTES, // remaining bytes, 2 times what is needed for 3 chars + testChars[1], testChars[2])); + + char[] testNoChars = new char[] {}; + consumeTestCase(testNoChars, "new char[]{}", + Arrays.asList((byte) 1, // do not return null for the array + 0, 'a', 0, 0)); + + short[] testShorts = new short[] {(short) 1, (short) 2, (short) 3}; + consumeTestCase(testShorts, "new short[]{(short) 1, (short) 2, (short) 3}", + Arrays.asList((byte) 1, // do not return null for the array + 2 * 3 * Short.BYTES, // remaining bytes + testShorts)); + + long[] testLongs = new long[] {1L, 2L, 3L}; + consumeTestCase(testLongs, "new long[]{1L, 2L, 3L}", + Arrays.asList((byte) 1, // do not return null for the array + 2 * 3 * Long.BYTES, // remaining bytes + testLongs)); + + consumeTestCase(new String[] {"foo", "bar", "foo\nbar"}, + "new java.lang.String[]{\"foo\", \"bar\", \"foo\\\\nbar\"}", + Arrays.asList((byte) 1, // do not return null for the array + 32, // remaining bytes + (byte) 1, // do not return null for the string + 31, // remaining bytes + "foo", + 28, // remaining bytes + 28, // array length + (byte) 1, // do not return null for the string + 27, // remaining bytes + "bar", + (byte) 1, // do not return null for the string + 23, // remaining bytes + "foo\nbar")); + + byte[] testInputStreamBytes = new byte[] {(byte) 1, (byte) 2, (byte) 3}; + consumeTestCase(new ByteArrayInputStream(testInputStreamBytes), + "new java.io.ByteArrayInputStream(new byte[]{(byte) 1, (byte) 2, (byte) 3})", + Arrays.asList((byte) 1, // do not return null for the InputStream + 2 * 3, // remaining bytes (twice the desired length) + testInputStreamBytes)); + + consumeTestCase(TestEnum.BAR, + String.format("%s.%s", TestEnum.class.getName(), TestEnum.BAR.name()), + Arrays.asList((byte) 1, // do not return null for the enum value + 1 /* second value */ + )); + + consumeTestCase(YourAverageJavaClass.class, + "com.code_intelligence.jazzer.autofuzz.YourAverageJavaClass.class", + Collections.singletonList((byte) 1)); } @Test - public void testAutofuzz() { - FuzzedDataProvider data = CannedFuzzedDataProvider.create(Arrays.asList(5, - (byte) 1, // do not return null - 6, // remainingBytes - "foo", - (byte) 1, // do not return null - 6, // remainingBytes - "bar", - (byte) 1, // do not return null - 8, // remainingBytes - "buzz", - (byte) 1, // do not return null - 6, // remainingBytes - "jazzer", - (byte) 1, // do not return null - 6, // remainingBytes - "jazzer")); - assertTrue(Meta.autofuzz(data, MetaTest::isFive)); - assertEquals("foobar", Meta.autofuzz(data, String::concat)); + public void testAutofuzz() throws NoSuchMethodException { + autofuzzTestCase(true, "com.code_intelligence.jazzer.autofuzz.MetaTest.isFive(5)", + MetaTest.class.getMethod("isFive", int.class), Collections.singletonList(5)); + autofuzzTestCase(false, "com.code_intelligence.jazzer.autofuzz.MetaTest.intEquals(5, 4)", + MetaTest.class.getMethod("intEquals", int.class, int.class), Arrays.asList(5, 4)); + autofuzzTestCase("foobar", "\"foo\".concat(\"bar\")", + String.class.getMethod("concat", String.class), + Arrays.asList((byte) 1, 6, "foo", (byte) 1, 6, "bar")); + autofuzzTestCase("jazzer", "new java.lang.String(\"jazzer\")", + String.class.getConstructor(String.class), Arrays.asList((byte) 1, 12, "jazzer")); + autofuzzTestCase("\"jazzer\"", "com.google.json.JsonSanitizer.sanitize(\"jazzer\")", + JsonSanitizer.class.getMethod("sanitize", String.class), + Arrays.asList((byte) 1, 12, "jazzer")); + + FuzzedDataProvider data = + CannedFuzzedDataProvider.create(Arrays.asList((byte) 1, // do not return null + 8, // remainingBytes + "buzz")); assertEquals("fizzbuzz", Meta.autofuzz(data, "fizz" ::concat)); - assertEquals("jazzer", Meta.autofuzz(data, (Function1<String, ?>) String::new)); - assertEquals( - "\"jazzer\"", Meta.autofuzz(data, (Function1<String, String>) JsonSanitizer::sanitize)); } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java index 5403b19e..7c869531 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java @@ -14,32 +14,30 @@ package com.code_intelligence.jazzer.autofuzz; -import static org.junit.Assert.assertEquals; +import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase; -import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters; import java.util.Arrays; import org.junit.Test; public class SettersTest { - FuzzedDataProvider data = CannedFuzzedDataProvider.create( - Arrays.asList((byte) 1, // do not return null for EmployeeWithSetters - 0, // pick first constructor - 2, // pick two setters - 1, // pick second setter - 0, // pick first setter - (byte) 1, // do not return null for String - 6, // remaining bytes - "foo", // setFirstName - 26 // setAge - )); - @Test public void testEmptyConstructorWithSetters() { EmployeeWithSetters employee = new EmployeeWithSetters(); employee.setFirstName("foo"); employee.setAge(26); - assertEquals(Meta.consume(data, EmployeeWithSetters.class), employee); + + consumeTestCase(employee, + "((java.util.function.Supplier<com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters>) (() -> {com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters autofuzzVariable0 = new com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters(); autofuzzVariable0.setFirstName(\"foo\"); autofuzzVariable0.setAge(26); return autofuzzVariable0;})).get()", + Arrays.asList((byte) 1, // do not return null for EmployeeWithSetters + 0, // pick first constructor + 2, // pick two setters + 1, // pick second setter + 0, // pick first setter + (byte) 1, // do not return null for String + 6, // remaining bytes + "foo", // setFirstName + 26 // setAge + )); } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java new file mode 100644 index 00000000..52f19a74 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java @@ -0,0 +1,85 @@ +// Copyright 2021 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.autofuzz; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.io.ByteArrayInputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.List; + +class TestHelpers { + static void assertGeneralEquals(Object expected, Object actual) { + Class<?> type = expected != null ? expected.getClass() : Object.class; + if (type.isArray()) { + if (type.getComponentType() == boolean.class) { + assertArrayEquals((boolean[]) expected, (boolean[]) actual); + } else if (type.getComponentType() == char.class) { + assertArrayEquals((char[]) expected, (char[]) actual); + } else if (type.getComponentType() == short.class) { + assertArrayEquals((short[]) expected, (short[]) actual); + } else if (type.getComponentType() == long.class) { + assertArrayEquals((long[]) expected, (long[]) actual); + } else { + assertArrayEquals((Object[]) expected, (Object[]) actual); + } + } else if (type == ByteArrayInputStream.class) { + ByteArrayInputStream expectedStream = (ByteArrayInputStream) expected; + ByteArrayInputStream actualStream = (ByteArrayInputStream) actual; + assertArrayEquals(readAllBytes(expectedStream), readAllBytes(actualStream)); + } else { + assertEquals(expected, actual); + } + } + + static void consumeTestCase( + Object expectedResult, String expectedResultString, List<Object> cannedData) { + Class<?> type = expectedResult != null ? expectedResult.getClass() : Object.class; + consumeTestCase(type, expectedResult, expectedResultString, cannedData); + } + + static void consumeTestCase( + Class<?> type, Object expectedResult, String expectedResultString, List<Object> cannedData) { + assertTrue(expectedResult == null || type.isAssignableFrom(expectedResult.getClass())); + AutofuzzCodegenVisitor visitor = new AutofuzzCodegenVisitor(); + FuzzedDataProvider data = CannedFuzzedDataProvider.create(cannedData); + assertGeneralEquals(expectedResult, Meta.consume(data, type, visitor)); + assertEquals(expectedResultString, visitor.generate()); + } + + static void autofuzzTestCase(Object expectedResult, String expectedResultString, Executable func, + List<Object> cannedData) { + AutofuzzCodegenVisitor visitor = new AutofuzzCodegenVisitor(); + FuzzedDataProvider data = CannedFuzzedDataProvider.create(cannedData); + if (func instanceof Method) { + assertGeneralEquals(expectedResult, Meta.autofuzz(data, (Method) func, visitor)); + } else { + assertGeneralEquals(expectedResult, Meta.autofuzz(data, (Constructor<?>) func, visitor)); + } + assertEquals(expectedResultString, visitor.generate()); + } + + private static byte[] readAllBytes(ByteArrayInputStream in) { + byte[] result = new byte[in.available()]; + in.read(result, 0, in.available()); + return result; + } +} |