aboutsummaryrefslogtreecommitdiff
path: root/agent
diff options
context:
space:
mode:
authorKhaled Yakdan <yakdan@code-intelligence.com>2021-10-15 13:32:38 +0200
committerFabian Meumertzheim <fabian@meumertzhe.im>2021-10-19 11:07:51 +0200
commitbebc491c60c0b26313ed43387411627817ef1d0c (patch)
treecf33eae9ca4ebd00475653b9be74003640da3512 /agent
parent8ffc98fe44236a542127395c60253e980f8970a6 (diff)
downloadjazzer-api-bebc491c60c0b26313ed43387411627817ef1d0c.tar.gz
Create object with nested builder class
Diffstat (limited to 'agent')
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java169
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel14
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java103
3 files changed, 223 insertions, 63 deletions
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 8f229d51..f544192c 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
@@ -25,15 +25,68 @@ import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.WeakHashMap;
+import java.util.stream.Collectors;
import net.jodah.typetools.TypeResolver;
import net.jodah.typetools.TypeResolver.Unknown;
public class Meta {
static WeakHashMap<Class<?>, List<Class<?>>> cache = new WeakHashMap<>();
+ public static Object autofuzz(FuzzedDataProvider data, Method method) {
+ if (Modifier.isStatic(method.getModifiers())) {
+ return autofuzz(data, method, null);
+ } else {
+ return autofuzz(data, method, consume(data, method.getDeclaringClass()));
+ }
+ }
+
+ public static Object autofuzz(FuzzedDataProvider data, Method method, Object thisObject) {
+ Object[] arguments = consumeArguments(data, method);
+ try {
+ return method.invoke(thisObject, arguments);
+ } catch (IllegalAccessException | IllegalArgumentException | NullPointerException e) {
+ // We should ensure that the arguments fed into the method are always valid.
+ throw new AutofuzzError(e);
+ } catch (InvocationTargetException e) {
+ throw new AutofuzzInvocationException(e.getCause());
+ }
+ }
+
+ public static <R> R autofuzz(FuzzedDataProvider data, Constructor<R> constructor) {
+ Object[] arguments = consumeArguments(data, constructor);
+ try {
+ return constructor.newInstance(arguments);
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) {
+ // This should never be reached as the logic in consume should prevent us from e.g. calling
+ // constructors of abstract classes or private constructors.
+ throw new AutofuzzError(e);
+ } catch (InvocationTargetException e) {
+ throw new AutofuzzInvocationException(e.getCause());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1> void autofuzz(FuzzedDataProvider data, Consumer1<T1> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Consumer1.class, func.getClass());
+ func.accept((T1) consumeChecked(data, types, 0));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, R> R autofuzz(FuzzedDataProvider data, Function1<T1, R> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Function1.class, func.getClass());
+ return func.apply((T1) consumeChecked(data, types, 0));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, R> R autofuzz(FuzzedDataProvider data, Function2<T1, T2, R> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Function2.class, func.getClass());
+ return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1));
+ }
+
public static Object consume(FuzzedDataProvider data, Class<?> type) {
if (type == byte.class || type == Byte.class) {
return data.consumeByte();
@@ -90,17 +143,55 @@ public class Meta {
return consume(data, data.pickValue(implementingClasses));
} else if (type.getConstructors().length > 0) {
return autofuzz(data, data.pickValue(type.getConstructors()));
+ } else if (getNestedBuilderClasses(type).size() > 0) {
+ List<Class<?>> nestedBuilderClasses = getNestedBuilderClasses(type);
+ Class<?> pickedBuilder = data.pickValue(nestedBuilderClasses);
+
+ List<Method> cascadingBuilderMethods = Arrays.stream(pickedBuilder.getMethods())
+ .filter(m -> m.getReturnType() == pickedBuilder)
+ .collect(Collectors.toList());
+
+ List<Method> originalObjectCreationMethods = Arrays.stream(pickedBuilder.getMethods())
+ .filter(m -> m.getReturnType() == type)
+ .collect(Collectors.toList());
+
+ int pickedMethodsNumber = data.consumeInt(0, cascadingBuilderMethods.size());
+ List<Method> pickedMethods = new ArrayList<>();
+ for (int i = 0; i < pickedMethodsNumber; i++) {
+ Method method = data.pickValue(cascadingBuilderMethods);
+ pickedMethods.add(method);
+ cascadingBuilderMethods.remove(method);
+ }
+
+ Method builderMethod = data.pickValue(originalObjectCreationMethods);
+
+ Object obj = autofuzz(data, data.pickValue(pickedBuilder.getConstructors()));
+ for (Method method : pickedMethods) {
+ obj = autofuzz(data, method, obj);
+ }
+
+ try {
+ return builderMethod.invoke(obj);
+ } catch (Exception e) {
+ throw new AutofuzzConstructionException(e);
+ }
}
return null;
}
- private static Object consumeChecked(FuzzedDataProvider data, Class<?>[] types, int i) {
- if (types[i] == Unknown.class) {
- throw new AutofuzzError("Failed to determine type of argument " + (i + 1));
- }
- Object result;
+ private static List<Class<?>> getNestedBuilderClasses(Class<?> type) {
+ return Arrays.stream(type.getClasses())
+ .filter(cls -> cls.getName().endsWith("Builder"))
+ .collect(Collectors.toList());
+ }
+
+ private static Object[] consumeArguments(FuzzedDataProvider data, Executable executable) {
+ Object[] result;
try {
- result = consume(data, types[i]);
+ result = Arrays.stream(executable.getParameterTypes())
+ .map((type) -> consume(data, type))
+ .toArray();
+ return result;
} catch (AutofuzzConstructionException e) {
// Do not nest AutofuzzConstructionExceptions.
throw e;
@@ -111,52 +202,15 @@ public class Meta {
} catch (Throwable t) {
throw new AutofuzzConstructionException(t);
}
- if (result != null && !types[i].isAssignableFrom(result.getClass())) {
- throw new AutofuzzError("consume returned " + result.getClass() + ", but need " + types[i]);
- }
- return result;
- }
-
- public static Object autofuzz(FuzzedDataProvider data, Method method) {
- if (Modifier.isStatic(method.getModifiers())) {
- return autofuzz(data, method, null);
- } else {
- return autofuzz(data, method, consume(data, method.getDeclaringClass()));
- }
- }
-
- public static Object autofuzz(FuzzedDataProvider data, Method method, Object thisObject) {
- Object[] arguments = consumeArguments(data, method);
- try {
- return method.invoke(thisObject, arguments);
- } catch (IllegalAccessException | IllegalArgumentException | NullPointerException e) {
- // We should ensure that the arguments fed into the method are always valid.
- throw new AutofuzzError(e);
- } catch (InvocationTargetException e) {
- throw new AutofuzzInvocationException(e.getCause());
- }
}
- public static <R> R autofuzz(FuzzedDataProvider data, Constructor<R> constructor) {
- Object[] arguments = consumeArguments(data, constructor);
- try {
- return constructor.newInstance(arguments);
- } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) {
- // This should never be reached as the logic in consume should prevent us from e.g. calling
- // constructors of abstract classes or private constructors.
- throw new AutofuzzError(e);
- } catch (InvocationTargetException e) {
- throw new AutofuzzInvocationException(e.getCause());
+ private static Object consumeChecked(FuzzedDataProvider data, Class<?>[] types, int i) {
+ if (types[i] == Unknown.class) {
+ throw new AutofuzzError("Failed to determine type of argument " + (i + 1));
}
- }
-
- private static Object[] consumeArguments(FuzzedDataProvider data, Executable executable) {
- Object[] result;
+ Object result;
try {
- result = Arrays.stream(executable.getParameterTypes())
- .map((type) -> consume(data, type))
- .toArray();
- return result;
+ result = consume(data, types[i]);
} catch (AutofuzzConstructionException e) {
// Do not nest AutofuzzConstructionExceptions.
throw e;
@@ -167,20 +221,9 @@ public class Meta {
} catch (Throwable t) {
throw new AutofuzzConstructionException(t);
}
- }
-
- public static <T1> void autofuzz(FuzzedDataProvider data, Consumer1<T1> func) {
- Class<?>[] types = TypeResolver.resolveRawArguments(Consumer1.class, func.getClass());
- func.accept((T1) consumeChecked(data, types, 0));
- }
-
- public static <T1, R> R autofuzz(FuzzedDataProvider data, Function1<T1, R> func) {
- Class<?>[] types = TypeResolver.resolveRawArguments(Function1.class, func.getClass());
- return func.apply((T1) consumeChecked(data, types, 0));
- }
-
- public static <T1, T2, R> R autofuzz(FuzzedDataProvider data, Function2<T1, T2, R> func) {
- Class<?>[] types = TypeResolver.resolveRawArguments(Function2.class, func.getClass());
- return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1));
+ if (result != null && !types[i].isAssignableFrom(result.getClass())) {
+ throw new AutofuzzError("consume returned " + result.getClass() + ", but need " + types[i]);
+ }
+ return result;
}
}
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 454e8e3f..84d86e2a 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
@@ -28,3 +28,17 @@ java_test(
"@maven//:junit_junit",
],
)
+
+java_test(
+ name = "BuilderPatternTest",
+ size = "small",
+ srcs = [
+ "BuilderPatternTest.java",
+ ],
+ 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",
+ "@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
new file mode 100644
index 00000000..2389f83e
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java
@@ -0,0 +1,103 @@
+// 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.assertEquals;
+
+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;
+
+class Employee {
+ private final String firstName;
+ private final String lastName;
+ private final String jobTitle;
+ private final int age;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Employee hero = (Employee) o;
+ return age == hero.age && Objects.equals(firstName, hero.firstName)
+ && Objects.equals(lastName, hero.lastName) && Objects.equals(jobTitle, hero.jobTitle);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(firstName, lastName, jobTitle, age);
+ }
+
+ private Employee(Builder builder) {
+ this.jobTitle = builder.jobTitle;
+ this.firstName = builder.firstName;
+ this.lastName = builder.lastName;
+ this.age = builder.age;
+ }
+
+ public static class Builder {
+ private final String firstName;
+ private final String lastName;
+ private String jobTitle;
+ private int age;
+
+ public Builder(String firstName, String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public Builder withAge(int age) {
+ this.age = age;
+ return this;
+ }
+
+ public Builder withJobTitle(String jobTitle) {
+ this.jobTitle = jobTitle;
+ return this;
+ }
+
+ public Employee build() {
+ return new Employee(this);
+ }
+ }
+}
+
+public class BuilderPatternTest {
+ FuzzedDataProvider data =
+ CannedFuzzedDataProvider.create(Arrays.asList(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
+ 6, // remaining bytes
+ "foo", // firstName
+ 6, // remaining bytes
+ "bar", // lastName
+ 20, // age
+ 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());
+ }
+}