aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/com/code_intelligence/jazzer/instrumentor
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/com/code_intelligence/jazzer/instrumentor')
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java87
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt89
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java85
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java29
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel152
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java53
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt89
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java61
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java25
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java41
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java67
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt176
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt72
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java21
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt40
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java87
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java53
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java106
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt64
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java136
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java26
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt89
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java126
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java23
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java153
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt143
-rw-r--r--src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java45
27 files changed, 2138 insertions, 0 deletions
diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java
new file mode 100644
index 00000000..f8d6782c
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt
new file mode 100644
index 00000000..55263786
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt
@@ -0,0 +1,89 @@
+// 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.instrumentor.PatchTestUtils.bytecodeToClass
+import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode
+import org.junit.Test
+import java.io.File
+
+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, null).instrument(
+ AfterHooksTarget::class.java.name.replace('.', '/'),
+ originalBytecode,
+ )
+ val patchedClass = bytecodeToClass(AfterHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as AfterHooksTargetContract
+}
+
+private fun getPatchedAfterHooksTargetInstance(classWithHooksEnabledField: Class<*>?): AfterHooksTargetContract {
+ val originalBytecode = classToBytecode(AfterHooksTarget::class.java)
+ val hooks = Hooks.loadHooks(emptyList(), setOf(AfterHooks::class.java.name)).first().hooks
+ val patchedBytecode = HookInstrumentor(
+ hooks,
+ false,
+ classWithHooksEnabledField = classWithHooksEnabledField?.name?.replace('.', '/'),
+ ).instrument(AfterHooksTarget::class.java.name.replace('.', '/'), 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 AfterHooksPatchTest {
+
+ @Test
+ fun testOriginal() {
+ assertSelfCheck(getOriginalAfterHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testPatchedWithoutHooks() {
+ assertSelfCheck(getNoHooksAfterHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testPatched() {
+ assertSelfCheck(getPatchedAfterHooksTargetInstance(null), true)
+ }
+
+ object HooksEnabled {
+ @Suppress("unused")
+ const val hooksEnabled = true
+ }
+
+ object HooksDisabled {
+ @Suppress("unused")
+ const val hooksEnabled = false
+ }
+
+ @Test
+ fun testPatchedWithConditionalHooksEnabled() {
+ assertSelfCheck(getPatchedAfterHooksTargetInstance(HooksEnabled::class.java), true)
+ }
+
+ @Test
+ fun testPatchedWithConditionalHooksDisabled() {
+ assertSelfCheck(getPatchedAfterHooksTargetInstance(HooksDisabled::class.java), false)
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java
new file mode 100644
index 00000000..a47b03a5
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java
new file mode 100644
index 00000000..cb12b148
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
new file mode 100644
index 00000000..4fdad567
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
@@ -0,0 +1,152 @@
+load("//bazel:kotlin.bzl", "ktlint", "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",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+wrapped_kt_jvm_test(
+ name = "trace_data_flow_instrumentation_test",
+ size = "small",
+ srcs = [
+ "MockTraceDataFlowCallbacks.java",
+ "TraceDataFlowInstrumentationTarget.java",
+ "TraceDataFlowInstrumentationTest.kt",
+ ],
+ associates = [
+ "//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 = [
+ "//src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.CoverageInstrumentationTest",
+ deps = [
+ ":patch_test_utils",
+ "//src/main/java/com/code_intelligence/jazzer/runtime:coverage_map",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "descriptor_utils_test",
+ size = "small",
+ srcs = [
+ "DescriptorUtilsTest.kt",
+ ],
+ associates = [
+ "//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 = [
+ "//src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.HookValidationTest",
+ deps = [
+ "//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 = [
+ "//src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.AfterHooksPatchTest",
+ deps = [
+ ":patch_test_utils",
+ "//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 = [
+ "//src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.BeforeHooksPatchTest",
+ deps = [
+ ":patch_test_utils",
+ "//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",
+ "ReplaceHooksInit.java",
+ "ReplaceHooksPatchTest.kt",
+ "ReplaceHooksTarget.java",
+ "ReplaceHooksTargetContract.java",
+ ],
+ associates = [
+ "//src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksPatchTest",
+ deps = [
+ ":patch_test_utils",
+ "//src/main/java/com/code_intelligence/jazzer/api",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+ktlint()
diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java
new file mode 100644
index 00000000..31577dad
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt
new file mode 100644
index 00000000..aae469c7
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt
@@ -0,0 +1,89 @@
+// 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.instrumentor.PatchTestUtils.bytecodeToClass
+import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode
+import org.junit.Test
+import java.io.File
+
+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, null).instrument(
+ BeforeHooksTarget::class.java.name.replace('.', '/'),
+ originalBytecode,
+ )
+ val patchedClass = bytecodeToClass(BeforeHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as BeforeHooksTargetContract
+}
+
+private fun getPatchedBeforeHooksTargetInstance(classWithHooksEnabledField: Class<*>?): BeforeHooksTargetContract {
+ val originalBytecode = classToBytecode(BeforeHooksTarget::class.java)
+ val hooks = Hooks.loadHooks(emptyList(), setOf(BeforeHooks::class.java.name)).first().hooks
+ val patchedBytecode = HookInstrumentor(
+ hooks,
+ false,
+ classWithHooksEnabledField = classWithHooksEnabledField?.name?.replace('.', '/'),
+ ).instrument(BeforeHooksTarget::class.java.name.replace('.', '/'), 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 BeforeHooksPatchTest {
+
+ @Test
+ fun testOriginal() {
+ assertSelfCheck(getOriginalBeforeHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testPatchedWithoutHooks() {
+ assertSelfCheck(getNoHooksBeforeHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testPatched() {
+ assertSelfCheck(getPatchedBeforeHooksTargetInstance(null), true)
+ }
+
+ object HooksEnabled {
+ @Suppress("unused")
+ const val hooksEnabled = true
+ }
+
+ object HooksDisabled {
+ @Suppress("unused")
+ const val hooksEnabled = false
+ }
+
+ @Test
+ fun testPatchedWithConditionalHooksEnabled() {
+ assertSelfCheck(getPatchedBeforeHooksTargetInstance(HooksEnabled::class.java), true)
+ }
+
+ @Test
+ fun testPatchedWithConditionalHooksDisabled() {
+ assertSelfCheck(getPatchedBeforeHooksTargetInstance(HooksDisabled::class.java), false)
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java
new file mode 100644
index 00000000..869e04bf
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java
new file mode 100644
index 00000000..61f79dcc
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java
new file mode 100644
index 00000000..cb811803
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java
new file mode 100644
index 00000000..7502481d
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt
new file mode 100644
index 00000000..5a3c355a
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt
@@ -0,0 +1,176 @@
+// 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.instrumentor.PatchTestUtils.bytecodeToClass
+import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode
+import org.junit.Test
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import java.io.File
+import kotlin.test.assertEquals
+
+/**
+ * Amends the instrumentation performed by [strategy] to call the map's public static void method
+ * updated() after every update to coverage counters.
+ */
+private fun makeTestable(strategy: EdgeCoverageStrategy): EdgeCoverageStrategy =
+ object : EdgeCoverageStrategy by strategy {
+ override fun instrumentControlFlowEdge(
+ mv: MethodVisitor,
+ edgeId: Int,
+ variable: Int,
+ coverageMapInternalClassName: String,
+ ) {
+ strategy.instrumentControlFlowEdge(mv, edgeId, variable, coverageMapInternalClassName)
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, coverageMapInternalClassName, "updated", "()V", false)
+ }
+ }
+
+private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract {
+ return CoverageInstrumentationTarget()
+}
+
+private fun getInstrumentedInstrumentationTargetInstance(): DynamicTestContract {
+ val originalBytecode = classToBytecode(CoverageInstrumentationTarget::class.java)
+ val patchedBytecode = EdgeCoverageInstrumentor(
+ makeTestable(ClassInstrumentor.defaultEdgeCoverageStrategy),
+ MockCoverageMap::class.java,
+ 0,
+ ).instrument(CoverageInstrumentationTarget::class.java.name.replace('.', '/'), 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())
+}
+
+@Suppress("unused")
+class CoverageInstrumentationTest {
+
+ private val constructorReturn = 0
+
+ private val mapConstructor = 1
+ private val addFor0 = 2
+ private val addFor1 = 3
+ private val addFor2 = 4
+ private val addFor3 = 5
+ private val addFor4 = 6
+ private val addFoobar = 7
+
+ private val ifTrueBranch = 8
+ private val addBlock1 = 9
+ private val ifFalseBranch = 10
+ private val ifEnd = 11
+
+ private val outerForCondition = 12
+ private val innerForCondition = 13
+ private val innerForBodyIfTrueBranch = 14
+ private val innerForBodyIfFalseBranch = 15
+ private val innerForBodyPutInvocation = 16
+ private val outerForIncrementCounter = 17
+
+ private val afterFooInvocation = 18
+ private val fooAfterBarInvocation = 19
+ private val barAfterPutInvocation = 20
+
+ @Test
+ fun testOriginal() {
+ assertSelfCheck(getOriginalInstrumentationTargetInstance())
+ }
+
+ @Test
+ fun testInstrumented() {
+ MockCoverageMap.clear()
+ assertSelfCheck(getInstrumentedInstrumentationTargetInstance())
+
+ val mapControlFlow = listOf(mapConstructor, addFor0, addFor1, addFor2, addFor3, addFor4, addFoobar)
+ val ifControlFlow = listOf(ifTrueBranch, addBlock1, ifEnd)
+ val forFirstRunControlFlow = mutableListOf<Int>().apply {
+ add(outerForCondition)
+ repeat(5) {
+ addAll(listOf(innerForCondition, innerForBodyIfFalseBranch, innerForBodyPutInvocation))
+ }
+ add(outerForIncrementCounter)
+ }.toList()
+ val forSecondRunControlFlow = mutableListOf<Int>().apply {
+ add(outerForCondition)
+ repeat(5) {
+ addAll(listOf(innerForCondition, innerForBodyIfTrueBranch, innerForBodyPutInvocation))
+ }
+ add(outerForIncrementCounter)
+ }.toList()
+ val forControlFlow = forFirstRunControlFlow + forSecondRunControlFlow
+ val fooCallControlFlow = listOf(
+ barAfterPutInvocation,
+ fooAfterBarInvocation,
+ afterFooInvocation,
+ )
+ assertControlFlow(
+ listOf(constructorReturn) +
+ mapControlFlow +
+ ifControlFlow +
+ forControlFlow +
+ fooCallControlFlow,
+ )
+ }
+
+ @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 = ifTrueBranch
+
+ var lastCounter = 0.toUByte()
+ for (i in 1..600) {
+ assertSelfCheck(target)
+ assertEquals(1, MockCoverageMap.counters[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.counters[takenOnEveryRunEdge].toUByte()
+ assertEquals(expectedCounter, actualCounter, "After $i runs:")
+ }
+ }
+
+ @Test
+ fun testSpecialCases() {
+ val originalBytecode = classToBytecode(CoverageInstrumentationSpecialCasesTarget::class.java)
+ val patchedBytecode = EdgeCoverageInstrumentor(
+ makeTestable(ClassInstrumentor.defaultEdgeCoverageStrategy),
+ MockCoverageMap::class.java,
+ 0,
+ ).instrument(CoverageInstrumentationSpecialCasesTarget::class.java.name.replace('.', '/'), 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/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt
new file mode 100644
index 00000000..c1a3584e
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt
@@ -0,0 +1,72 @@
+// 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;",
+ ),
+ 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/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java
new file mode 100644
index 00000000..163b226a
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt
new file mode 100644
index 00000000..bf02da72
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt
@@ -0,0 +1,40 @@
+// 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() {
+ val hooks = Hooks.loadHooks(emptyList(), setOf(ValidHookMocks::class.java.name)).first().hooks
+ assertEquals(5, hooks.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") {
+ val methodHook = method.declaredAnnotations.first() as MethodHook
+ Hook.createAndVerifyHook(method, methodHook, methodHook.targetClassName)
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java
new file mode 100644
index 00000000..0df349ca
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.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;
+
+@SuppressWarnings({"unused", "RedundantThrows"})
+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.System", targetMethod = "gc",
+ targetMethodDescriptor = "()V")
+ public static Object
+ invalidReplaceVoidMethod(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return null;
+ }
+
+ @MethodHook(type = HookType.BEFORE, 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) {}
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder",
+ targetMethod = "<init>", targetMethodDescriptor = "(Ljava/lang/String;)V")
+ public static void
+ replaceOnInitWithoutReturnType(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId) throws Throwable {}
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder",
+ targetMethod = "<init>", targetMethodDescriptor = "(Ljava/lang/String;)V")
+ public static Object
+ replaceOnInitWithIncompatibleType(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId) throws Throwable {
+ return new Object();
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static void primitiveReturnType(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, boolean returnValue) {}
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java
new file mode 100644
index 00000000..3ea33d19
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.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 java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MockCoverageMap {
+ public static final int SIZE = 65536;
+ public static final ByteBuffer counters = ByteBuffer.allocate(SIZE);
+
+ 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) != counters.get(i)) {
+ updated_pos = i;
+ }
+ }
+ locations.add(updated_pos);
+ System.arraycopy(counters.array(), 0, previous_mem.array(), 0, SIZE);
+ }
+
+ public static void enlargeIfNeeded(int nextId) {
+ // This mock coverage map is statically sized.
+ }
+
+ public static void recordCoverage(int id) {
+ byte counter = counters.get(id);
+ counters.put(id, (byte) (counter == -1 ? 1 : counter + 1));
+ }
+
+ public static void clear() {
+ Arrays.fill(counters.array(), (byte) 0);
+ Arrays.fill(previous_mem.array(), (byte) 0);
+ locations.clear();
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java
new file mode 100644
index 00000000..ad659da0
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt
new file mode 100644
index 00000000..de2cc187
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt
@@ -0,0 +1,64 @@
+// 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.io.FileOutputStream
+
+object PatchTestUtils {
+ @JvmStatic
+ fun classToBytecode(targetClass: Class<*>): ByteArray {
+ return ClassLoader
+ .getSystemClassLoader()
+ .getResourceAsStream("${targetClass.name.replace('.', '/')}.class")!!
+ .use {
+ it.readBytes()
+ }
+ }
+
+ @JvmStatic
+ fun bytecodeToClass(name: String, bytecode: ByteArray): Class<*> {
+ return BytecodeClassLoader(name, bytecode).loadClass(name)
+ }
+
+ @JvmStatic
+ fun dumpBytecode(outDir: String, name: String, originalBytecode: ByteArray) {
+ FileOutputStream("$outDir/$name.class").use { fos -> fos.write(originalBytecode) }
+ }
+
+ /**
+ * 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/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java
new file mode 100644
index 00000000..7e31b77b
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java
@@ -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 com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+@SuppressWarnings("unused")
+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;
+ }
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.util.Set", targetMethod = "contains",
+ targetMethodDescriptor = "(Ljava/lang/Object;)Z")
+ public static boolean
+ patchSetGet(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksInit",
+ targetMethod = "<init>", targetMethodDescriptor = "()V")
+ public static ReplaceHooksInit
+ patchInit(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ // Test with subclass
+ return new ReplaceHooksInit() {
+ { initialized = true; }
+ };
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksInit",
+ targetMethod = "<init>", targetMethodDescriptor = "(ZLjava/lang/String;)V")
+ public static ReplaceHooksInit
+ patchInitWithParams(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return new ReplaceHooksInit(true, "");
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java
new file mode 100644
index 00000000..da77be81
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java
@@ -0,0 +1,26 @@
+// 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;
+
+public class ReplaceHooksInit {
+ public boolean initialized;
+
+ public ReplaceHooksInit() {}
+
+ @SuppressWarnings("unused")
+ public ReplaceHooksInit(boolean initialized, String ignored) {
+ this.initialized = initialized;
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt
new file mode 100644
index 00000000..275c43f9
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt
@@ -0,0 +1,89 @@
+// 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.instrumentor.PatchTestUtils.bytecodeToClass
+import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode
+import org.junit.Test
+import java.io.File
+
+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, null).instrument(
+ ReplaceHooksTarget::class.java.name.replace('.', '/'),
+ originalBytecode,
+ )
+ val patchedClass = bytecodeToClass(ReplaceHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as ReplaceHooksTargetContract
+}
+
+private fun getPatchedReplaceHooksTargetInstance(classWithHooksEnabledField: Class<*>?): ReplaceHooksTargetContract {
+ val originalBytecode = classToBytecode(ReplaceHooksTarget::class.java)
+ val hooks = Hooks.loadHooks(emptyList(), setOf(ReplaceHooks::class.java.name)).first().hooks
+ val patchedBytecode = HookInstrumentor(
+ hooks,
+ false,
+ classWithHooksEnabledField = classWithHooksEnabledField?.name?.replace('.', '/'),
+ ).instrument(ReplaceHooksTarget::class.java.name.replace('.', '/'), 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 ReplaceHooksPatchTest {
+
+ @Test
+ fun testOriginal() {
+ assertSelfCheck(getOriginalReplaceHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testPatchedWithoutHooks() {
+ assertSelfCheck(getNoHooksReplaceHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testPatched() {
+ assertSelfCheck(getPatchedReplaceHooksTargetInstance(null), true)
+ }
+
+ object HooksEnabled {
+ @Suppress("unused")
+ const val hooksEnabled = true
+ }
+
+ object HooksDisabled {
+ @Suppress("unused")
+ const val hooksEnabled = false
+ }
+
+ @Test
+ fun testPatchedWithConditionalHooksEnabled() {
+ assertSelfCheck(getPatchedReplaceHooksTargetInstance(HooksEnabled::class.java), true)
+ }
+
+ @Test
+ fun testPatchedWithConditionalHooksDisabled() {
+ assertSelfCheck(getPatchedReplaceHooksTargetInstance(HooksDisabled::class.java), false)
+ }
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java
new file mode 100644
index 00000000..fadbdf80
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java
@@ -0,0 +1,126 @@
+// 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.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+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();
+ }
+
+ ArrayList<Boolean> boolList = new ArrayList<>();
+ boolList.add(false);
+ results.put("arrayListGet", boolList.get(0));
+
+ HashSet<Boolean> boolSet = new HashSet<>();
+ results.put("stringSetGet", boolSet.contains(Boolean.TRUE));
+
+ results.put("shouldInitialize", new ReplaceHooksInit().initialized);
+ results.put("shouldInitializeWithParams", new ReplaceHooksInit(false, "foo").initialized);
+
+ 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/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java
new file mode 100644
index 00000000..e3dff93e
--- /dev/null
+++ b/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/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java
new file mode 100644
index 00000000..d8e28881
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java
@@ -0,0 +1,153 @@
+// 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;
+
+ @SuppressWarnings("ReturnValueIgnored")
+ @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/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt
new file mode 100644
index 00000000..b7383f1f
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt
@@ -0,0 +1,143 @@
+// 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.instrumentor.PatchTestUtils.bytecodeToClass
+import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode
+import org.junit.Test
+import java.io.File
+
+private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract {
+ return TraceDataFlowInstrumentationTarget()
+}
+
+private fun getInstrumentedInstrumentationTargetInstance(): DynamicTestContract {
+ val originalBytecode = classToBytecode(TraceDataFlowInstrumentationTarget::class.java)
+ val patchedBytecode = TraceDataFlowInstrumentor(
+ setOf(
+ InstrumentationType.CMP,
+ InstrumentationType.DIV,
+ InstrumentationType.GEP,
+ ),
+ MockTraceDataFlowCallbacks::class.java.name.replace('.', '/'),
+ ).instrument(TraceDataFlowInstrumentationTarget::class.java.name.replace('.', '/'), 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/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java b/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java
new file mode 100644
index 00000000..a919242b
--- /dev/null
+++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.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 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.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;
+ }
+}