diff options
author | Khaled Yakdan <yakdan@code-intelligence.com> | 2021-10-15 13:32:38 +0200 |
---|---|---|
committer | Fabian Meumertzheim <fabian@meumertzhe.im> | 2021-10-19 11:07:51 +0200 |
commit | bebc491c60c0b26313ed43387411627817ef1d0c (patch) | |
tree | cf33eae9ca4ebd00475653b9be74003640da3512 /agent/src | |
parent | 8ffc98fe44236a542127395c60253e980f8970a6 (diff) | |
download | jazzer-api-bebc491c60c0b26313ed43387411627817ef1d0c.tar.gz |
Create object with nested builder class
Diffstat (limited to 'agent/src')
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()); + } +} |