From 55973e53b3cc59ac276a3e309884ac5c6b4387a8 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Mon, 22 Sep 2025 12:54:07 +0200 Subject: [PATCH 1/5] fix: null pointer exception when detaching maps containing X->null Java's toMap and further down uniqKeysMapAccumulator require non-null values. --- .../mutator/collection/MapMutatorFactory.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorFactory.java index 6e2e4f171..6754d514b 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/collection/MapMutatorFactory.java @@ -31,7 +31,6 @@ import static com.code_intelligence.jazzer.mutation.support.TypeSupport.parameterTypesIfParameterized; import static java.lang.Math.min; import static java.lang.String.format; -import static java.util.stream.Collectors.toMap; import com.code_intelligence.jazzer.mutation.annotation.WithSize; import com.code_intelligence.jazzer.mutation.api.Debuggable; @@ -207,11 +206,11 @@ public boolean hasFixedSize() { @Override public Map detach(Map value) { - return value.entrySet().stream() - .collect( - toMap( - entry -> keyMutator.detach(entry.getKey()), - entry -> valueMutator.detach(entry.getValue()))); + Map detached = new LinkedHashMap<>(value.size(), 1.0f); + for (Map.Entry entry : value.entrySet()) { + detached.put(keyMutator.detach(entry.getKey()), valueMutator.detach(entry.getValue())); + } + return detached; } @Override From 7a9acc19df281ec5bb13f5ecb820674f724ebd49 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Thu, 18 Sep 2025 10:05:52 +0200 Subject: [PATCH 2/5] test: self-fuzz the mutation framework This new self-fuzz-test uncovered many problems with the mutation framework for cases where users fuzz for a while before changing the fuzz test signature and then continue fuzzing. This might (and often times will) result in corpus inputs that Jazzer's mutation framework was not designed to deal with. Most bugs come from the protobuf mutators that has no default max container size. Co-authored-by: Simon Resch --- .../mutation/ArgumentsMutatorFuzzTest.java | 272 ++++++++++++++++++ .../selffuzz/mutation/BUILD.bazel | 30 ++ .../selffuzz/mutation/BeanWithParent.java | 63 ++++ .../ConstructorPropertiesAnnotatedBean.java | 63 ++++ .../selffuzz/mutation/ImmutableBuilder.java | 77 +++++ .../mutation/annotation/proto/BUILD.bazel | 1 + .../jazzer/mutation/mutator/proto/BUILD.bazel | 6 +- 7 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java create mode 100644 selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel create mode 100644 selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BeanWithParent.java create mode 100644 selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ConstructorPropertiesAnnotatedBean.java create mode 100644 selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ImmutableBuilder.java diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java new file mode 100644 index 000000000..d95e3f60a --- /dev/null +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java @@ -0,0 +1,272 @@ +/* + * Copyright 2025 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.selffuzz.mutation; + +import static com.google.common.truth.Truth.assertThat; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.junit.FuzzTest; +import com.code_intelligence.jazzer.mutation.annotation.WithSize; +import com.code_intelligence.jazzer.mutation.annotation.WithUtf8Length; +import com.code_intelligence.jazzer.protobuf.Proto3; +import com.code_intelligence.selffuzz.jazzer.mutation.ArgumentsMutator; +import com.code_intelligence.selffuzz.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.selffuzz.jazzer.mutation.annotation.WithLength; +import com.code_intelligence.selffuzz.jazzer.mutation.mutator.Mutators; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.InputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ArgumentsMutatorFuzzTest { + static List methods = getSelfFuzzTestMethods(); + static List mutators = + methods.stream() + .map( + m -> + ArgumentsMutator.forMethod(Mutators.newFactory(), m) + .orElseThrow(() -> new IllegalArgumentException("Invalid method: " + m))) + .collect(Collectors.toList()); + + static { + System.out.println("Found " + methods.size() + " @SelfFuzzTest methods."); + for (Method method : methods) { + System.out.println(" - " + method); + } + assertThat(methods).isNotEmpty(); + } + + /** + * Second-order fuzzing of the mutation framework. Runs all fuzz tests marked by @SelfFuzzTest. We + * use FuzzedDataProvider to force the top-level fuzzer to not use the mutation framework, for + * easier debugging. + */ + @FuzzTest + void allTests(FuzzedDataProvider data) throws Throwable { + int index = data.consumeInt(0, methods.size() - 1); + Method method = methods.get(index); + ArgumentsMutator mutator = mutators.get(index); + + long seed = data.consumeLong(); + byte[] input = data.consumeRemainingAsBytes(); + + try { + mutator.init(seed); + ByteArrayOutputStream initedOut = new ByteArrayOutputStream(); + mutator.write(new DataOutputStream(initedOut)); + InputStream inited = new ByteArrayInputStream(initedOut.toByteArray()); + + mutator.read(new ByteArrayInputStream(input)); + mutator.invoke(this, true); + + mutator.mutate(seed); + ByteArrayOutputStream mutatedOut = new ByteArrayOutputStream(); + mutator.write(new DataOutputStream(mutatedOut)); + InputStream mutated = new ByteArrayInputStream(mutatedOut.toByteArray()); + + mutator.crossOver(mutated, inited, seed); + } catch (Exception e) { + throw new RuntimeException("In method: " + method, e); + } + } + + @SelfFuzzTest + void fuzzStrings( + @NotNull @WithUtf8Length(min = 5, max = 7) String s0, + @NotNull String s1, + @NotNull @WithUtf8Length(min = 10, max = 20) String s2) {} + + @SelfFuzzTest // BUG: null pointer exception + void fuzzListOfMaps(Map nullableMap) {} + + @SelfFuzzTest + void fuzzListOfLists(List<@NotNull List> nullableMap, List> nullableList) {} + + @SelfFuzzTest + void fuzzPPrimitiveArrays( + int @WithLength(max = 10) [] a0, boolean[] a2, int @WithLength(max = 8193) [] a3) {} + + @SelfFuzzTest + void fuzzBean(@NotNull ConstructorPropertiesAnnotatedBean bean, BeanWithParent beanWithParent) {} + + @SelfFuzzTest + void fuzzListOfBeans(@WithSize(max = 4) List beanWithParent) {} + + @SelfFuzzTest + void fuzzListOfListOfBeans( + @WithSize(max = 4) List<@WithSize(max = 4) List> beanWithParent) {} + + @SelfFuzzTest + void fuzzTime(LocalDate date, LocalTime time, LocalDateTime dateTime) {} + + @SelfFuzzTest + void fuzz_Arrays( + List listOfIntArrays, + byte[] @WithLength(max = 11) [] byteArrays) {} + + @SelfFuzzTest + void fuzz_Builder( + // @NotNull // BUG: @NotNull causes "Index -1 out of bounds for length 0" + // in InPlaceProductMutator.writeExclusive + ImmutableBuilder b) {} + + @SelfFuzzTest + void fuzzPrimitives( + Integer i0, + int i1, + Boolean b0, + boolean b1, + Double d0, + double d1, + Float f0, + float f1, + Long l0, + long l1, + Byte by0, + byte by1, + Short s0, + short s1) {} + + @SelfFuzzTest + void fuzzPrimitivesNotNull( + @NotNull Integer i0, + int i1, + @NotNull Boolean b0, + boolean b1, + @NotNull Double d0, + double d1, + @NotNull Float f0, + float f1, + @NotNull Long l0, + long l1, + @NotNull Byte by0, + byte by1, + @NotNull Short s0, + short s1) {} + + @SelfFuzzTest + void fuzzPrimitiveArrays( + Integer @WithLength(max = 3) [] i0, + int[] i1, + Boolean @WithLength(max = 3) [] b0, + boolean[] b1, + Double @WithLength(max = 3) [] d0, + double[] d1, + Float @WithLength(max = 3) [] f0, + float[] f1, + Long @WithLength(max = 3) [] l0, + long[] l1, + Byte @WithLength(max = 3) [] by0, + byte[] by1, + Short @WithLength(max = 3) [] s0, + short[] s1) {} + + enum MyEnum { + A, + B, + C, + D, + E, + F, + G + } + + @SelfFuzzTest + void fuzz_Enums(MyEnum e0, MyEnum e1, MyEnum e2) {} + + @SelfFuzzTest + void fuzz_ProtoBufs( + // com.google.protobuf.StringValue v0, // BUG: makes maxIncreaseSize negative in + // LibProtobufMutator.mutate + com.google.protobuf.Int32Value v1, + com.google.protobuf.BoolValue v2, + com.google.protobuf.UInt64Value v3, + com.google.protobuf.FloatValue v4, + com.google.protobuf.DoubleValue v5, + // com.google.protobuf.BytesValue v6, // BUG: makes maxIncreaseSize negative in + // LibProtobufMutator.mutate + com.google.protobuf.Int64Value v7) { + if (v7 != null) { + assertThat(v7.getValue()).isAtLeast(Long.MIN_VALUE); + assertThat(v7.getValue()).isAtMost(Long.MAX_VALUE); + } + } + + @SelfFuzzTest + void fuzz_ProtoBufsNotNull( + // @NotNull com.google.protobuf.StringValue v0, // BUG: makes maxIncreaseSize negative in + // LibProtobufMutator.mutate + @NotNull com.google.protobuf.Int32Value v1, + @NotNull com.google.protobuf.BoolValue v2, + @NotNull com.google.protobuf.UInt64Value v3, + @NotNull com.google.protobuf.FloatValue v4, + @NotNull com.google.protobuf.DoubleValue v5, + // @NotNull com.google.protobuf.BytesValue v6, // BUG: makes maxIncreaseSize negative in + // LibProtobufMutator.mutate + @NotNull com.google.protobuf.Int64Value v7) { + if (v7 != null) { + assertThat(v7.getValue()).isAtLeast(Long.MIN_VALUE); + assertThat(v7.getValue()).isAtMost(Long.MAX_VALUE); + } + } + + // BUG: makes maxIncreaseSize negative in LibProtobufMutator.mutate + // @SelfFuzzTest + // public static void fuzz_TestProtobuf(TestProtobuf o1) {} + + @SelfFuzzTest + void fuzz_MapField3(Proto3.MapField3 o1) {} + + // BUG: causes java.lang.IllegalArgumentException: argument type mismatch + // no problem when testing the two types separately + // @SelfFuzzTest + // public static void fuzz_MutuallyReferringProtobufs( + // Proto2.TestProtobuf o1, Proto2.TestSubProtobuf o2) {} + + /** + * @return all methods in this class annotated by @SelfFuzzTest. If any of those methods are + * annotated by @Solo, only those are returned. + */ + private static List getSelfFuzzTestMethods() { + return Arrays.stream(MethodHandles.lookup().lookupClass().getDeclaredMethods()) + .filter(m -> m.isAnnotationPresent(SelfFuzzTest.class)) + .collect( + Collectors.collectingAndThen( + Collectors.partitioningBy(m -> m.isAnnotationPresent(Solo.class)), + // Return @Solo methods if any, otherwise all @SelfFuzzTest methods. + map -> map.get(true).isEmpty() ? map.get(false) : map.get(true))); + } + + /** Every method (public or private) annotated by @SelfFuzzTest will be fuzzed. */ + @Retention(RetentionPolicy.RUNTIME) + public @interface SelfFuzzTest {} + + /** When debugging, annotate @SelfFuzzTest fuzz tests by @Solo to only run those. */ + @Retention(RetentionPolicy.RUNTIME) + public @interface Solo {} +} diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel new file mode 100644 index 000000000..6c5da6719 --- /dev/null +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel @@ -0,0 +1,30 @@ +load("//bazel:fuzz_target.bzl", "java_fuzz_target_test") + +java_fuzz_target_test( + name = "ArgumentsMutatorFuzzTest", + srcs = [ + "ArgumentsMutatorFuzzTest.java", + "BeanWithParent.java", + "ConstructorPropertiesAnnotatedBean.java", + "ImmutableBuilder.java", + ], + fuzzer_args = [ + # Make sure that the fuzzer can run. Longer fuzzing runs will be done in a separate GH action. + "-runs=10000", + ], + target_class = "com.code_intelligence.selffuzz.mutation.ArgumentsMutatorFuzzTest", + deps = [ + "//deploy:jazzer-project", + "//selffuzz:jazzer_api_selffuzz", + "//selffuzz:jazzer_selffuzz", + "//src/main/java/com/code_intelligence/jazzer/junit:fuzz_test", + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto:protobuf_runtime_compile_only", + "//src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto:proto2_java_proto", + "//src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto:proto3_java_proto", + "@maven//:com_google_truth_truth", + "@maven//:org_junit_jupiter_junit_jupiter_api", + "@maven//:org_junit_jupiter_junit_jupiter_engine", + "@maven//:org_junit_platform_junit_platform_launcher", + "@protobuf//java/core", + ], +) diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BeanWithParent.java b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BeanWithParent.java new file mode 100644 index 000000000..6e996ef4f --- /dev/null +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BeanWithParent.java @@ -0,0 +1,63 @@ +/* + * Copyright 2025 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.selffuzz.mutation; + +import java.beans.ConstructorProperties; +import java.util.Objects; + +public class BeanWithParent extends ConstructorPropertiesAnnotatedBean { + protected int quz; + + @ConstructorProperties({"foo", "BAR", "baz", "quz"}) + BeanWithParent(boolean a, String b, int c, int q) { + super(a, b, c); + this.quz = q; + } + + public int getQuz() { + return quz; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + BeanWithParent that = (BeanWithParent) o; + return quz == that.quz; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), quz); + } + + @Override + public String toString() { + return "BeanWithParent{" + + "quz=" + + quz + + ", foo=" + + isFoo() + + ", bar='" + + getBAR() + + '\'' + + ", baz=" + + getBaz() + + '}'; + } +} diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ConstructorPropertiesAnnotatedBean.java b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ConstructorPropertiesAnnotatedBean.java new file mode 100644 index 000000000..c9a1faf31 --- /dev/null +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ConstructorPropertiesAnnotatedBean.java @@ -0,0 +1,63 @@ +/* + * Copyright 2025 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.selffuzz.mutation; + +import java.beans.ConstructorProperties; +import java.util.Objects; + +public class ConstructorPropertiesAnnotatedBean { + private final boolean foo; + private final String bar; + private final int baz; + + @ConstructorProperties({"foo", "BAR", "baz"}) + ConstructorPropertiesAnnotatedBean(boolean a, String b, int c) { + this.foo = a; + this.bar = b; + this.baz = c; + } + + public boolean isFoo() { + return foo; + } + + public String getBAR() { + return bar; + } + + int getBaz() { + return baz; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConstructorPropertiesAnnotatedBean that = (ConstructorPropertiesAnnotatedBean) o; + return foo == that.foo && baz == that.baz && Objects.equals(bar, that.bar); + } + + @Override + public int hashCode() { + return Objects.hash(foo, bar, baz); + } + + @Override + public String toString() { + return "SimpleTypeBean{" + "foo=" + foo + ", bar='" + bar + '\'' + ", baz=" + baz + '}'; + } +} diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ImmutableBuilder.java b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ImmutableBuilder.java new file mode 100644 index 000000000..afdf894df --- /dev/null +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ImmutableBuilder.java @@ -0,0 +1,77 @@ +/* + * Copyright 2025 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.selffuzz.mutation; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class ImmutableBuilder { + private final int i; + private final boolean b; + private final List list; + + public ImmutableBuilder() { + this(0, false, Collections.emptyList()); + } + + private ImmutableBuilder(int i, boolean b, List list) { + this.i = i; + this.b = b; + this.list = list; + } + + public int getI() { + return i; + } + + public boolean isB() { + return b; + } + + public ImmutableBuilder withI(int i) { + return new ImmutableBuilder(i, b, list); + } + + // Both withX and setX are supported on immutable builders. + public ImmutableBuilder setB(boolean b) { + return new ImmutableBuilder(i, b, list); + } + + public ImmutableBuilder setList(List list) { + return new ImmutableBuilder(i, b, list); + } + + @Override + @SuppressWarnings("PatternVariableCanBeUsed") + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ImmutableBuilder)) return false; + ImmutableBuilder that = (ImmutableBuilder) o; + return i == that.i && b == that.b; + } + + @Override + public int hashCode() { + return Objects.hash(i, b); + } + + @Override + public String toString() { + return "ImmutableBuilder{" + "i=" + i + ", b=" + b + '}'; + } +} diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto/BUILD.bazel index 38b469267..0a68b420c 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto/BUILD.bazel @@ -16,6 +16,7 @@ java_library( # supporting proto mutations. neverlink = True, visibility = [ + "//selffuzz:__pkg__", "//selffuzz:__subpackages__", "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto:__pkg__", ], diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel index 5c8545753..17081d854 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/proto/BUILD.bazel @@ -11,7 +11,10 @@ proto_library( java_proto_library( name = "proto3_java_proto", testonly = True, - visibility = ["//src/test/java/com/code_intelligence/jazzer/mutation/mutator:__pkg__"], + visibility = [ + "//selffuzz:__subpackages__", + "//src/test/java/com/code_intelligence/jazzer/mutation/mutator:__pkg__", + ], deps = [":proto3_proto"], ) @@ -24,6 +27,7 @@ java_proto_library( name = "proto2_java_proto", testonly = True, visibility = [ + "//selffuzz:__subpackages__", "//src/test/java/com/code_intelligence/jazzer/mutation/mutator:__pkg__", "//tests:__pkg__", ], From 2d6067e0aa6a41de4861b1a52d1c819f43a90a6a Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Tue, 23 Sep 2025 10:10:18 +0200 Subject: [PATCH 3/5] chore: use fuzz test corpus in regression mode Co-authored-by: Simon Resch --- selffuzz/.gitignore | 1 - .../java/com/code_intelligence/selffuzz/mutation/BUILD.bazel | 2 ++ .../src/test/resources/.corpus/ArgumentsMutatorFuzzTest/seed | 1 + selffuzz/src/test/resources/.gitignore | 1 + selffuzz/src/test/resources/BUILD.bazel | 5 +++++ 5 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 selffuzz/src/test/resources/.corpus/ArgumentsMutatorFuzzTest/seed create mode 100644 selffuzz/src/test/resources/.gitignore create mode 100644 selffuzz/src/test/resources/BUILD.bazel diff --git a/selffuzz/.gitignore b/selffuzz/.gitignore index 20978dd86..0e0c3777a 100644 --- a/selffuzz/.gitignore +++ b/selffuzz/.gitignore @@ -2,4 +2,3 @@ .cifuzz-corpus/ .cifuzz-findings/ target/ -src/test/resources/ \ No newline at end of file diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel index 6c5da6719..9824d1592 100644 --- a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel @@ -8,9 +8,11 @@ java_fuzz_target_test( "ConstructorPropertiesAnnotatedBean.java", "ImmutableBuilder.java", ], + data = ["//selffuzz/src/test/resources:ArgumentsMutatorFuzzTest-corpus"], fuzzer_args = [ # Make sure that the fuzzer can run. Longer fuzzing runs will be done in a separate GH action. "-runs=10000", + "$(rlocationpaths //selffuzz/src/test/resources:ArgumentsMutatorFuzzTest-corpus)", ], target_class = "com.code_intelligence.selffuzz.mutation.ArgumentsMutatorFuzzTest", deps = [ diff --git a/selffuzz/src/test/resources/.corpus/ArgumentsMutatorFuzzTest/seed b/selffuzz/src/test/resources/.corpus/ArgumentsMutatorFuzzTest/seed new file mode 100644 index 000000000..74e0f12e3 --- /dev/null +++ b/selffuzz/src/test/resources/.corpus/ArgumentsMutatorFuzzTest/seed @@ -0,0 +1 @@ +! \ No newline at end of file diff --git a/selffuzz/src/test/resources/.gitignore b/selffuzz/src/test/resources/.gitignore new file mode 100644 index 000000000..075ae33ee --- /dev/null +++ b/selffuzz/src/test/resources/.gitignore @@ -0,0 +1 @@ +.corpus/ diff --git a/selffuzz/src/test/resources/BUILD.bazel b/selffuzz/src/test/resources/BUILD.bazel new file mode 100644 index 000000000..aee3fdbdb --- /dev/null +++ b/selffuzz/src/test/resources/BUILD.bazel @@ -0,0 +1,5 @@ +filegroup( + name = "ArgumentsMutatorFuzzTest-corpus", + srcs = [".corpus/ArgumentsMutatorFuzzTest"], + visibility = ["//selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation:__pkg__"], +) From cc89824a8db143cdd2267bfcd3e374f3b7e947a9 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Mon, 22 Sep 2025 15:01:59 +0200 Subject: [PATCH 4/5] chore: add a fuzzing workflow --- .github/workflows/fuzzing.yml | 73 +++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .github/workflows/fuzzing.yml diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml new file mode 100644 index 000000000..ea8d17cd1 --- /dev/null +++ b/.github/workflows/fuzzing.yml @@ -0,0 +1,73 @@ +name: Fuzzing +on: + schedule: + # At 03:00 UTC (01:00 CEST) every day + - cron: '0 3 * * *' + + merge_group: + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + + build_and_test: + runs-on: ${{ matrix.os }} + name: Fuzzing + strategy: + matrix: + os: [ubuntu-22.04, macos-14] + jdk: [21, 8] + include: + - jdk: 21 + # Workaround for https://github.com/bazelbuild/bazel/issues/14502 + extra_bazel_args: "--jvmopt=-Djava.security.manager=allow" + - os: ubuntu-22.04 + arch: "linux" + - os: macos-14 + arch: "macos-arm64" + bazel_args: "--xcode_version_config=//.github:host_xcodes" + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 21 + + - name: Set Build Buddy config + run: .github/scripts/echoBuildBuddyConfig.sh ${{ secrets.BUILDBUDDY_API_KEY }} >> $GITHUB_ENV + shell: bash + + - name: Cache Fuzzing Corpus + uses: actions/cache@v4 + with: + path: | + selffuzz/src/test/resources/.corpus + + key: fuzzing-corpus-${{ matrix.os }}-${{ matrix.jdk }}-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: | + fuzzing-corpus- + + - name: Build & Fuzz + run: | + bazelisk run ${{env.BUILD_BUDDY_CONFIG}} --java_runtime_version=remotejdk_${{ matrix.jdk }} ${{ matrix.bazel_args }} ${{ matrix.extra_bazel_args }} //selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation:ArgumentsMutatorFuzzTest --jvmopt=-Xmx10000m -- -runs=1000000 + + # Notification job that runs after all matrix jobs complete + notification: + needs: build_and_test + runs-on: ubuntu-24.04 + if: failure() # Run regardless of build_and_test outcome + steps: + - name: Slack notification on failure + run: | + curl -X POST -H 'Content-type: application/json' \ + --data '{ + "workflow_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + }' \ + ${{ secrets.SLACK_WEBHOOK_URL }} From da265ca314c30421091a0023b4404d7b8a10c827 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Tue, 23 Sep 2025 11:47:02 +0200 Subject: [PATCH 5/5] chore: add corpus cache for regression testing --- .github/workflows/run-all-tests-pr.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/run-all-tests-pr.yml b/.github/workflows/run-all-tests-pr.yml index 8efd5e238..68a17a067 100644 --- a/.github/workflows/run-all-tests-pr.yml +++ b/.github/workflows/run-all-tests-pr.yml @@ -72,6 +72,16 @@ jobs: choco install llvm --version=19.1.0 --force echo "C:\Program Files\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Load fuzzing corpus cache + uses: actions/cache/restore@v4 + with: + path: | + selffuzz/src/test/resources/.corpus + + key: fuzzing-corpus-${{ matrix.os }}-${{ matrix.jdk }}-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: | + fuzzing-corpus- + - name: Build & Test run: bazelisk test ${{env.BUILD_BUDDY_CONFIG}} --java_runtime_version=remotejdk_${{ matrix.jdk }} ${{ matrix.bazel_args }} ${{ matrix.extra_bazel_args }} --build_tag_filters="-no-${{ matrix.arch }},-no-${{ matrix.arch }}-jdk${{ matrix.jdk }},-no-jdk${{ matrix.jdk }}" --test_tag_filters="-no-${{ matrix.arch }},-no-${{ matrix.arch }}-jdk${{ matrix.jdk }},-no-jdk${{ matrix.jdk }}" //...