aboutsummaryrefslogtreecommitdiff
path: root/agent
diff options
context:
space:
mode:
authorFabian Meumertzheim <fabian@meumertzhe.im>2021-10-29 10:14:06 +0200
committerFabian Meumertzheim <fabian@meumertzhe.im>2021-11-18 11:13:53 +0100
commit70e9992f37217426952ff48c952bc95e8fc56e34 (patch)
tree5f1837a11559255074814cb4fd8c5a6f36f3f563 /agent
parent3041449418bcbcc03362ef91dfeb0d5febaf134c (diff)
downloadjazzer-api-70e9992f37217426952ff48c952bc95e8fc56e34.tar.gz
Implement code generation for consume and autofuzz
Method/Constructor are not yet implemented.
Diffstat (limited to 'agent')
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java117
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java4
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java233
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel18
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java46
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java39
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java135
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java30
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java85
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;
+ }
+}