aboutsummaryrefslogtreecommitdiff
path: root/agent/src/test/java/com
diff options
context:
space:
mode:
authorFabian Meumertzheim <meumertzheim@code-intelligence.com>2021-01-29 16:20:19 +0100
committerFabian Meumertzheim <meumertzheim@code-intelligence.com>2021-02-09 17:20:51 +0100
commit5246e52be3bf4427791000355cbef86626b43eca (patch)
treee0683ad15664f2c3deecf3a6ce8c56f2a9597d85 /agent/src/test/java/com
downloadjazzer-api-5246e52be3bf4427791000355cbef86626b43eca.tar.gz
Initial commit
Diffstat (limited to 'agent/src/test/java/com')
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java60
-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.java78
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java27
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel146
-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.java80
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt136
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt67
-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.java46
-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.kt47
-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.java146
-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
26 files changed, 1874 insertions, 0 deletions
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..50588106
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java
@@ -0,0 +1,60 @@
+// 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 {
+ @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) {
+ ((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) throws Throwable {
+ // 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) throws Throwable {
+ // Use the returned secret to pass the test.
+ ((AfterHooksTargetContract) thisObject).verifySecondSecret((String) returnValue);
+ }
+}
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..e6a0a106
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java
@@ -0,0 +1,78 @@
+// 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();
+
+ 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!"));
+ }
+}
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..fb833c32
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java
@@ -0,0 +1,27 @@
+// 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);
+}
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..e745a662
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
@@ -0,0 +1,146 @@
+load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library", "kt_jvm_test")
+
+kt_jvm_library(
+ name = "patch_test_utils",
+ srcs = glob([
+ "DynamicTestContract.java",
+ "PatchTestUtils.kt",
+ ]),
+)
+
+kt_jvm_test(
+ name = "trace_data_flow_instrumentation_test",
+ size = "small",
+ srcs = [
+ "MockTraceDataFlowCallbacks.java",
+ "TraceDataFlowInstrumentationTarget.java",
+ "TraceDataFlowInstrumentationTest.kt",
+ ],
+ friends = [
+ "//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",
+ ],
+)
+
+kt_jvm_test(
+ name = "coverage_instrumentation_test",
+ size = "small",
+ srcs = [
+ "CoverageInstrumentationSpecialCasesTarget.java",
+ "CoverageInstrumentationTarget.java",
+ "CoverageInstrumentationTest.kt",
+ "MockCoverageMap.java",
+ ],
+ friends = [
+ "//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",
+ ],
+)
+
+kt_jvm_test(
+ name = "descriptor_utils_test",
+ size = "small",
+ srcs = [
+ "DescriptorUtilsTest.kt",
+ ],
+ friends = [
+ "//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",
+ ],
+)
+
+kt_jvm_test(
+ name = "hook_validation_test",
+ size = "small",
+ srcs = [
+ "HookValidationTest.kt",
+ "InvalidHookMocks.java",
+ "ValidHookMocks.java",
+ ],
+ friends = [
+ "//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",
+ ],
+)
+
+kt_jvm_test(
+ name = "after_hooks_patch_test",
+ size = "small",
+ srcs = [
+ "AfterHooks.java",
+ "AfterHooksPatchTest.kt",
+ "AfterHooksTarget.java",
+ "AfterHooksTargetContract.java",
+ ],
+ friends = [
+ "//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",
+ ],
+)
+
+kt_jvm_test(
+ name = "before_hooks_patch_test",
+ size = "small",
+ srcs = [
+ "BeforeHooks.java",
+ "BeforeHooksPatchTest.kt",
+ "BeforeHooksTarget.java",
+ "BeforeHooksTargetContract.java",
+ ],
+ friends = [
+ "//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",
+ ],
+)
+
+kt_jvm_test(
+ name = "replace_hooks_patch_test",
+ size = "small",
+ srcs = [
+ "ReplaceHooks.java",
+ "ReplaceHooksPatchTest.kt",
+ "ReplaceHooksTarget.java",
+ "ReplaceHooksTargetContract.java",
+ ],
+ friends = [
+ "//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..b738ab19
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java
@@ -0,0 +1,80 @@
+// 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 {
+ // Constructor loc: constructorStart
+
+ volatile int int1 = 3;
+ volatile int int2 = 213234;
+
+ @Override
+ public Map<String, Boolean> selfCheck() {
+ // loc: selfCheckStart
+ Map<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) {
+ // loc: ifFirstBranch
+ results.put("block1", true);
+ } else {
+ // loc: not reached
+ results.put("block2", false);
+ }
+ // loc: ifEnd
+
+ for (int i = 0; /* loc: outerForCondition */ i < 2; /* loc: outerForIncrementCounter */ i++) {
+ /* loc: outerForBody */
+ for (int j = 0; /* loc: innerForCondition */ j < 5; /* loc: innerForIncrementCounter */ j++) {
+ // loc: innerForBody
+ results.put("for" + j,
+ i != 0); // != 0 loc: innerForBodyIfSecondRun, == 0 loc: innerForBodyIfFirstRun
+ }
+ }
+ // loc: outerForAfter
+
+ foo(results);
+ // baz(results);
+
+ return results;
+ }
+
+ private void foo(Map<String, Boolean> results) {
+ // loc: fooStart
+ bar(results);
+ }
+
+ private void bar(Map<String, Boolean> results) {
+ // loc: barStart
+ results.put("foobar", true);
+ }
+
+ // Not called.
+ @SuppressWarnings("unused")
+ private void baz(Map<String, Boolean> results) {
+ // loc: not reached
+ 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..5de70f8a
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt
@@ -0,0 +1,136 @@
+// 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 AFLCoverageMapInstrumentor(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 constructorStart = 54445
+ private val selfCheckStart = 8397
+ private val ifFirstBranch = 1555
+ private val ifEnd = 26354
+ private val outerForCondition = 37842
+ private val outerForBody = 53325
+ private val innerForCondition = 38432
+ private val innerForBody = 5673
+ private val innerForBodyIfFirstRun = 2378
+ private val innerForBodyIfSecondRun = 57606
+ private val innerForIncrementCounter = 7617
+ private val outerForIncrementCounter = 14668
+ private val outerForAfter = 9328
+ private val fooStart = 32182
+ private val barStart = 1381
+
+ @Test
+ fun testOriginal() {
+ assertSelfCheck(getOriginalInstrumentationTargetInstance())
+ }
+
+ @Test
+ fun testInstrumented() {
+ MockCoverageMap.clear()
+ assertSelfCheck(getInstrumentedInstrumentationTargetInstance())
+
+ val innerForFirstRunControlFlow = mutableListOf<Int>().apply {
+ repeat(5) {
+ addAll(listOf(innerForCondition, innerForBody, innerForBodyIfFirstRun, innerForIncrementCounter))
+ }
+ add(innerForCondition)
+ }.toList()
+ val innerForSecondRunControlFlow = mutableListOf<Int>().apply {
+ repeat(5) {
+ addAll(listOf(innerForCondition, innerForBody, innerForBodyIfSecondRun, innerForIncrementCounter))
+ }
+ add(innerForCondition)
+ }.toList()
+ val outerForControlFlow = listOf(outerForCondition, outerForBody) +
+ innerForFirstRunControlFlow +
+ listOf(outerForIncrementCounter, outerForCondition, outerForBody) +
+ innerForSecondRunControlFlow +
+ listOf(outerForIncrementCounter, outerForCondition)
+
+ assertControlFlow(
+ listOf(constructorStart, selfCheckStart, ifFirstBranch, ifEnd) +
+ outerForControlFlow +
+ listOf(outerForAfter, fooStart, barStart)
+ )
+ }
+
+ /**
+ * Computes the position of the counter in the coverage map to be incremented when control flows
+ * from the first member of [blocks] to the second.
+ */
+ fun edge(blocks: Pair<Int, Int>) = (blocks.first shr 1) xor blocks.second
+
+ @OptIn(ExperimentalUnsignedTypes::class)
+ @Test
+ fun testCounters() {
+ MockCoverageMap.clear()
+
+ val target = getInstrumentedInstrumentationTargetInstance()
+ // The constructor of the target is run only once.
+ val takenOnceEdge = edge(constructorStart to selfCheckStart)
+ // Control flows from the start of selfCheck to the first if branch once per run.
+ val takenOnEveryRunEdge = edge(selfCheckStart to ifFirstBranch)
+
+ for (i in 1..300) {
+ assertSelfCheck(target)
+ assertEquals(1, MockCoverageMap.mem[takenOnceEdge])
+ // Verify that the counter does not overflow.
+ val expectedCounter = i.coerceAtMost(255).toUByte()
+ 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..bd7ddc88
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt
@@ -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 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;"
+ ),
+ )
+ for ((method, parameterDescriptors, returnTypeDescriptor) in testCases) {
+ val descriptor = method.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..b289d20c
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java
@@ -0,0 +1,46 @@
+// 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;
+ }
+ }
+ int cur_location = updated_pos ^ prev_location;
+ locations.add(cur_location);
+ 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..f2c63b0c
--- /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
+ // bit size of case values is always 32 (int)
+ || libfuzzerCaseValues[0] != 32
+ // number of case values must match length
+ || libfuzzerCaseValues[1] != libfuzzerCaseValues.length - 2) {
+ 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..b803630e
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt
@@ -0,0 +1,47 @@
+// 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")!!.readAllBytes()
+}
+
+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..428b58e4
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java
@@ -0,0 +1,146 @@
+// 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);
+ }
+
+ 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;
+ }
+}