aboutsummaryrefslogtreecommitdiff
path: root/agent/src/test/java/com/code_intelligence/jazzer
diff options
context:
space:
mode:
Diffstat (limited to 'agent/src/test/java/com/code_intelligence/jazzer')
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java107
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel21
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel69
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java103
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java111
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java147
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java43
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java85
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel5
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java56
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java87
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt63
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java85
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java29
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel147
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java53
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt63
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java61
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java25
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java41
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java67
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt141
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt73
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java21
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt38
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java61
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java45
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java106
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt53
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java109
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt63
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java120
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java23
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java152
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt145
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java49
36 files changed, 2667 insertions, 0 deletions
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java
new file mode 100644
index 00000000..66a85db6
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java
@@ -0,0 +1,107 @@
+// 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.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Test;
+
+public class AutofuzzTest {
+ public interface UnimplementedInterface {}
+
+ public interface ImplementedInterface {}
+ public static class ImplementingClass implements ImplementedInterface {}
+
+ private static boolean implIsNotNull(ImplementedInterface impl) {
+ return impl != null;
+ }
+
+ private static boolean implIsNotNull(UnimplementedInterface impl) {
+ return impl != null;
+ }
+
+ private static void checkAllTheArguments(
+ String arg1, int arg2, byte arg3, ImplementedInterface arg4) {
+ if (!arg1.equals("foobar") || arg2 != 42 || arg3 != 5 || arg4 == null) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Test
+ public void testConsume() {
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(
+ Arrays.asList((byte) 1 /* do not return null */, 0 /* first class on the classpath */,
+ (byte) 1 /* do not return null */, 0 /* first constructor */));
+ ImplementedInterface result = Jazzer.consume(data, ImplementedInterface.class);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testConsumeFailsWithoutException() {
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(Collections.singletonList(
+ (byte) 1 /* do not return null without searching for implementing classes */));
+ assertNull(Jazzer.consume(data, UnimplementedInterface.class));
+ }
+
+ @Test
+ public void testAutofuzz() {
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(
+ Arrays.asList((byte) 1 /* do not return null */, 0 /* first class on the classpath */,
+ (byte) 1 /* do not return null */, 0 /* first constructor */));
+ assertEquals(Boolean.TRUE,
+ Jazzer.autofuzz(data, (Function1<ImplementedInterface, ?>) AutofuzzTest::implIsNotNull));
+ }
+
+ @Test
+ public void testAutofuzzFailsWithException() {
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(
+ Collections.singletonList((byte) 1 /* do not return null */));
+ try {
+ Jazzer.autofuzz(data, (Function1<UnimplementedInterface, ?>) AutofuzzTest::implIsNotNull);
+ } catch (AutofuzzConstructionException e) {
+ // Pass.
+ return;
+ }
+ fail("should have thrown an AutofuzzConstructionException");
+ }
+
+ @Test
+ public void testAutofuzzConsumer() {
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(
+ Arrays.asList((byte) 1 /* do not return null */, 6 /* string length */, "foobar", 42,
+ (byte) 5, (byte) 1 /* do not return null */, 0 /* first class on the classpath */,
+ (byte) 1 /* do not return null */, 0 /* first constructor */));
+ Jazzer.autofuzz(data, AutofuzzTest::checkAllTheArguments);
+ }
+
+ @Test
+ public void testAutofuzzConsumerThrowsException() {
+ FuzzedDataProvider data =
+ CannedFuzzedDataProvider.create(Arrays.asList((byte) 1 /* do not return null */,
+ 6 /* string length */, "foobar", 42, (byte) 5, (byte) 0 /* *do* return null */));
+ try {
+ Jazzer.autofuzz(data, AutofuzzTest::checkAllTheArguments);
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ return;
+ }
+ fail("should have thrown an IllegalArgumentException");
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel
new file mode 100644
index 00000000..9192ff77
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel
@@ -0,0 +1,21 @@
+java_test(
+ name = "AutofuzzTest",
+ size = "small",
+ srcs = [
+ "AutofuzzTest.java",
+ ],
+ env = {
+ # Also consider implementing classes from com.code_intelligence.jazzer.*.
+ "JAZZER_AUTOFUZZ_TESTING": "1",
+ },
+ test_class = "com.code_intelligence.jazzer.api.AutofuzzTest",
+ runtime_deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz",
+ # Needed for JazzerInternal.
+ "//agent/src/main/java/com/code_intelligence/jazzer/runtime",
+ ],
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "@maven//:junit_junit",
+ ],
+)
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
new file mode 100644
index 00000000..f8448f01
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel
@@ -0,0 +1,69 @@
+java_test(
+ name = "MetaTest",
+ size = "small",
+ srcs = [
+ "MetaTest.java",
+ ],
+ 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",
+ "@maven//:junit_junit",
+ ],
+)
+
+java_test(
+ name = "InterfaceCreationTest",
+ size = "small",
+ srcs = [
+ "InterfaceCreationTest.java",
+ ],
+ env = {
+ # Also consider implementing classes from com.code_intelligence.jazzer.*.
+ "JAZZER_AUTOFUZZ_TESTING": "1",
+ },
+ test_class = "com.code_intelligence.jazzer.autofuzz.InterfaceCreationTest",
+ deps = [
+ ":test_helpers",
+ "@maven//:junit_junit",
+ ],
+)
+
+java_test(
+ name = "BuilderPatternTest",
+ size = "small",
+ srcs = [
+ "BuilderPatternTest.java",
+ ],
+ test_class = "com.code_intelligence.jazzer.autofuzz.BuilderPatternTest",
+ deps = [
+ ":test_helpers",
+ "@maven//:junit_junit",
+ ],
+)
+
+java_test(
+ name = "SettersTest",
+ size = "small",
+ srcs = [
+ "SettersTest.java",
+ ],
+ 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",
+ "@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..a602d712
--- /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 com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase;
+
+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 {
+ @Test
+ public void testBuilderPattern() {
+ 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
new file mode 100644
index 00000000..4d85ca6c
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java
@@ -0,0 +1,111 @@
+// 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 com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase;
+
+import java.util.Arrays;
+import java.util.Objects;
+import org.junit.Test;
+
+interface InterfaceA {
+ void foo();
+
+ void bar();
+}
+
+abstract class ClassA1 implements InterfaceA {
+ @Override
+ public void foo() {}
+}
+
+class ClassB1 extends ClassA1 {
+ int n;
+
+ public ClassB1(int _n) {
+ n = _n;
+ }
+
+ @Override
+ public void bar() {}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ClassB1 classB1 = (ClassB1) o;
+ return n == classB1.n;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(n);
+ }
+}
+
+class ClassB2 implements InterfaceA {
+ String s;
+
+ public ClassB2(String _s) {
+ s = _s;
+ }
+
+ @Override
+ public void foo() {}
+
+ @Override
+ public void bar() {}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ClassB2 classB2 = (ClassB2) o;
+ return Objects.equals(s, classB2.s);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(s);
+ }
+}
+
+public class InterfaceCreationTest {
+ @Test
+ public void testConsumeInterface() {
+ 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
new file mode 100644
index 00000000..0615e9ae
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java
@@ -0,0 +1,147 @@
+// 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 com.code_intelligence.jazzer.autofuzz.TestHelpers.autofuzzTestCase;
+import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase;
+import static org.junit.Assert.assertEquals;
+
+import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider;
+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;
+
+public class MetaTest {
+ public static boolean isFive(int arg) {
+ return arg == 5;
+ }
+
+ public static boolean intEquals(int arg1, int arg2) {
+ return arg1 == arg2;
+ }
+
+ public enum TestEnum {
+ FOO,
+ BAR,
+ BAZ,
+ }
+
+ @Test
+ public void testConsume() {
+ 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() 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));
+ }
+}
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
new file mode 100644
index 00000000..7c869531
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java
@@ -0,0 +1,43 @@
+// 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 com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase;
+
+import com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class SettersTest {
+ @Test
+ public void testEmptyConstructorWithSetters() {
+ EmployeeWithSetters employee = new EmployeeWithSetters();
+ employee.setFirstName("foo");
+ employee.setAge(26);
+
+ 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;
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel
new file mode 100644
index 00000000..c2c68803
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel
@@ -0,0 +1,5 @@
+java_library(
+ name = "test_data",
+ srcs = glob(["*.java"]),
+ visibility = ["//visibility:public"],
+)
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java
new file mode 100644
index 00000000..2c76a61f
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java
@@ -0,0 +1,56 @@
+// 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.testdata;
+
+import java.util.Objects;
+
+public class EmployeeWithSetters {
+ private String firstName;
+ private String lastName;
+ private String jobTitle;
+ private int age;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ EmployeeWithSetters hero = (EmployeeWithSetters) 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);
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public void setJobTitle(String jobTitle) {
+ this.jobTitle = jobTitle;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java
new file mode 100644
index 00000000..f8d6782c
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java
@@ -0,0 +1,87 @@
+// 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.instrumentor;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+public class AfterHooks {
+ static AfterHooksTargetContract instance;
+
+ @MethodHook(type = HookType.AFTER,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.AfterHooksTarget",
+ targetMethod = "func1")
+ public static void
+ patchFunc1(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
+ instance = (AfterHooksTargetContract) thisObject;
+ ((AfterHooksTargetContract) thisObject).registerHasFunc1BeenCalled();
+ }
+
+ @MethodHook(type = HookType.AFTER,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.AfterHooksTarget",
+ targetMethod = "registerTimesCalled", targetMethodDescriptor = "()V")
+ public static void
+ patchRegisterTimesCalled(MethodHandle method, Object thisObject, Object[] arguments, int hookId,
+ Object returnValue) throws Throwable {
+ // Invoke registerTimesCalled() again to pass the test.
+ method.invoke();
+ }
+
+ @MethodHook(type = HookType.AFTER,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.AfterHooksTarget",
+ targetMethod = "getFirstSecret", targetMethodDescriptor = "()Ljava/lang/String;")
+ public static void
+ patchGetFirstSecret(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, String returnValue) {
+ // Use the returned secret to pass the test.
+ ((AfterHooksTargetContract) thisObject).verifyFirstSecret(returnValue);
+ }
+
+ @MethodHook(type = HookType.AFTER,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.AfterHooksTarget",
+ targetMethod = "getSecondSecret")
+ public static void
+ patchGetSecondSecret(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
+ // Use the returned secret to pass the test.
+ ((AfterHooksTargetContract) thisObject).verifySecondSecret((String) returnValue);
+ }
+
+ // Verify the interaction of a BEFORE and an AFTER hook. The BEFORE hook modifies the argument of
+ // the StringBuilder constructor.
+ @MethodHook(
+ type = HookType.BEFORE, targetClassName = "java.lang.StringBuilder", targetMethod = "<init>")
+ public static void
+ patchStringBuilderBeforeInit(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ arguments[0] = "hunter3";
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.StringBuilder", targetMethod = "<init>")
+ public static void
+ patchStringBuilderInit(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
+ String secret = ((StringBuilder) thisObject).toString();
+ // Verify that the argument passed to this AFTER hook agrees with the argument passed to the
+ // StringBuilder constructor, which has been modified by the BEFORE hook.
+ if (secret.equals(arguments[0])) {
+ // Verify that the argument has been modified to the correct value "hunter3".
+ instance.verifyThirdSecret(secret);
+ }
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt
new file mode 100644
index 00000000..53efd200
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt
@@ -0,0 +1,63 @@
+// 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.instrumentor
+
+import org.junit.Test
+import java.io.File
+
+private fun applyAfterHooks(bytecode: ByteArray): ByteArray {
+ return HookInstrumentor(loadHooks(AfterHooks::class.java), false).instrument(bytecode)
+}
+
+private fun getOriginalAfterHooksTargetInstance(): AfterHooksTargetContract {
+ return AfterHooksTarget()
+}
+
+private fun getNoHooksAfterHooksTargetInstance(): AfterHooksTargetContract {
+ val originalBytecode = classToBytecode(AfterHooksTarget::class.java)
+ // Let the bytecode pass through the hooking logic, but don't apply any hooks.
+ val patchedBytecode = HookInstrumentor(emptyList(), false).instrument(originalBytecode)
+ val patchedClass = bytecodeToClass(AfterHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as AfterHooksTargetContract
+}
+
+private fun getPatchedAfterHooksTargetInstance(): AfterHooksTargetContract {
+ val originalBytecode = classToBytecode(AfterHooksTarget::class.java)
+ val patchedBytecode = applyAfterHooks(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${AfterHooksTarget::class.java.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${AfterHooksTarget::class.java.simpleName}.patched.class").writeBytes(patchedBytecode)
+ val patchedClass = bytecodeToClass(AfterHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as AfterHooksTargetContract
+}
+
+class AfterHookTest {
+
+ @Test
+ fun testAfterHooksOriginal() {
+ assertSelfCheck(getOriginalAfterHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testAfterHooksNoHooks() {
+ assertSelfCheck(getNoHooksAfterHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testAfterHooksPatched() {
+ assertSelfCheck(getPatchedAfterHooksTargetInstance(), true)
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java
new file mode 100644
index 00000000..a47b03a5
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.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.instrumentor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// selfCheck() only passes with the hooks in AfterHooks.java applied.
+public class AfterHooksTarget implements AfterHooksTargetContract {
+ static Map<String, Boolean> results = new HashMap<>();
+ static int timesCalled = 0;
+ Boolean func1Called = false;
+
+ public static void registerTimesCalled() {
+ timesCalled++;
+ results.put("hasBeenCalledTwice", timesCalled == 2);
+ }
+
+ public Map<String, Boolean> selfCheck() {
+ results = new HashMap<>();
+
+ if (results.isEmpty()) {
+ registerHasFunc1BeenCalled();
+ func1();
+ }
+
+ timesCalled = 0;
+ registerTimesCalled();
+
+ verifyFirstSecret("not_secret");
+ getFirstSecret();
+
+ verifySecondSecret("not_secret_at_all");
+ getSecondSecret();
+
+ verifyThirdSecret("not_the_secret");
+ new StringBuilder("not_hunter3");
+
+ return results;
+ }
+
+ public void func1() {
+ func1Called = true;
+ }
+
+ public void registerHasFunc1BeenCalled() {
+ results.put("hasFunc1BeenCalled", func1Called);
+ }
+
+ @SuppressWarnings("UnusedReturnValue")
+ String getFirstSecret() {
+ return "hunter2";
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ public void verifyFirstSecret(String secret) {
+ results.put("verifyFirstSecret", secret.equals("hunter2"));
+ }
+
+ @SuppressWarnings("UnusedReturnValue")
+ String getSecondSecret() {
+ return "hunter2!";
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ public void verifySecondSecret(String secret) {
+ results.put("verifySecondSecret", secret.equals("hunter2!"));
+ }
+
+ public void verifyThirdSecret(String secret) {
+ results.put("verifyThirdSecret", secret.equals("hunter3"));
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java
new file mode 100644
index 00000000..cb12b148
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java
@@ -0,0 +1,29 @@
+// 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.instrumentor;
+
+/**
+ * Helper interface used to call methods on instances of AfterHooksTarget classes loaded via
+ * different class loaders.
+ */
+public interface AfterHooksTargetContract extends DynamicTestContract {
+ void registerHasFunc1BeenCalled();
+
+ void verifyFirstSecret(String secret);
+
+ void verifySecondSecret(String secret);
+
+ void verifyThirdSecret(String secret);
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
new file mode 100644
index 00000000..472d2b98
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
@@ -0,0 +1,147 @@
+load("//bazel:kotlin.bzl", "wrapped_kt_jvm_test")
+load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
+
+kt_jvm_library(
+ name = "patch_test_utils",
+ srcs = [
+ "DynamicTestContract.java",
+ "PatchTestUtils.kt",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "trace_data_flow_instrumentation_test",
+ size = "small",
+ srcs = [
+ "MockTraceDataFlowCallbacks.java",
+ "TraceDataFlowInstrumentationTarget.java",
+ "TraceDataFlowInstrumentationTest.kt",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.TraceDataFlowInstrumentationTest",
+ deps = [
+ ":patch_test_utils",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "coverage_instrumentation_test",
+ size = "small",
+ srcs = [
+ "CoverageInstrumentationSpecialCasesTarget.java",
+ "CoverageInstrumentationTarget.java",
+ "CoverageInstrumentationTest.kt",
+ "MockCoverageMap.java",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.CoverageInstrumentationTest",
+ deps = [
+ ":patch_test_utils",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "descriptor_utils_test",
+ size = "small",
+ srcs = [
+ "DescriptorUtilsTest.kt",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.DescriptorUtilsTest",
+ deps = [
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "hook_validation_test",
+ size = "small",
+ srcs = [
+ "HookValidationTest.kt",
+ "InvalidHookMocks.java",
+ "ValidHookMocks.java",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.HookValidationTest",
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "after_hooks_patch_test",
+ size = "small",
+ srcs = [
+ "AfterHooks.java",
+ "AfterHooksPatchTest.kt",
+ "AfterHooksTarget.java",
+ "AfterHooksTargetContract.java",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.AfterHookTest",
+ deps = [
+ ":patch_test_utils",
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "before_hooks_patch_test",
+ size = "small",
+ srcs = [
+ "BeforeHooks.java",
+ "BeforeHooksPatchTest.kt",
+ "BeforeHooksTarget.java",
+ "BeforeHooksTargetContract.java",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.BeforeHookTest",
+ deps = [
+ ":patch_test_utils",
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "replace_hooks_patch_test",
+ size = "small",
+ srcs = [
+ "ReplaceHooks.java",
+ "ReplaceHooksPatchTest.kt",
+ "ReplaceHooksTarget.java",
+ "ReplaceHooksTargetContract.java",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.ReplaceHookTest",
+ deps = [
+ ":patch_test_utils",
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java
new file mode 100644
index 00000000..31577dad
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java
@@ -0,0 +1,53 @@
+// 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.instrumentor;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+public class BeforeHooks {
+ @MethodHook(type = HookType.BEFORE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.BeforeHooksTarget",
+ targetMethod = "hasFunc1BeenCalled", targetMethodDescriptor = "()Z")
+ public static void
+ patchHasFunc1BeenCalled(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ ((BeforeHooksTargetContract) thisObject).func1();
+ }
+
+ @MethodHook(type = HookType.BEFORE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.BeforeHooksTarget",
+ targetMethod = "getTimesCalled", targetMethodDescriptor = "()Ljava/lang/Integer;")
+ public static void
+ patchHasBeenCalled(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
+ throws Throwable {
+ // Invoke static method getTimesCalled() again to pass the test.
+ method.invoke();
+ }
+
+ @MethodHook(type = HookType.BEFORE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.BeforeHooksTarget",
+ targetMethod = "hasFuncWithArgsBeenCalled")
+ public static void
+ patchHasFuncWithArgsBeenCalled(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ if (arguments.length == 2 && arguments[0] instanceof Boolean
+ && arguments[1] instanceof String) {
+ // only if the arguments passed to the hook match the expected argument types and count invoke
+ // the method to pass the test
+ ((BeforeHooksTargetContract) thisObject).setFuncWithArgsCalled((Boolean) arguments[0]);
+ }
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt
new file mode 100644
index 00000000..31e9733c
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt
@@ -0,0 +1,63 @@
+// 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.instrumentor
+
+import org.junit.Test
+import java.io.File
+
+private fun applyBeforeHooks(bytecode: ByteArray): ByteArray {
+ return HookInstrumentor(loadHooks(BeforeHooks::class.java), false).instrument(bytecode)
+}
+
+private fun getOriginalBeforeHooksTargetInstance(): BeforeHooksTargetContract {
+ return BeforeHooksTarget()
+}
+
+private fun getNoHooksBeforeHooksTargetInstance(): BeforeHooksTargetContract {
+ val originalBytecode = classToBytecode(BeforeHooksTarget::class.java)
+ // Let the bytecode pass through the hooking logic, but don't apply any hooks.
+ val patchedBytecode = HookInstrumentor(emptyList(), false).instrument(originalBytecode)
+ val patchedClass = bytecodeToClass(BeforeHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as BeforeHooksTargetContract
+}
+
+private fun getPatchedBeforeHooksTargetInstance(): BeforeHooksTargetContract {
+ val originalBytecode = classToBytecode(BeforeHooksTarget::class.java)
+ val patchedBytecode = applyBeforeHooks(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${BeforeHooksTarget::class.java.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${BeforeHooksTarget::class.java.simpleName}.patched.class").writeBytes(patchedBytecode)
+ val patchedClass = bytecodeToClass(BeforeHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as BeforeHooksTargetContract
+}
+
+class BeforeHookTest {
+
+ @Test
+ fun testBeforeHooksOriginal() {
+ assertSelfCheck(getOriginalBeforeHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testBeforeHooksNoHooks() {
+ assertSelfCheck(getNoHooksBeforeHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testBeforeHooksPatched() {
+ assertSelfCheck(getPatchedBeforeHooksTargetInstance(), true)
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java
new file mode 100644
index 00000000..869e04bf
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java
@@ -0,0 +1,61 @@
+// 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.instrumentor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// selfCheck() only passes with the hooks in BeforeHooks.java applied.
+public class BeforeHooksTarget implements BeforeHooksTargetContract {
+ static private int timesCalled = 0;
+ Map<String, Boolean> results = new HashMap<>();
+ Boolean func1Called = false;
+ Boolean funcWithArgsCalled = false;
+
+ static Integer getTimesCalled() {
+ return ++timesCalled;
+ }
+
+ public Map<String, Boolean> selfCheck() {
+ results = new HashMap<>();
+
+ results.put("hasFunc1BeenCalled", hasFunc1BeenCalled());
+
+ timesCalled = 0;
+ results.put("hasBeenCalledTwice", getTimesCalled() == 2);
+
+ if (!results.containsKey("hasBeenCalledWithArgs")) {
+ results.put("hasBeenCalledWithArgs", hasFuncWithArgsBeenCalled(true, "foo"));
+ }
+
+ return results;
+ }
+
+ public void func1() {
+ func1Called = true;
+ }
+
+ private boolean hasFunc1BeenCalled() {
+ return func1Called;
+ }
+
+ public void setFuncWithArgsCalled(Boolean val) {
+ funcWithArgsCalled = val;
+ }
+
+ private boolean hasFuncWithArgsBeenCalled(Boolean boolArgument, String stringArgument) {
+ return funcWithArgsCalled;
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java
new file mode 100644
index 00000000..61f79dcc
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java
@@ -0,0 +1,25 @@
+// 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.instrumentor;
+
+/**
+ * Helper interface used to call methods on instances of BeforeHooksTarget classes loaded via
+ * different class loaders.
+ */
+public interface BeforeHooksTargetContract extends DynamicTestContract {
+ void func1();
+
+ void setFuncWithArgsCalled(Boolean val);
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java
new file mode 100644
index 00000000..cb811803
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java
@@ -0,0 +1,41 @@
+// 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.instrumentor;
+
+import java.util.Random;
+
+public class CoverageInstrumentationSpecialCasesTarget {
+ public ReturnClass newAfterJump() {
+ if (new Random().nextBoolean()) {
+ throw new RuntimeException("");
+ }
+ return new ReturnClass(new Random().nextBoolean() ? "foo" : "bar");
+ }
+
+ public int newAndTryCatch() {
+ new Random();
+ try {
+ new Random();
+ return 2;
+ } catch (RuntimeException e) {
+ new Random();
+ return 1;
+ }
+ }
+
+ public static class ReturnClass {
+ public ReturnClass(String content) {}
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java
new file mode 100644
index 00000000..7502481d
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java
@@ -0,0 +1,67 @@
+// 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.instrumentor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CoverageInstrumentationTarget implements DynamicTestContract {
+ volatile int int1 = 3;
+ volatile int int2 = 213234;
+
+ @Override
+ public Map<String, Boolean> selfCheck() {
+ HashMap<String, Boolean> results = new HashMap<>();
+
+ results.put("for0", false);
+ results.put("for1", false);
+ results.put("for2", false);
+ results.put("for3", false);
+ results.put("for4", false);
+ results.put("foobar", false);
+ results.put("baz", true);
+
+ if (int1 < int2) {
+ results.put("block1", true);
+ } else {
+ results.put("block2", false);
+ }
+
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 5; j++) {
+ results.put("for" + j, i != 0);
+ }
+ }
+
+ foo(results);
+
+ return results;
+ }
+
+ private void foo(HashMap<String, Boolean> results) {
+ bar(results);
+ }
+
+ // The use of Map instead of HashMap is deliberate here: Since Map#put can throw exceptions, the
+ // invocation should be instrumented for coverage.
+ private void bar(Map<String, Boolean> results) {
+ results.put("foobar", true);
+ }
+
+ @SuppressWarnings("unused")
+ private void baz(HashMap<String, Boolean> results) {
+ results.put("baz", false);
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt
new file mode 100644
index 00000000..15c88f4c
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt
@@ -0,0 +1,141 @@
+// 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.instrumentor
+
+import org.junit.Test
+import java.io.File
+import kotlin.test.assertEquals
+
+private fun applyInstrumentation(bytecode: ByteArray): ByteArray {
+ return EdgeCoverageInstrumentor(0, MockCoverageMap::class.java).instrument(bytecode)
+}
+
+private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract {
+ return CoverageInstrumentationTarget()
+}
+
+private fun getInstrumentedInstrumentationTargetInstance(): DynamicTestContract {
+ val originalBytecode = classToBytecode(CoverageInstrumentationTarget::class.java)
+ val patchedBytecode = applyInstrumentation(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${CoverageInstrumentationTarget::class.java.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${CoverageInstrumentationTarget::class.java.simpleName}.patched.class").writeBytes(patchedBytecode)
+ val patchedClass = bytecodeToClass(CoverageInstrumentationTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as DynamicTestContract
+}
+
+private fun assertControlFlow(expectedLocations: List<Int>) {
+ assertEquals(expectedLocations, MockCoverageMap.locations.toList())
+}
+
+class CoverageInstrumentationTest {
+
+ private val constructorReturn = 0
+ private val ifFirstBranch = 1
+ @Suppress("unused")
+ private val ifSecondBranch = 2
+ private val ifEnd = 3
+ private val outerForCondition = 4
+ private val innerForBodyIfFirstRun = 6
+ private val innerForBodyIfSecondRun = 5
+ private val innerForIncrementCounter = 7
+ private val outerForIncrementCounter = 8
+ private val afterFooInvocation = 9
+ private val beforeReturn = 10
+ private val fooAfterBarInvocation = 11
+ private val fooBeforeReturn = 12
+ private val barAfterMapPutInvocation = 13
+ private val barBeforeReturn = 14
+ @Suppress("unused")
+ private val bazReturn = 15
+
+ @Test
+ fun testOriginal() {
+ assertSelfCheck(getOriginalInstrumentationTargetInstance())
+ }
+
+ @Test
+ fun testInstrumented() {
+ MockCoverageMap.clear()
+ assertSelfCheck(getInstrumentedInstrumentationTargetInstance())
+
+ val innerForFirstRunControlFlow = mutableListOf<Int>().apply {
+ repeat(5) {
+ addAll(listOf(innerForBodyIfFirstRun, innerForIncrementCounter))
+ }
+ }.toList()
+ val innerForSecondRunControlFlow = mutableListOf<Int>().apply {
+ repeat(5) {
+ addAll(listOf(innerForBodyIfSecondRun, innerForIncrementCounter))
+ }
+ }.toList()
+ val outerForControlFlow =
+ listOf(outerForCondition) +
+ innerForFirstRunControlFlow +
+ listOf(outerForIncrementCounter, outerForCondition) +
+ innerForSecondRunControlFlow +
+ listOf(outerForIncrementCounter)
+
+ assertControlFlow(
+ listOf(constructorReturn, ifFirstBranch, ifEnd) +
+ outerForControlFlow +
+ listOf(
+ barAfterMapPutInvocation, barBeforeReturn,
+ fooAfterBarInvocation, fooBeforeReturn,
+ afterFooInvocation, beforeReturn
+ )
+ )
+ }
+
+ @OptIn(ExperimentalUnsignedTypes::class)
+ @Test
+ fun testCounters() {
+ MockCoverageMap.clear()
+
+ val target = getInstrumentedInstrumentationTargetInstance()
+ // The constructor of the target is run only once.
+ val takenOnceEdge = constructorReturn
+ // Control flows through the first if branch once per run.
+ val takenOnEveryRunEdge = ifFirstBranch
+
+ var lastCounter = 0.toUByte()
+ for (i in 1..600) {
+ assertSelfCheck(target)
+ assertEquals(1, MockCoverageMap.mem[takenOnceEdge])
+ // Verify that the counter increments, but is never zero.
+ val expectedCounter = (lastCounter + 1U).toUByte().takeUnless { it == 0.toUByte() }
+ ?: (lastCounter + 2U).toUByte()
+ lastCounter = expectedCounter
+ val actualCounter = MockCoverageMap.mem[takenOnEveryRunEdge].toUByte()
+ assertEquals(expectedCounter, actualCounter, "After $i runs:")
+ }
+ }
+
+ @Test
+ fun testSpecialCases() {
+ val originalBytecode = classToBytecode(CoverageInstrumentationSpecialCasesTarget::class.java)
+ val patchedBytecode = applyInstrumentation(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${CoverageInstrumentationSpecialCasesTarget::class.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${CoverageInstrumentationSpecialCasesTarget::class.simpleName}.patched.class").writeBytes(
+ patchedBytecode
+ )
+ val patchedClass = bytecodeToClass(CoverageInstrumentationSpecialCasesTarget::class.java.name, patchedBytecode)
+ // Trigger a class load
+ patchedClass.declaredMethods
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt
new file mode 100644
index 00000000..e7e1feba
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt
@@ -0,0 +1,73 @@
+// 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.instrumentor
+
+import com.code_intelligence.jazzer.utils.descriptor
+import org.junit.Test
+import kotlin.test.assertEquals
+
+class DescriptorUtilsTest {
+
+ @Test
+ fun testClassDescriptor() {
+ assertEquals("V", java.lang.Void::class.javaPrimitiveType?.descriptor)
+ assertEquals("J", java.lang.Long::class.javaPrimitiveType?.descriptor)
+ assertEquals("[[[Z", Array<Array<BooleanArray>>::class.java.descriptor)
+ assertEquals("[Ljava/lang/String;", Array<String>::class.java.descriptor)
+ }
+
+ @Test
+ fun testExtractInternalClassName() {
+ assertEquals("java/lang/String", extractInternalClassName("Ljava/lang/String;"))
+ assertEquals("[Ljava/lang/String;", extractInternalClassName("[Ljava/lang/String;"))
+ assertEquals("B", extractInternalClassName("B"))
+ }
+
+ @Test
+ fun testExtractTypeDescriptors() {
+ val testCases = listOf(
+ Triple(
+ String::class.java.getMethod("equals", Object::class.java),
+ listOf("Ljava/lang/Object;"),
+ "Z"
+ ),
+ Triple(
+ String::class.java.getMethod("regionMatches", Boolean::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java, Int::class.javaPrimitiveType, Integer::class.javaPrimitiveType),
+ listOf("Z", "I", "Ljava/lang/String;", "I", "I"),
+ "Z"
+ ),
+ Triple(
+ String::class.java.getMethod("getChars", Integer::class.javaPrimitiveType, Int::class.javaPrimitiveType, CharArray::class.java, Int::class.javaPrimitiveType),
+ listOf("I", "I", "[C", "I"),
+ "V"
+ ),
+ Triple(
+ String::class.java.getMethod("subSequence", Integer::class.javaPrimitiveType, Integer::class.javaPrimitiveType),
+ listOf("I", "I"),
+ "Ljava/lang/CharSequence;"
+ ),
+ Triple(
+ String::class.java.getConstructor(),
+ emptyList(),
+ "V"
+ )
+ )
+ for ((executable, parameterDescriptors, returnTypeDescriptor) in testCases) {
+ val descriptor = executable.descriptor
+ assertEquals(extractParameterTypeDescriptors(descriptor), parameterDescriptors)
+ assertEquals(extractReturnTypeDescriptor(descriptor), returnTypeDescriptor)
+ }
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java
new file mode 100644
index 00000000..163b226a
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java
@@ -0,0 +1,21 @@
+// 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.instrumentor;
+
+import java.util.Map;
+
+public interface DynamicTestContract {
+ Map<String, Boolean> selfCheck();
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt
new file mode 100644
index 00000000..7e7c31c9
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt
@@ -0,0 +1,38 @@
+// 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.instrumentor
+
+import com.code_intelligence.jazzer.api.MethodHook
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+class HookValidationTest {
+ @Test
+ fun testValidHooks() {
+ assertEquals(6, loadHooks(ValidHookMocks::class.java).size)
+ }
+
+ @Test
+ fun testInvalidHooks() {
+ for (method in InvalidHookMocks::class.java.methods) {
+ if (method.isAnnotationPresent(MethodHook::class.java)) {
+ assertFailsWith<IllegalArgumentException>("Expected ${method.name} to be an invalid hook") {
+ Hook.verifyAndGetHook(method, method.declaredAnnotations.first() as MethodHook)
+ }
+ }
+ }
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java
new file mode 100644
index 00000000..2723ad6e
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java
@@ -0,0 +1,61 @@
+// 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.instrumentor;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+class InvalidHookMocks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static void incorrectHookIdType(
+ MethodHandle method, String thisObject, Object[] arguments, long hookId) {}
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ private static void invalidAfterHook(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, Boolean returnValue) {}
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ public void invalidAfterHook2(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, boolean returnValue) {}
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.String",
+ targetMethod = "equals", targetMethodDescriptor = "(Ljava/lang/Object;)Z")
+ public static String
+ incorrectReturnType(MethodHandle method, String thisObject, Object[] arguments, int hookId) {
+ return "foo";
+ }
+
+ @MethodHook(
+ type = HookType.REPLACE, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static boolean
+ invalidReplaceHook2(MethodHandle method, Integer thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder",
+ targetMethod = "<init>", targetMethodDescriptor = "(Ljava/lang/String;)V")
+ public static Object
+ invalidReturnType(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
+ throws Throwable {
+ return null;
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
+ targetMethod = "startsWith", targetMethodDescriptor = "(Ljava/lang/String;)Z")
+ public static void
+ primitiveReturnValueMustBeWrapped(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, boolean returnValue) {}
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java
new file mode 100644
index 00000000..787ea493
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java
@@ -0,0 +1,45 @@
+// 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.instrumentor;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MockCoverageMap {
+ public static final int SIZE = 65536;
+ public static final ByteBuffer mem = ByteBuffer.allocate(SIZE);
+ public static int prev_location = 0; // is used in byte code directly
+
+ private static final ByteBuffer previous_mem = ByteBuffer.allocate(SIZE);
+ public static ArrayList<Integer> locations = new ArrayList<>();
+
+ public static void updated() {
+ int updated_pos = -1;
+ for (int i = 0; i < SIZE; i++) {
+ if (previous_mem.get(i) != mem.get(i)) {
+ updated_pos = i;
+ }
+ }
+ locations.add(updated_pos);
+ System.arraycopy(mem.array(), 0, previous_mem.array(), 0, SIZE);
+ }
+
+ public static void clear() {
+ Arrays.fill(mem.array(), (byte) 0);
+ Arrays.fill(previous_mem.array(), (byte) 0);
+ locations.clear();
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java
new file mode 100644
index 00000000..ad659da0
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java
@@ -0,0 +1,106 @@
+// 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.instrumentor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class MockTraceDataFlowCallbacks {
+ private static List<String> hookCalls;
+ private static int assertedCalls;
+
+ public static void init() {
+ hookCalls = new ArrayList<>();
+ assertedCalls = 0;
+ }
+
+ public static boolean hookCall(String expectedCall) {
+ if (assertedCalls >= hookCalls.size()) {
+ System.err.println("Not seen (" + hookCalls.size() + " calls, but " + (assertedCalls + 1)
+ + " expected): " + expectedCall);
+ return false;
+ }
+
+ if (!hookCalls.get(assertedCalls).equals(expectedCall)) {
+ System.err.println("Call " + expectedCall + " not seen, got " + hookCalls.get(assertedCalls));
+ return false;
+ }
+
+ assertedCalls++;
+ return true;
+ }
+
+ public static boolean finish() {
+ if (assertedCalls == hookCalls.size())
+ return true;
+ System.err.println("The following calls were not asserted:");
+ for (int i = assertedCalls; i < hookCalls.size(); i++) {
+ System.err.println(hookCalls.get(i));
+ }
+
+ return false;
+ }
+
+ public static void traceCmpLong(long arg1, long arg2, int pc) {
+ hookCalls.add("LCMP: " + Math.min(arg1, arg2) + ", " + Math.max(arg1, arg2));
+ }
+
+ public static void traceCmpInt(int arg1, int arg2, int pc) {
+ hookCalls.add("ICMP: " + Math.min(arg1, arg2) + ", " + Math.max(arg1, arg2));
+ }
+
+ public static void traceConstCmpInt(int arg1, int arg2, int pc) {
+ hookCalls.add("CICMP: " + arg1 + ", " + arg2);
+ }
+
+ public static void traceDivInt(int val, int pc) {
+ hookCalls.add("IDIV: " + val);
+ }
+
+ public static void traceDivLong(long val, int pc) {
+ hookCalls.add("LDIV: " + val);
+ }
+
+ public static void traceGep(long idx, int pc) {
+ hookCalls.add("GEP: " + idx);
+ }
+
+ public static void traceSwitch(long switchValue, long[] libfuzzerCaseValues, int pc) {
+ if (libfuzzerCaseValues.length < 3
+ // number of case values must match length
+ || libfuzzerCaseValues[0] != libfuzzerCaseValues.length - 2
+ // bit size of case values is always 32 (int)
+ || libfuzzerCaseValues[1] != 32) {
+ hookCalls.add("INVALID_SWITCH");
+ return;
+ }
+
+ StringBuilder builder = new StringBuilder("SWITCH: " + switchValue + ", (");
+ for (int i = 2; i < libfuzzerCaseValues.length; i++) {
+ builder.append(libfuzzerCaseValues[i]);
+ builder.append(", ");
+ }
+ builder.append(")");
+ hookCalls.add(builder.toString());
+ }
+
+ public static int traceCmpLongWrapper(long value1, long value2, int pc) {
+ traceCmpLong(value1, value2, pc);
+ // Long.compare serves as a substitute for the lcmp opcode here
+ // (behaviour is the same)
+ return Long.compare(value1, value2);
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt
new file mode 100644
index 00000000..f286d03f
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt
@@ -0,0 +1,53 @@
+// 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.instrumentor
+
+fun classToBytecode(targetClass: Class<*>): ByteArray {
+ return ClassLoader
+ .getSystemClassLoader()
+ .getResourceAsStream("${targetClass.name.replace('.', '/')}.class")!!
+ .use {
+ it.readBytes()
+ }
+}
+
+fun bytecodeToClass(name: String, bytecode: ByteArray): Class<*> {
+ return BytecodeClassLoader(name, bytecode).loadClass(name)
+}
+
+/**
+ * A ClassLoader that dynamically loads a single specified class from byte code and delegates all other class loads to
+ * its own ClassLoader.
+ */
+class BytecodeClassLoader(val className: String, private val classBytecode: ByteArray) :
+ ClassLoader(BytecodeClassLoader::class.java.classLoader) {
+ override fun loadClass(name: String): Class<*> {
+ if (name != className)
+ return super.loadClass(name)
+
+ return defineClass(className, classBytecode, 0, classBytecode.size)
+ }
+}
+
+fun assertSelfCheck(target: DynamicTestContract, shouldPass: Boolean = true) {
+ val results = target.selfCheck()
+ for ((test, passed) in results) {
+ if (shouldPass) {
+ assert(passed) { "$test should pass" }
+ } else {
+ assert(!passed) { "$test should not pass" }
+ }
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java
new file mode 100644
index 00000000..a71e1180
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java
@@ -0,0 +1,109 @@
+// 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.instrumentor;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+public class ReplaceHooks {
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnTrue1")
+ public static boolean
+ patchShouldReturnTrue1(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnTrue2")
+ public static Boolean
+ patchShouldReturnTrue2(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnTrue3")
+ public static Object
+ patchShouldReturnTrue3(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnFalse1")
+ public static Boolean
+ patchShouldReturnFalse1(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return false;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnFalse2")
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnFalse3")
+ public static Object
+ patchShouldReturnFalse2(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return false;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnReversed",
+ targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/String;")
+ public static String
+ patchShouldReturnReversed(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return new StringBuilder((String) arguments[0]).reverse().toString();
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldIncrement")
+ public static int
+ patchShouldIncrement(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return ((int) arguments[0]) + 1;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldCallPass")
+ public static void
+ patchShouldCallPass(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ ((ReplaceHooksTargetContract) thisObject).pass("shouldCallPass");
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "idempotent", targetMethodDescriptor = "(I)I")
+ public static int
+ patchIdempotent(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
+ throws Throwable {
+ // Iterate the function twice to pass the test.
+ int input = (int) arguments[0];
+ int temp = (int) method.invokeWithArguments(thisObject, input);
+ return (int) method.invokeWithArguments(thisObject, temp);
+ }
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.util.AbstractList",
+ targetMethod = "get", targetMethodDescriptor = "(I)Ljava/lang/Object;")
+ public static Object
+ patchAbstractListGet(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt
new file mode 100644
index 00000000..76fb53e5
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt
@@ -0,0 +1,63 @@
+// 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.instrumentor
+
+import org.junit.Test
+import java.io.File
+
+private fun applyReplaceHooks(bytecode: ByteArray): ByteArray {
+ return HookInstrumentor(loadHooks(ReplaceHooks::class.java), false).instrument(bytecode)
+}
+
+private fun getOriginalReplaceHooksTargetInstance(): ReplaceHooksTargetContract {
+ return ReplaceHooksTarget()
+}
+
+private fun getNoHooksReplaceHooksTargetInstance(): ReplaceHooksTargetContract {
+ val originalBytecode = classToBytecode(ReplaceHooksTarget::class.java)
+ // Let the bytecode pass through the hooking logic, but don't apply any hooks.
+ val patchedBytecode = HookInstrumentor(emptyList(), false).instrument(originalBytecode)
+ val patchedClass = bytecodeToClass(ReplaceHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as ReplaceHooksTargetContract
+}
+
+private fun getPatchedReplaceHooksTargetInstance(): ReplaceHooksTargetContract {
+ val originalBytecode = classToBytecode(ReplaceHooksTarget::class.java)
+ val patchedBytecode = applyReplaceHooks(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${ReplaceHooksTarget::class.java.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${ReplaceHooksTarget::class.java.simpleName}.patched.class").writeBytes(patchedBytecode)
+ val patchedClass = bytecodeToClass(ReplaceHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as ReplaceHooksTargetContract
+}
+
+class ReplaceHookTest {
+
+ @Test
+ fun testReplaceHooksOriginal() {
+ assertSelfCheck(getOriginalReplaceHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testReplaceHooksNoHooks() {
+ assertSelfCheck(getNoHooksReplaceHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testReplaceHooksPatched() {
+ assertSelfCheck(getPatchedReplaceHooksTargetInstance(), true)
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java
new file mode 100644
index 00000000..7a4b89f8
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java
@@ -0,0 +1,120 @@
+// 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.instrumentor;
+
+import java.security.SecureRandom;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+// selfCheck() only passes with the hooks in ReplaceHooks.java applied.
+public class ReplaceHooksTarget implements ReplaceHooksTargetContract {
+ Map<String, Boolean> results = new HashMap<>();
+
+ public static boolean shouldReturnTrue3() {
+ // return true;
+ return false;
+ }
+
+ public Map<String, Boolean> selfCheck() {
+ results = new HashMap<>();
+
+ results.put("shouldReturnTrue1", shouldReturnTrue1());
+ results.put("shouldReturnTrue2", shouldReturnTrue2());
+ results.put("shouldReturnTrue3", shouldReturnTrue3());
+ try {
+ boolean notTrue = false;
+ results.put("shouldReturnFalse1", notTrue);
+ if (!results.get("shouldReturnFalse1"))
+ results.put("shouldReturnFalse1", !shouldReturnFalse1());
+ boolean notFalse = true;
+ results.put("shouldReturnFalse2", !shouldReturnFalse2() && notFalse);
+ results.put("shouldReturnFalse3", !shouldReturnFalse3());
+ } catch (Exception e) {
+ boolean notTrue = false;
+ results.put("shouldNotBeExecuted", notTrue);
+ }
+ results.put("shouldReturnReversed", shouldReturnReversed("foo").equals("oof"));
+ results.put("shouldIncrement", shouldIncrement(5) == 6);
+ results.put("verifyIdentity", verifyIdentity());
+
+ results.put("shouldCallPass", false);
+ if (!results.get("shouldCallPass")) {
+ shouldCallPass();
+ }
+
+ AbstractList<Boolean> boolList = new ArrayList<>();
+ boolList.add(false);
+ results.put("arrayListGet", boolList.get(0));
+
+ return results;
+ }
+
+ public boolean shouldReturnTrue1() {
+ // return true;
+ return false;
+ }
+
+ public boolean shouldReturnTrue2() {
+ // return true;
+ return false;
+ }
+
+ protected Boolean shouldReturnFalse1() {
+ // return false;
+ return true;
+ }
+
+ Boolean shouldReturnFalse2() {
+ // return false;
+ return true;
+ }
+
+ public Boolean shouldReturnFalse3() {
+ // return false;
+ return true;
+ }
+
+ public String shouldReturnReversed(String input) {
+ // return new StringBuilder(input).reverse().toString();
+ return input;
+ }
+
+ public int shouldIncrement(int input) {
+ // return input + 1;
+ return input;
+ }
+
+ private void shouldCallPass() {
+ // pass("shouldCallPass");
+ }
+
+ private boolean verifyIdentity() {
+ SecureRandom rand = new SecureRandom();
+ int input = rand.nextInt();
+ // return idempotent(idempotent(input)) == input;
+ return idempotent(input) == input;
+ }
+
+ private int idempotent(int input) {
+ int secret = 0x12345678;
+ return input ^ secret;
+ }
+
+ public void pass(String test) {
+ results.put(test, true);
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java
new file mode 100644
index 00000000..e3dff93e
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java
@@ -0,0 +1,23 @@
+// 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.instrumentor;
+
+/**
+ * Helper interface used to call methods on instances of ReplaceHooksTarget classes loaded via
+ * different class loaders.
+ */
+public interface ReplaceHooksTargetContract extends DynamicTestContract {
+ void pass(String test);
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java
new file mode 100644
index 00000000..48f16e60
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java
@@ -0,0 +1,152 @@
+// 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.instrumentor;
+
+import java.nio.ByteBuffer;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.Vector;
+
+public class TraceDataFlowInstrumentationTarget implements DynamicTestContract {
+ volatile long long1 = 1;
+ volatile long long2 = 1;
+ volatile long long3 = 2;
+ volatile long long4 = 3;
+
+ volatile int int1 = 4;
+ volatile int int2 = 4;
+ volatile int int3 = 6;
+ volatile int int4 = 5;
+
+ volatile int switchValue = 1200;
+
+ @Override
+ public Map<String, Boolean> selfCheck() {
+ Map<String, Boolean> results = new HashMap<>();
+
+ results.put("longCompareEq", long1 == long2);
+ results.put("longCompareNe", long3 != long4);
+
+ results.put("intCompareEq", int1 == int2);
+ results.put("intCompareNe", int3 != int4);
+ results.put("intCompareLt", int4 < int3);
+ results.put("intCompareLe", int4 <= int3);
+ results.put("intCompareGt", int3 > int4);
+ results.put("intCompareGe", int3 >= int4);
+
+ // Not instrumented since all case values are non-negative and < 256.
+ switch (switchValue) {
+ case 119:
+ case 120:
+ case 121:
+ results.put("tableSwitchUninstrumented", false);
+ break;
+ default:
+ results.put("tableSwitchUninstrumented", true);
+ }
+
+ // Not instrumented since all case values are non-negative and < 256.
+ switch (switchValue) {
+ case 1:
+ case 200:
+ results.put("lookupSwitchUninstrumented", false);
+ break;
+ default:
+ results.put("lookupSwitchUninstrumented", true);
+ }
+
+ results.put("emptySwitchUninstrumented", false);
+ switch (switchValue) {
+ default:
+ results.put("emptySwitchUninstrumented", true);
+ }
+
+ switch (switchValue) {
+ case 1000:
+ case 1001:
+ // case 1002: The tableswitch instruction will contain a gap case for 1002.
+ case 1003:
+ results.put("tableSwitch", false);
+ break;
+ default:
+ results.put("tableSwitch", true);
+ }
+
+ switch (-switchValue) {
+ case -1200:
+ results.put("lookupSwitch", true);
+ break;
+ case -1:
+ case -10:
+ case -1000:
+ case 200:
+ default:
+ results.put("lookupSwitch", false);
+ }
+
+ results.put("intDiv", (int3 / 2) == 3);
+
+ results.put("longDiv", (long4 / 2) == 1);
+
+ String[] referenceArray = {"foo", "foo", "bar"};
+ boolean[] boolArray = {false, false, true};
+ byte[] byteArray = {0, 0, 2};
+ char[] charArray = {0, 0, 0, 3};
+ double[] doubleArray = {0, 0, 0, 0, 4};
+ float[] floatArray = {0, 0, 0, 0, 0, 5};
+ int[] intArray = {0, 0, 0, 0, 0, 0, 6};
+ long[] longArray = {0, 0, 0, 0, 0, 0, 0, 7};
+ short[] shortArray = {0, 0, 0, 0, 0, 0, 0, 0, 8};
+
+ results.put("referenceArrayGep", referenceArray[2].equals("bar"));
+ results.put("boolArrayGep", boolArray[2]);
+ results.put("byteArrayGep", byteArray[2] == 2);
+ results.put("charArrayGep", charArray[3] == 3);
+ results.put("doubleArrayGep", doubleArray[4] == 4);
+ results.put("floatArrayGep", floatArray[5] == 5);
+ results.put("intArrayGep", intArray[6] == 6);
+ results.put("longArrayGep", longArray[7] == 7);
+ results.put("shortArrayGep", shortArray[8] == 8);
+
+ ByteBuffer buffer = ByteBuffer.allocate(100);
+ buffer.get(2);
+ buffer.getChar(3);
+ buffer.getDouble(4);
+ buffer.getFloat(5);
+ buffer.getInt(6);
+ buffer.getLong(7);
+ buffer.getShort(8);
+
+ "foobarbazbat".charAt(9);
+ "foobarbazbat".codePointAt(10);
+ new StringBuilder("foobarbazbat").charAt(11);
+
+ (new Vector<>(Collections.nCopies(20, "foo"))).get(12);
+ (new ArrayList<>(Collections.nCopies(20, "foo"))).get(13);
+ Stack<String> stack = new Stack<>();
+ for (int i = 0; i < 20; i++) stack.push("foo");
+ stack.get(14);
+ stack.get(15);
+ ((AbstractList<String>) stack).get(16);
+ ((List<String>) stack).get(17);
+
+ return results;
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt
new file mode 100644
index 00000000..c6fd218f
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt
@@ -0,0 +1,145 @@
+// 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.instrumentor
+
+import org.junit.Test
+import java.io.File
+
+private fun applyInstrumentation(bytecode: ByteArray): ByteArray {
+ return TraceDataFlowInstrumentor(
+ setOf(
+ InstrumentationType.CMP,
+ InstrumentationType.DIV,
+ InstrumentationType.GEP
+ ),
+ MockTraceDataFlowCallbacks::class.java
+ ).instrument(bytecode)
+}
+
+private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract {
+ return TraceDataFlowInstrumentationTarget()
+}
+
+private fun getInstrumentedInstrumentationTargetInstance(): DynamicTestContract {
+ val originalBytecode = classToBytecode(TraceDataFlowInstrumentationTarget::class.java)
+ val patchedBytecode = applyInstrumentation(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${TraceDataFlowInstrumentationTarget::class.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${TraceDataFlowInstrumentationTarget::class.simpleName}.patched.class").writeBytes(patchedBytecode)
+ val patchedClass = bytecodeToClass(TraceDataFlowInstrumentationTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as DynamicTestContract
+}
+
+class TraceDataFlowInstrumentationTest {
+
+ @Test
+ fun testOriginal() {
+ MockTraceDataFlowCallbacks.init()
+ assertSelfCheck(getOriginalInstrumentationTargetInstance())
+ assert(MockTraceDataFlowCallbacks.finish())
+ }
+
+ @Test
+ fun testInstrumented() {
+ MockTraceDataFlowCallbacks.init()
+ assertSelfCheck(getInstrumentedInstrumentationTargetInstance())
+ listOf(
+ // long compares
+ "LCMP: 1, 1",
+ "LCMP: 2, 3",
+ // int compares
+ "ICMP: 4, 4",
+ "ICMP: 5, 6",
+ "ICMP: 5, 6",
+ "ICMP: 5, 6",
+ "ICMP: 5, 6",
+ "ICMP: 5, 6",
+ // tableswitch with gap
+ "SWITCH: 1200, (1000, 1001, 1003, )",
+ // lookupswitch
+ "SWITCH: -1200, (200, -1200, -1000, -10, -1, )",
+ // (6 / 2) == 3
+ "IDIV: 2",
+ "ICMP: 3, 3",
+ // (3 / 2) == 1
+ "LDIV: 2",
+ "LCMP: 1, 1",
+ // referenceArray[2]
+ "GEP: 2",
+ // boolArray[2]
+ "GEP: 2",
+ // byteArray[2] == 2
+ "GEP: 2",
+ "ICMP: 2, 2",
+ // charArray[3] == 3
+ "GEP: 3",
+ "ICMP: 3, 3",
+ // doubleArray[4] == 4
+ "GEP: 4",
+ // floatArray[5] == 5
+ "GEP: 5",
+ "CICMP: 0, 0",
+ // intArray[6] == 6
+ "GEP: 6",
+ "ICMP: 6, 6",
+ // longArray[7] == 7
+ "GEP: 7",
+ "LCMP: 7, 7",
+ // shortArray[8] == 8
+ "GEP: 8",
+ "ICMP: 8, 8",
+
+ "GEP: 2",
+ "GEP: 3",
+ "GEP: 4",
+ "GEP: 5",
+ "GEP: 6",
+ "GEP: 7",
+ "GEP: 8",
+ "GEP: 9",
+ "GEP: 10",
+ "GEP: 11",
+ "GEP: 12",
+ "GEP: 13",
+ "ICMP: 0, 20",
+ "ICMP: 1, 20",
+ "ICMP: 2, 20",
+ "ICMP: 3, 20",
+ "ICMP: 4, 20",
+ "ICMP: 5, 20",
+ "ICMP: 6, 20",
+ "ICMP: 7, 20",
+ "ICMP: 8, 20",
+ "ICMP: 9, 20",
+ "ICMP: 10, 20",
+ "ICMP: 11, 20",
+ "ICMP: 12, 20",
+ "ICMP: 13, 20",
+ "ICMP: 14, 20",
+ "ICMP: 15, 20",
+ "ICMP: 16, 20",
+ "ICMP: 17, 20",
+ "ICMP: 18, 20",
+ "ICMP: 19, 20",
+ "ICMP: 20, 20",
+ "GEP: 14",
+ "GEP: 15",
+ "GEP: 16",
+ "GEP: 17",
+ ).forEach { assert(MockTraceDataFlowCallbacks.hookCall(it)) }
+ assert(MockTraceDataFlowCallbacks.finish())
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java
new file mode 100644
index 00000000..06bed141
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java
@@ -0,0 +1,49 @@
+// 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.instrumentor;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+class ValidHookMocks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static void validBeforeHook(
+ MethodHandle method, String thisObject, Object[] arguments, int hookId) {}
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static void validAfterHook(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, Boolean returnValue) {}
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static void validAfterHook2(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, boolean returnValue) {}
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.String",
+ targetMethod = "equals", targetMethodDescriptor = "(Ljava/lang/Object;)Z")
+ public static Boolean
+ validReplaceHook(MethodHandle method, String thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(
+ type = HookType.REPLACE, targetClassName = "java.lang.String", targetMethod = "equals")
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.String",
+ targetMethod = "equalsIgnoreCase")
+ public static boolean
+ validReplaceHook2(MethodHandle method, String thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+}