From 5246e52be3bf4427791000355cbef86626b43eca Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 29 Jan 2021 16:20:19 +0100 Subject: Initial commit --- .../jazzer/instrumentor/AfterHooks.java | 60 +++++++++ .../jazzer/instrumentor/AfterHooksPatchTest.kt | 63 +++++++++ .../jazzer/instrumentor/AfterHooksTarget.java | 78 +++++++++++ .../instrumentor/AfterHooksTargetContract.java | 27 ++++ .../jazzer/instrumentor/BUILD.bazel | 146 +++++++++++++++++++++ .../jazzer/instrumentor/BeforeHooks.java | 53 ++++++++ .../jazzer/instrumentor/BeforeHooksPatchTest.kt | 63 +++++++++ .../jazzer/instrumentor/BeforeHooksTarget.java | 61 +++++++++ .../instrumentor/BeforeHooksTargetContract.java | 25 ++++ .../CoverageInstrumentationSpecialCasesTarget.java | 41 ++++++ .../CoverageInstrumentationTarget.java | 80 +++++++++++ .../instrumentor/CoverageInstrumentationTest.kt | 136 +++++++++++++++++++ .../jazzer/instrumentor/DescriptorUtilsTest.kt | 67 ++++++++++ .../jazzer/instrumentor/DynamicTestContract.java | 21 +++ .../jazzer/instrumentor/HookValidationTest.kt | 38 ++++++ .../jazzer/instrumentor/InvalidHookMocks.java | 61 +++++++++ .../jazzer/instrumentor/MockCoverageMap.java | 46 +++++++ .../instrumentor/MockTraceDataFlowCallbacks.java | 106 +++++++++++++++ .../jazzer/instrumentor/PatchTestUtils.kt | 47 +++++++ .../jazzer/instrumentor/ReplaceHooks.java | 109 +++++++++++++++ .../jazzer/instrumentor/ReplaceHooksPatchTest.kt | 63 +++++++++ .../jazzer/instrumentor/ReplaceHooksTarget.java | 120 +++++++++++++++++ .../instrumentor/ReplaceHooksTargetContract.java | 23 ++++ .../TraceDataFlowInstrumentationTarget.java | 146 +++++++++++++++++++++ .../TraceDataFlowInstrumentationTest.kt | 145 ++++++++++++++++++++ .../jazzer/instrumentor/ValidHookMocks.java | 49 +++++++ 26 files changed, 1874 insertions(+) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 results = new HashMap<>(); + static int timesCalled = 0; + Boolean func1Called = false; + + public static void registerTimesCalled() { + timesCalled++; + results.put("hasBeenCalledTwice", timesCalled == 2); + } + + public Map 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 results = new HashMap<>(); + Boolean func1Called = false; + Boolean funcWithArgsCalled = false; + + static Integer getTimesCalled() { + return ++timesCalled; + } + + public Map 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 selfCheck() { + // loc: selfCheckStart + Map 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 results) { + // loc: fooStart + bar(results); + } + + private void bar(Map results) { + // loc: barStart + results.put("foobar", true); + } + + // Not called. + @SuppressWarnings("unused") + private void baz(Map 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) { + 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().apply { + repeat(5) { + addAll(listOf(innerForCondition, innerForBody, innerForBodyIfFirstRun, innerForIncrementCounter)) + } + add(innerForCondition) + }.toList() + val innerForSecondRunControlFlow = mutableListOf().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) = (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>::class.java.descriptor) + assertEquals("[Ljava/lang/String;", Array::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 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("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 = "", 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 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 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 results = new HashMap<>(); + + public static boolean shouldReturnTrue3() { + // return true; + return false; + } + + public Map 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 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 selfCheck() { + Map 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 stack = new Stack<>(); + for (int i = 0; i < 20; i++) stack.push("foo"); + stack.get(14); + stack.get(15); + ((AbstractList) stack).get(16); + ((List) 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; + } +} -- cgit v1.2.3 From 97942a9f4924ca4c9cad2a2756e44ad29fb44fca Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 12 Feb 2021 14:18:37 +0100 Subject: Instrument edges instead of basic blocks We are currently deriving edge coverage instrumentation from basic block instrumentation via the AFL XOR-technique. This has several downsides: * Different edges can be assigned the same position in the coverage map, which leads to underreported coverage. * The coverage map needs to be large enough for collisions to be unlikely (on the order of num_edges^2). In addition to being wasteful, it is also hard to determine the correct size given that we don't know the number of edges. In addition to the design limitations, the current implementation additionally does not take into account that most Java method invocations can throw exceptions and thus need to be instrumented. These issues are resolved by switching to true LLVM-style edge coverage instrumentation. The new coverage instrumentation is based on a lightly patched version of the JaCoCo internals. Note: //agent/src/test/java/com/code_intelligence/jazzer/instrumentor:coverage_instrumentation_test is not passing for this commit. It will be fixed with the next commit. --- .../CoverageInstrumentationTarget.java | 21 +------ .../instrumentor/CoverageInstrumentationTest.kt | 71 +++++++++++----------- .../jazzer/instrumentor/MockCoverageMap.java | 3 +- 3 files changed, 38 insertions(+), 57 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index b738ab19..5725ba23 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java @@ -18,14 +18,11 @@ 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 selfCheck() { - // loc: selfCheckStart Map results = new HashMap<>(); results.put("for0", false); @@ -37,44 +34,32 @@ public class CoverageInstrumentationTarget implements DynamicTestContract { 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 + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 5; j++) { + results.put("for" + j, i != 0); } } - // loc: outerForAfter foo(results); - // baz(results); return results; } private void foo(Map results) { - // loc: fooStart bar(results); } private void bar(Map results) { - // loc: barStart results.put("foobar", true); } - // Not called. @SuppressWarnings("unused") private void baz(Map 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 index 5de70f8a..c689e1ac 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt @@ -19,7 +19,9 @@ import java.io.File import kotlin.test.assertEquals private fun applyInstrumentation(bytecode: ByteArray): ByteArray { - return AFLCoverageMapInstrumentor(MockCoverageMap::class.java).instrument(bytecode) + EdgeCoverageInstrumentor.resetNextGlobalEdgeIdForTestingOnly() + EdgeCoverageInstrumentor.setCoverageMapClassForTestingOnly(MockCoverageMap::class.java) + return EdgeCoverageInstrumentor.instrument(bytecode) } private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract { @@ -43,21 +45,21 @@ private fun assertControlFlow(expectedLocations: List) { 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 + private val constructorReturn = 0 + private val ifFirstBranch = 1 + @Suppress("unused") + private val ifSecondBranch = 2 + private val ifEnd = 3 + private val outerForCondition = 4 + private val innerForBodyIfFirstRun = 6 + private val innerForBodyIfSecondRun = 5 + private val innerForIncrementCounter = 7 + private val outerForIncrementCounter = 8 + private val selfCheckReturn = 9 + private val fooReturn = 10 + private val barReturn = 11 + @Suppress("unused") + private val bazReturn = 12 @Test fun testOriginal() { @@ -71,35 +73,28 @@ class CoverageInstrumentationTest { val innerForFirstRunControlFlow = mutableListOf().apply { repeat(5) { - addAll(listOf(innerForCondition, innerForBody, innerForBodyIfFirstRun, innerForIncrementCounter)) + addAll(listOf(innerForBodyIfFirstRun, innerForIncrementCounter)) } - add(innerForCondition) }.toList() val innerForSecondRunControlFlow = mutableListOf().apply { repeat(5) { - addAll(listOf(innerForCondition, innerForBody, innerForBodyIfSecondRun, innerForIncrementCounter)) + addAll(listOf(innerForBodyIfSecondRun, innerForIncrementCounter)) } - add(innerForCondition) }.toList() - val outerForControlFlow = listOf(outerForCondition, outerForBody) + - innerForFirstRunControlFlow + - listOf(outerForIncrementCounter, outerForCondition, outerForBody) + - innerForSecondRunControlFlow + - listOf(outerForIncrementCounter, outerForCondition) + val outerForControlFlow = + listOf(outerForCondition) + + innerForFirstRunControlFlow + + listOf(outerForIncrementCounter, outerForCondition) + + innerForSecondRunControlFlow + + listOf(outerForIncrementCounter) assertControlFlow( - listOf(constructorStart, selfCheckStart, ifFirstBranch, ifEnd) + + listOf(constructorReturn, ifFirstBranch, ifEnd) + outerForControlFlow + - listOf(outerForAfter, fooStart, barStart) + listOf(barReturn, fooReturn, selfCheckReturn) ) } - /** - * 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) = (blocks.first shr 1) xor blocks.second - @OptIn(ExperimentalUnsignedTypes::class) @Test fun testCounters() { @@ -107,9 +102,9 @@ class CoverageInstrumentationTest { 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) + val takenOnceEdge = constructorReturn + // Control flows through the first if branch once per run. + val takenOnEveryRunEdge = ifFirstBranch for (i in 1..300) { assertSelfCheck(target) @@ -128,7 +123,9 @@ class CoverageInstrumentationTest { // 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) + 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/MockCoverageMap.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java index b289d20c..787ea493 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java @@ -33,8 +33,7 @@ public class MockCoverageMap { updated_pos = i; } } - int cur_location = updated_pos ^ prev_location; - locations.add(cur_location); + locations.add(updated_pos); System.arraycopy(mem.array(), 0, previous_mem.array(), 0, SIZE); } -- cgit v1.2.3 From 0677032a0a698c920f02b44fcf7528fc3a6ce448 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 12 Feb 2021 14:18:37 +0100 Subject: Skip instrumentation for known no-throw methods A method invocation only needs to be instrumented for coverage if the method can throw an exception (including RuntimeException, which is not declared via `throws`). For methods in the Java standard library (in `java.*`), this property is thoroughly documented via Javadoc. This commit adds a doclet, i.e., a Java program that parses Javadoc, for automatically compiling a list of known no-throw methods in `java.*`. The list is loaded lazily at runtime and used to skip calls to these methods in the coverage instrumentation. With OpenJDK 15, the list consists of almost 9000 methods, many of which are used very frequently and in performance-critical parts, e.g., StringBuilder#append(String) and List#size(). --- .../jazzer/instrumentor/CoverageInstrumentationTarget.java | 8 +++++--- .../jazzer/instrumentor/CoverageInstrumentationTest.kt | 13 ++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index 5725ba23..7502481d 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java @@ -23,7 +23,7 @@ public class CoverageInstrumentationTarget implements DynamicTestContract { @Override public Map selfCheck() { - Map results = new HashMap<>(); + HashMap results = new HashMap<>(); results.put("for0", false); results.put("for1", false); @@ -50,16 +50,18 @@ public class CoverageInstrumentationTarget implements DynamicTestContract { return results; } - private void foo(Map results) { + private void foo(HashMap 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 results) { results.put("foobar", true); } @SuppressWarnings("unused") - private void baz(Map results) { + private void baz(HashMap results) { results.put("baz", false); } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt index c689e1ac..31f40575 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt @@ -55,11 +55,14 @@ class CoverageInstrumentationTest { private val innerForBodyIfSecondRun = 5 private val innerForIncrementCounter = 7 private val outerForIncrementCounter = 8 - private val selfCheckReturn = 9 - private val fooReturn = 10 - private val barReturn = 11 + private val fooInvocation = 9 + private val selfCheckReturn = 10 + private val barInvocation = 11 + private val fooReturn = 12 + private val barMapPutInvocation = 13 + private val barReturn = 14 @Suppress("unused") - private val bazReturn = 12 + private val bazReturn = 15 @Test fun testOriginal() { @@ -91,7 +94,7 @@ class CoverageInstrumentationTest { assertControlFlow( listOf(constructorReturn, ifFirstBranch, ifEnd) + outerForControlFlow + - listOf(barReturn, fooReturn, selfCheckReturn) + listOf(fooInvocation, barInvocation, barMapPutInvocation, barReturn, fooReturn, selfCheckReturn) ) } -- cgit v1.2.3 From 4fb408bdcbfb32b207c0b92cc98bc3e95c9f7665 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 22 Feb 2021 08:12:56 +0100 Subject: Use NeverZero instead of Saturated Counters for coverage According to https://www.usenix.org/system/files/woot20-paper-fioraldi.pdf, letting coverage 8-bit counters wrap from 255 to 1 on increment performs better than having them stay at 255. In fact, the latter has been observed to hurt overall coverage. --- .../jazzer/instrumentor/CoverageInstrumentationTest.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index 31f40575..e52231cd 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt @@ -109,11 +109,14 @@ class CoverageInstrumentationTest { // Control flows through the first if branch once per run. val takenOnEveryRunEdge = ifFirstBranch - for (i in 1..300) { + var lastCounter = 0.toUByte() + for (i in 1..600) { assertSelfCheck(target) assertEquals(1, MockCoverageMap.mem[takenOnceEdge]) - // Verify that the counter does not overflow. - val expectedCounter = i.coerceAtMost(255).toUByte() + // Verify that the counter increments, but is never zero. + val expectedCounter = (lastCounter + 1U).toUByte().takeUnless { it == 0.toUByte() } + ?: (lastCounter + 2U).toUByte() + lastCounter = expectedCounter val actualCounter = MockCoverageMap.mem[takenOnEveryRunEdge].toUByte() assertEquals(expectedCounter, actualCounter, "After $i runs:") } -- cgit v1.2.3 From 090967e2ff692f097dfadc1eafbf21756bddc2e6 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 24 Feb 2021 08:59:09 +0100 Subject: Extract coverage ID generation out of EdgeCoverageInstrumentor Make EdgeCoverageInstrumentor a class rather than an object and inject a CoverageIdStrategy instead of using a global coverage ID counter. This makes the class more testable and allows the addition of other coverage ID generation strategies in follow-up commits. --- .../jazzer/instrumentor/CoverageInstrumentationTest.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index e52231cd..1a48b365 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt @@ -19,9 +19,7 @@ import java.io.File import kotlin.test.assertEquals private fun applyInstrumentation(bytecode: ByteArray): ByteArray { - EdgeCoverageInstrumentor.resetNextGlobalEdgeIdForTestingOnly() - EdgeCoverageInstrumentor.setCoverageMapClassForTestingOnly(MockCoverageMap::class.java) - return EdgeCoverageInstrumentor.instrument(bytecode) + return EdgeCoverageInstrumentor(0, MockCoverageMap::class.java).instrument(bytecode) } private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract { -- cgit v1.2.3 From d62c07f3c17c02cba18836d09cbeea4c70298e0f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 22 Feb 2021 07:20:18 +0100 Subject: Move agent utils into own package --- .../code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index bd7ddc88..546df13a 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt @@ -57,9 +57,14 @@ class DescriptorUtilsTest { listOf("I", "I"), "Ljava/lang/CharSequence;" ), + Triple( + String::class.java.getConstructor(), + emptyList(), + "()V" + ) ) - for ((method, parameterDescriptors, returnTypeDescriptor) in testCases) { - val descriptor = method.descriptor + for ((executable, parameterDescriptors, returnTypeDescriptor) in testCases) { + val descriptor = executable.descriptor assertEquals(extractParameterTypeDescriptors(descriptor), parameterDescriptors) assertEquals(extractReturnTypeDescriptor(descriptor), returnTypeDescriptor) } -- cgit v1.2.3 From 14ada3769e88861b2214670320c5e20d918dbce4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 22 Feb 2021 07:21:52 +0100 Subject: Hook reflective calls Calls through reflection are the equivalent of indirect calls in C/C++. With this commit, caller-callee relationships (hook ID and hash of method descriptor) are tracked by libFuzzer via the __sanitizer_cov_trace_pc_indir callback. --- .../com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index 546df13a..e7e1feba 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt @@ -14,6 +14,7 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.utils.descriptor import org.junit.Test import kotlin.test.assertEquals @@ -60,7 +61,7 @@ class DescriptorUtilsTest { Triple( String::class.java.getConstructor(), emptyList(), - "()V" + "V" ) ) for ((executable, parameterDescriptors, returnTypeDescriptor) in testCases) { -- cgit v1.2.3 From 312c3ffc4fcff890856c80abcb6b765df5977810 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 4 Mar 2021 15:50:08 +0100 Subject: Fix cases array construction in switch instrumentation The switch instrumentation constructed the array of case labels incorrectly, which lead to libFuzzer performing an out-of-bounds read. --- .../jazzer/instrumentor/MockTraceDataFlowCallbacks.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index f2c63b0c..ad659da0 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java @@ -80,10 +80,10 @@ public class MockTraceDataFlowCallbacks { 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) { + || libfuzzerCaseValues[0] != libfuzzerCaseValues.length - 2 + // bit size of case values is always 32 (int) + || libfuzzerCaseValues[1] != 32) { hookCalls.add("INVALID_SWITCH"); return; } -- cgit v1.2.3 From 82728d401275a2c400ceae280f669596c0f707b2 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sat, 10 Apr 2021 09:03:43 +0200 Subject: Fix instrumentation failure on switch without keys --- .../jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index 428b58e4..48f16e60 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java @@ -72,6 +72,12 @@ public class TraceDataFlowInstrumentationTarget implements DynamicTestContract { results.put("lookupSwitchUninstrumented", true); } + results.put("emptySwitchUninstrumented", false); + switch (switchValue) { + default: + results.put("emptySwitchUninstrumented", true); + } + switch (switchValue) { case 1000: case 1001: -- cgit v1.2.3 From f566095eb92f0dd03379d3a1fba05e0ba38f1e7a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 21 Apr 2021 12:28:26 +0200 Subject: Make PatchTestUtils compatible with Java 8 --- .../code_intelligence/jazzer/instrumentor/PatchTestUtils.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index b803630e..f286d03f 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt @@ -15,7 +15,12 @@ package com.code_intelligence.jazzer.instrumentor fun classToBytecode(targetClass: Class<*>): ByteArray { - return ClassLoader.getSystemClassLoader().getResourceAsStream("${targetClass.name.replace('.', '/')}.class")!!.readAllBytes() + return ClassLoader + .getSystemClassLoader() + .getResourceAsStream("${targetClass.name.replace('.', '/')}.class")!! + .use { + it.readBytes() + } } fun bytecodeToClass(name: String, bytecode: ByteArray): Class<*> { @@ -26,7 +31,8 @@ fun bytecodeToClass(name: String, bytecode: ByteArray): Class<*> { * 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) { +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) -- cgit v1.2.3 From 94694a4cbe40fb3c01f9aa418e9b9ca6a7246aa1 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 21 Apr 2021 15:01:06 +0200 Subject: Fix: Insert method probes after invocations Coverage probes for method invocations should be emitted after the invocation as their purpose is to track whether the method has returned. This is in line with how unpatched JaCoCo instrumentes lines with method calls: It emits the probe when it visits the line number label, which happens after all instructions associated with the line have been processed. --- .../jazzer/instrumentor/CoverageInstrumentationTest.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index 1a48b365..15c88f4c 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt @@ -53,12 +53,12 @@ class CoverageInstrumentationTest { private val innerForBodyIfSecondRun = 5 private val innerForIncrementCounter = 7 private val outerForIncrementCounter = 8 - private val fooInvocation = 9 - private val selfCheckReturn = 10 - private val barInvocation = 11 - private val fooReturn = 12 - private val barMapPutInvocation = 13 - private val barReturn = 14 + private val afterFooInvocation = 9 + private val beforeReturn = 10 + private val fooAfterBarInvocation = 11 + private val fooBeforeReturn = 12 + private val barAfterMapPutInvocation = 13 + private val barBeforeReturn = 14 @Suppress("unused") private val bazReturn = 15 @@ -92,7 +92,11 @@ class CoverageInstrumentationTest { assertControlFlow( listOf(constructorReturn, ifFirstBranch, ifEnd) + outerForControlFlow + - listOf(fooInvocation, barInvocation, barMapPutInvocation, barReturn, fooReturn, selfCheckReturn) + listOf( + barAfterMapPutInvocation, barBeforeReturn, + fooAfterBarInvocation, fooBeforeReturn, + afterFooInvocation, beforeReturn + ) ) } -- cgit v1.2.3 From ca507ade4dc4c811fdb298efd2ea21aae80333f0 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 3 May 2021 16:29:46 +0200 Subject: Support multiple hooks per method BEFORE and AFTER hooks can be applied to the same method by replacing the call instruction in the BEFORE hook instrumentation with the full instrumentation for the AFTER hook. While this is not very useful for instance methods, where the same can be achieved with a REPLACE hook, it is the only way to modify arguments to a constructor call while still being able to act on the fully initialized object. This commit adds this functionality by cycling through the three types in order and nesting the AFTER bytecode into the BEFORE bytecode if both exist. This adds a test verifying that: * BEFORE and AFTER hooks can be combined. * If an argument is modified in a BEFORE hook, the modified argument is received in the corresponding AFTER hook. * AFTER hooks on constructors receive the fully initialized this object (added in a previous commit). --- .../jazzer/instrumentor/AfterHooks.java | 27 ++++++++++++++++++++++ .../jazzer/instrumentor/AfterHooksTarget.java | 7 ++++++ .../instrumentor/AfterHooksTargetContract.java | 2 ++ 3 files changed, 36 insertions(+) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index 50588106..fff7e575 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java @@ -19,12 +19,15 @@ 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(); } @@ -57,4 +60,28 @@ public class AfterHooks { // 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 = "") + public static void + patchStringBuilderBeforeInit( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + arguments[0] = "hunter3"; + } + + @MethodHook( + type = HookType.AFTER, targetClassName = "java.lang.StringBuilder", targetMethod = "") + public static void + patchStringBuilderInit( + MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) { + String secret = ((StringBuilder) thisObject).toString(); + // Verify that the argument passed to this AFTER hook agrees with the argument passed to the + // StringBuilder constructor, which has been modified by the BEFORE hook. + if (secret.equals(arguments[0])) { + // Verify that the argument has been modified to the correct value "hunter3". + instance.verifyThirdSecret(secret); + } + } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java index e6a0a106..a47b03a5 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java @@ -45,6 +45,9 @@ public class AfterHooksTarget implements AfterHooksTargetContract { verifySecondSecret("not_secret_at_all"); getSecondSecret(); + verifyThirdSecret("not_the_secret"); + new StringBuilder("not_hunter3"); + return results; } @@ -75,4 +78,8 @@ public class AfterHooksTarget implements AfterHooksTargetContract { public void verifySecondSecret(String secret) { results.put("verifySecondSecret", secret.equals("hunter2!")); } + + public void verifyThirdSecret(String secret) { + results.put("verifyThirdSecret", secret.equals("hunter3")); + } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java index fb833c32..cb12b148 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java @@ -24,4 +24,6 @@ public interface AfterHooksTargetContract extends DynamicTestContract { void verifyFirstSecret(String secret); void verifySecondSecret(String secret); + + void verifyThirdSecret(String secret); } -- cgit v1.2.3 From 6567515b11f07bfa95c26ab120fd68664a5ce6ad Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 3 May 2021 16:29:46 +0200 Subject: Apply automated code cleanups --- .../com/code_intelligence/jazzer/instrumentor/AfterHooks.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index fff7e575..f8d6782c 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java @@ -45,8 +45,8 @@ public class AfterHooks { 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 { + patchGetFirstSecret( + MethodHandle method, Object thisObject, Object[] arguments, int hookId, String returnValue) { // Use the returned secret to pass the test. ((AfterHooksTargetContract) thisObject).verifyFirstSecret(returnValue); } @@ -55,8 +55,8 @@ public class AfterHooks { 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 { + patchGetSecondSecret( + MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) { // Use the returned secret to pass the test. ((AfterHooksTargetContract) thisObject).verifySecondSecret((String) returnValue); } -- cgit v1.2.3 From 78623a5247eeb7b36199ddc9bcb593049708a87a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 28 Jul 2021 15:05:26 +0200 Subject: Update rules_kotlin --- .../jazzer/instrumentor/BUILD.bazel | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index e745a662..605a8363 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -1,11 +1,11 @@ -load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library", "kt_jvm_test") +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test") kt_jvm_library( name = "patch_test_utils", - srcs = glob([ + srcs = [ "DynamicTestContract.java", "PatchTestUtils.kt", - ]), + ], ) kt_jvm_test( @@ -16,7 +16,7 @@ kt_jvm_test( "TraceDataFlowInstrumentationTarget.java", "TraceDataFlowInstrumentationTest.kt", ], - friends = [ + associates = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor", ], test_class = "com.code_intelligence.jazzer.instrumentor.TraceDataFlowInstrumentationTest", @@ -36,7 +36,7 @@ kt_jvm_test( "CoverageInstrumentationTest.kt", "MockCoverageMap.java", ], - friends = [ + associates = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor", ], test_class = "com.code_intelligence.jazzer.instrumentor.CoverageInstrumentationTest", @@ -53,7 +53,7 @@ kt_jvm_test( srcs = [ "DescriptorUtilsTest.kt", ], - friends = [ + associates = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor", ], test_class = "com.code_intelligence.jazzer.instrumentor.DescriptorUtilsTest", @@ -71,7 +71,7 @@ kt_jvm_test( "InvalidHookMocks.java", "ValidHookMocks.java", ], - friends = [ + associates = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor", ], test_class = "com.code_intelligence.jazzer.instrumentor.HookValidationTest", @@ -91,7 +91,7 @@ kt_jvm_test( "AfterHooksTarget.java", "AfterHooksTargetContract.java", ], - friends = [ + associates = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor", ], test_class = "com.code_intelligence.jazzer.instrumentor.AfterHookTest", @@ -112,7 +112,7 @@ kt_jvm_test( "BeforeHooksTarget.java", "BeforeHooksTargetContract.java", ], - friends = [ + associates = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor", ], test_class = "com.code_intelligence.jazzer.instrumentor.BeforeHookTest", @@ -133,7 +133,7 @@ kt_jvm_test( "ReplaceHooksTarget.java", "ReplaceHooksTargetContract.java", ], - friends = [ + associates = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor", ], test_class = "com.code_intelligence.jazzer.instrumentor.ReplaceHookTest", -- cgit v1.2.3 From 4a3a6395fcd051fa74b60bcede25987f2e3d3fe4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 14 Oct 2021 22:00:20 +0200 Subject: Wrap kt_jvm_test in java_test for Windows compatibility --- .../code_intelligence/jazzer/instrumentor/BUILD.bazel | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') 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 index 605a8363..472d2b98 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -1,4 +1,5 @@ -load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test") +load("//bazel:kotlin.bzl", "wrapped_kt_jvm_test") +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") kt_jvm_library( name = "patch_test_utils", @@ -8,7 +9,7 @@ kt_jvm_library( ], ) -kt_jvm_test( +wrapped_kt_jvm_test( name = "trace_data_flow_instrumentation_test", size = "small", srcs = [ @@ -27,7 +28,7 @@ kt_jvm_test( ], ) -kt_jvm_test( +wrapped_kt_jvm_test( name = "coverage_instrumentation_test", size = "small", srcs = [ @@ -47,7 +48,7 @@ kt_jvm_test( ], ) -kt_jvm_test( +wrapped_kt_jvm_test( name = "descriptor_utils_test", size = "small", srcs = [ @@ -63,7 +64,7 @@ kt_jvm_test( ], ) -kt_jvm_test( +wrapped_kt_jvm_test( name = "hook_validation_test", size = "small", srcs = [ @@ -82,7 +83,7 @@ kt_jvm_test( ], ) -kt_jvm_test( +wrapped_kt_jvm_test( name = "after_hooks_patch_test", size = "small", srcs = [ @@ -103,7 +104,7 @@ kt_jvm_test( ], ) -kt_jvm_test( +wrapped_kt_jvm_test( name = "before_hooks_patch_test", size = "small", srcs = [ @@ -124,7 +125,7 @@ kt_jvm_test( ], ) -kt_jvm_test( +wrapped_kt_jvm_test( name = "replace_hooks_patch_test", size = "small", srcs = [ -- cgit v1.2.3 From b8509959fe239b81cb3fd9a1e219c8f285c4c25d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 19 Sep 2021 10:57:12 +0200 Subject: Add basic generic autofuzz and consume functionality --- .../code_intelligence/jazzer/autofuzz/BUILD.bazel | 16 +++++ .../jazzer/autofuzz/MetaTest.java | 78 ++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel new file mode 100644 index 00000000..c25d7147 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_java//java:defs.bzl", "java_test") + +java_test( + name = "MetaTest", + size = "small", + srcs = [ + "MetaTest.java", + ], + test_class = "com.code_intelligence.jazzer.autofuzz.MetaTest", + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", + "@maven//:com_mikesamuel_json_sanitizer", + "@maven//:junit_junit", + ], +) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java new file mode 100644 index 00000000..1e1fe09e --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.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.autofuzz; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.google.json.JsonSanitizer; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import org.junit.Test; + +public class MetaTest { + private static FuzzedDataProvider makeFuzzedDataProvider(List replies) { + try { + try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) { + try (ObjectOutputStream out = new ObjectOutputStream(bout)) { + out.writeObject(new ArrayList<>(replies)); + String base64 = Base64.getEncoder().encodeToString(bout.toByteArray()); + return new CannedFuzzedDataProvider(base64); + } + } + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public static boolean isFive(int arg) { + return arg == 5; + } + + @Test + public void testConsume() { + FuzzedDataProvider data = makeFuzzedDataProvider(Collections.singletonList(5)); + assertEquals(5, Meta.consume(data, int.class)); + } + + @Test + public void testAutofuzz() { + FuzzedDataProvider data = makeFuzzedDataProvider(Arrays.asList(5, + 6, // remainingBytes + "foo", + 6, // remainingBytes + "bar", + 8, // remainingBytes + "buzz", + 6, // remainingBytes + "jazzer", + 6, // remainingBytes + "jazzer")); + assertTrue(Meta.autofuzz(data, MetaTest::isFive)); + assertEquals("foobar", Meta.autofuzz(data, String::concat)); + assertEquals("fizzbuzz", Meta.autofuzz(data, "fizz" ::concat)); + assertEquals("jazzer", Meta.autofuzz(data, (Function1) String::new)); + assertEquals( + "\"jazzer\"", Meta.autofuzz(data, (Function1) JsonSanitizer::sanitize)); + } +} -- cgit v1.2.3 From 8311978af16a7e0a9661457b13423adc47540dc8 Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Thu, 14 Oct 2021 11:49:00 +0200 Subject: refactor CannedFuzzedDataProvider so that it can is useful for multiple tests --- .../jazzer/autofuzz/MetaTest.java | 26 ++++------------------ 1 file changed, 4 insertions(+), 22 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index 1e1fe09e..c491ed66 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -20,44 +20,26 @@ import static org.junit.Assert.assertTrue; import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.google.json.JsonSanitizer; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.util.ArrayList; + import java.util.Arrays; -import java.util.Base64; import java.util.Collections; -import java.util.List; + import org.junit.Test; public class MetaTest { - private static FuzzedDataProvider makeFuzzedDataProvider(List replies) { - try { - try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) { - try (ObjectOutputStream out = new ObjectOutputStream(bout)) { - out.writeObject(new ArrayList<>(replies)); - String base64 = Base64.getEncoder().encodeToString(bout.toByteArray()); - return new CannedFuzzedDataProvider(base64); - } - } - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - public static boolean isFive(int arg) { return arg == 5; } @Test public void testConsume() { - FuzzedDataProvider data = makeFuzzedDataProvider(Collections.singletonList(5)); + FuzzedDataProvider data = CannedFuzzedDataProvider.create(Collections.singletonList(5)); assertEquals(5, Meta.consume(data, int.class)); } @Test public void testAutofuzz() { - FuzzedDataProvider data = makeFuzzedDataProvider(Arrays.asList(5, + FuzzedDataProvider data = CannedFuzzedDataProvider.create(Arrays.asList(5, 6, // remainingBytes "foo", 6, // remainingBytes -- cgit v1.2.3 From bc8cb4614b97cc1d905e5d1e083ea084ddfaa9af Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Thu, 14 Oct 2021 15:25:34 +0200 Subject: Create Objects from classes implementing an interface --- .../code_intelligence/jazzer/autofuzz/BUILD.bazel | 14 +++ .../jazzer/autofuzz/InterfaceCreationTest.java | 106 +++++++++++++++++++++ .../jazzer/autofuzz/MetaTest.java | 2 - 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel index c25d7147..454e8e3f 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel @@ -14,3 +14,17 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "InterfaceCreationTest", + size = "small", + srcs = [ + "InterfaceCreationTest.java", + ], + test_class = "com.code_intelligence.jazzer.autofuzz.InterfaceCreationTest", + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", + "@maven//:junit_junit", + ], +) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java new file mode 100644 index 00000000..d15cfd89 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.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.autofuzz; + +import static org.junit.Assert.assertEquals; + +import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.util.Arrays; +import java.util.Objects; +import org.junit.Test; + +interface InterfaceA { + void foo(); + + void bar(); +} + +abstract class ClassA1 implements InterfaceA { + @Override + public void foo() {} +} + +class ClassB1 extends ClassA1 { + int n; + + public ClassB1(int _n) { + n = _n; + } + + @Override + public void bar() {} + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ClassB1 classB1 = (ClassB1) o; + return n == classB1.n; + } + + @Override + public int hashCode() { + return Objects.hash(n); + } +} + +class ClassB2 implements InterfaceA { + String s; + + public ClassB2(String _s) { + s = _s; + } + + @Override + public void foo() {} + + @Override + public void bar() {} + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ClassB2 classB2 = (ClassB2) o; + return Objects.equals(s, classB2.s); + } + + @Override + public int hashCode() { + return Objects.hash(s); + } +} + +public class InterfaceCreationTest { + FuzzedDataProvider data = CannedFuzzedDataProvider.create(Arrays.asList(0, // pick ClassB1 + 0, // pick first constructor + 5, // arg for ClassB1 constructor + 1, // pick ClassB2 + 0, // pick first constructor + 8, // remaining bytes + "test" // arg for ClassB2 constructor + )); + + @Test + public void testConsumeInterface() { + assertEquals(Meta.consume(data, InterfaceA.class), new ClassB1(5)); + assertEquals(Meta.consume(data, InterfaceA.class), new ClassB2("test")); + } +} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index c491ed66..52ec1eb0 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -20,10 +20,8 @@ import static org.junit.Assert.assertTrue; import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.google.json.JsonSanitizer; - import java.util.Arrays; import java.util.Collections; - import org.junit.Test; public class MetaTest { -- cgit v1.2.3 From bebc491c60c0b26313ed43387411627817ef1d0c Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Fri, 15 Oct 2021 13:32:38 +0200 Subject: Create object with nested builder class --- .../code_intelligence/jazzer/autofuzz/BUILD.bazel | 14 +++ .../jazzer/autofuzz/BuilderPatternTest.java | 103 +++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel index 454e8e3f..84d86e2a 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel @@ -28,3 +28,17 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "BuilderPatternTest", + size = "small", + srcs = [ + "BuilderPatternTest.java", + ], + test_class = "com.code_intelligence.jazzer.autofuzz.BuilderPatternTest", + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", + "@maven//:junit_junit", + ], +) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java new file mode 100644 index 00000000..2389f83e --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java @@ -0,0 +1,103 @@ +// Copyright 2021 Code Intelligence GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.code_intelligence.jazzer.autofuzz; + +import static org.junit.Assert.assertEquals; + +import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.util.Arrays; +import java.util.Objects; +import org.junit.Test; + +class Employee { + private final String firstName; + private final String lastName; + private final String jobTitle; + private final int age; + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Employee hero = (Employee) o; + return age == hero.age && Objects.equals(firstName, hero.firstName) + && Objects.equals(lastName, hero.lastName) && Objects.equals(jobTitle, hero.jobTitle); + } + + @Override + public int hashCode() { + return Objects.hash(firstName, lastName, jobTitle, age); + } + + private Employee(Builder builder) { + this.jobTitle = builder.jobTitle; + this.firstName = builder.firstName; + this.lastName = builder.lastName; + this.age = builder.age; + } + + public static class Builder { + private final String firstName; + private final String lastName; + private String jobTitle; + private int age; + + public Builder(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public Builder withAge(int age) { + this.age = age; + return this; + } + + public Builder withJobTitle(String jobTitle) { + this.jobTitle = jobTitle; + return this; + } + + public Employee build() { + return new Employee(this); + } + } +} + +public class BuilderPatternTest { + FuzzedDataProvider data = + CannedFuzzedDataProvider.create(Arrays.asList(0, // Select the first Builder + 2, // Select two Builder methods returning a builder object (fluent design) + 0, // Select the first build method + 0, // pick the first remaining builder method (withAge) + 0, // pick the first remaining builder method (withJobTitle) + 0, // pick the first build method + 6, // remaining bytes + "foo", // firstName + 6, // remaining bytes + "bar", // lastName + 20, // age + 6, // remaining bytes + "baz" // jobTitle + )); + + @Test + public void testBuilderPattern() { + assertEquals(Meta.consume(data, Employee.class), + new Employee.Builder("foo", "bar").withAge(20).withJobTitle("baz").build()); + } +} -- cgit v1.2.3 From 9a804bb4272b35b10028a7dc246c6c63176e0eee Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Sun, 17 Oct 2021 21:49:09 +0200 Subject: Handle creating classes with a default constructor and setter methods --- .../code_intelligence/jazzer/autofuzz/BUILD.bazel | 15 ++++++ .../jazzer/autofuzz/SettersTest.java | 43 +++++++++++++++++ .../jazzer/autofuzz/testdata/BUILD.bazel | 5 ++ .../autofuzz/testdata/EmployeeWithSetters.java | 56 ++++++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel index 84d86e2a..cc71e09d 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel @@ -42,3 +42,18 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "SettersTest", + size = "small", + srcs = [ + "SettersTest.java", + ], + test_class = "com.code_intelligence.jazzer.autofuzz.SettersTest", + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", + "//agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata:test_data", + "@maven//:junit_junit", + ], +) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java new file mode 100644 index 00000000..59a46636 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java @@ -0,0 +1,43 @@ +// Copyright 2021 Code Intelligence GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.code_intelligence.jazzer.autofuzz; + +import static org.junit.Assert.assertEquals; + +import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters; +import java.util.Arrays; +import org.junit.Test; + +public class SettersTest { + FuzzedDataProvider data = + CannedFuzzedDataProvider.create(Arrays.asList(0, // pick first constructor + 2, // pick two setters + 1, // pick second setter + 0, // pick first setter + 6, // remaining bytes + "foo", // setFirstName + 26 // setAge + )); + + @Test + public void testEmptyConstructorWithSetters() { + EmployeeWithSetters employee = new EmployeeWithSetters(); + employee.setFirstName("foo"); + employee.setAge(26); + assertEquals(Meta.consume(data, EmployeeWithSetters.class), employee); + } +} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel new file mode 100644 index 00000000..c2c68803 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel @@ -0,0 +1,5 @@ +java_library( + name = "test_data", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], +) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java new file mode 100644 index 00000000..2c76a61f --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java @@ -0,0 +1,56 @@ +// Copyright 2021 Code Intelligence GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.code_intelligence.jazzer.autofuzz.testdata; + +import java.util.Objects; + +public class EmployeeWithSetters { + private String firstName; + private String lastName; + private String jobTitle; + private int age; + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + EmployeeWithSetters hero = (EmployeeWithSetters) o; + return age == hero.age && Objects.equals(firstName, hero.firstName) + && Objects.equals(lastName, hero.lastName) && Objects.equals(jobTitle, hero.jobTitle); + } + + @Override + public int hashCode() { + return Objects.hash(firstName, lastName, jobTitle, age); + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public void setJobTitle(String jobTitle) { + this.jobTitle = jobTitle; + } + + public void setAge(int age) { + this.age = age; + } +} -- cgit v1.2.3 From aada4f8b0b8e8fcadc3139fdd8e970e013d395c0 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 18 Oct 2021 13:33:23 +0200 Subject: Sometimes return null for non-primitive types --- .../jazzer/autofuzz/BuilderPatternTest.java | 6 +++++- .../jazzer/autofuzz/InterfaceCreationTest.java | 22 ++++++++++++++-------- .../jazzer/autofuzz/MetaTest.java | 5 +++++ .../jazzer/autofuzz/SettersTest.java | 6 ++++-- 4 files changed, 28 insertions(+), 11 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java index 2389f83e..4f59832f 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java @@ -80,17 +80,21 @@ class Employee { public class BuilderPatternTest { FuzzedDataProvider data = - CannedFuzzedDataProvider.create(Arrays.asList(0, // Select the first Builder + CannedFuzzedDataProvider.create(Arrays.asList((byte) 1, // do not return null + 0, // Select the first Builder 2, // Select two Builder methods returning a builder object (fluent design) 0, // Select the first build method 0, // pick the first remaining builder method (withAge) 0, // pick the first remaining builder method (withJobTitle) 0, // pick the first build method + (byte) 1, // do not return null 6, // remaining bytes "foo", // firstName + (byte) 1, // do not return null 6, // remaining bytes "bar", // lastName 20, // age + (byte) 1, // do not return null 6, // remaining bytes "baz" // jobTitle )); diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java index d15cfd89..2858d68d 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java @@ -89,14 +89,20 @@ class ClassB2 implements InterfaceA { } public class InterfaceCreationTest { - FuzzedDataProvider data = CannedFuzzedDataProvider.create(Arrays.asList(0, // pick ClassB1 - 0, // pick first constructor - 5, // arg for ClassB1 constructor - 1, // pick ClassB2 - 0, // pick first constructor - 8, // remaining bytes - "test" // arg for ClassB2 constructor - )); + FuzzedDataProvider data = + CannedFuzzedDataProvider.create(Arrays.asList((byte) 1, // do not return null + 0, // pick ClassB1 + (byte) 1, // do not return null + 0, // pick first constructor + 5, // arg for ClassB1 constructor + (byte) 1, // do not return null + 1, // pick ClassB2 + (byte) 1, // do not return null + 0, // pick first constructor + (byte) 1, // do not return null + 8, // remaining bytes + "test" // arg for ClassB2 constructor + )); @Test public void testConsumeInterface() { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index 52ec1eb0..a3c851be 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -38,14 +38,19 @@ public class MetaTest { @Test public void testAutofuzz() { FuzzedDataProvider data = CannedFuzzedDataProvider.create(Arrays.asList(5, + (byte) 1, // do not return null 6, // remainingBytes "foo", + (byte) 1, // do not return null 6, // remainingBytes "bar", + (byte) 1, // do not return null 8, // remainingBytes "buzz", + (byte) 1, // do not return null 6, // remainingBytes "jazzer", + (byte) 1, // do not return null 6, // remainingBytes "jazzer")); assertTrue(Meta.autofuzz(data, MetaTest::isFive)); diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java index 59a46636..5403b19e 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java @@ -23,11 +23,13 @@ import java.util.Arrays; import org.junit.Test; public class SettersTest { - FuzzedDataProvider data = - CannedFuzzedDataProvider.create(Arrays.asList(0, // pick first constructor + FuzzedDataProvider data = CannedFuzzedDataProvider.create( + Arrays.asList((byte) 1, // do not return null for EmployeeWithSetters + 0, // pick first constructor 2, // pick two setters 1, // pick second setter 0, // pick first setter + (byte) 1, // do not return null for String 6, // remaining bytes "foo", // setFirstName 26 // setAge -- cgit v1.2.3 From 908ee7f9505c684b0f6ddd66165af079d9aea315 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 18 Oct 2021 17:41:45 +0200 Subject: Do not find our own classes This could pretty wild AutofillErrors. --- agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel index cc71e09d..2414c470 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel @@ -21,6 +21,10 @@ java_test( srcs = [ "InterfaceCreationTest.java", ], + env = { + # Also consider implementing classes from com.code_intelligence.jazzer.*. + "JAZZER_AUTOFUZZ_TESTING": "1", + }, test_class = "com.code_intelligence.jazzer.autofuzz.InterfaceCreationTest", deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", -- cgit v1.2.3 From 4d925f2464b4ccf759bd4d957d5db9aa749e85b3 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 21 Oct 2021 11:06:25 +0200 Subject: Remove remaining loads of @rules_java --- agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel | 2 -- 1 file changed, 2 deletions(-) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel index 2414c470..82de3bc9 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel @@ -1,5 +1,3 @@ -load("@rules_java//java:defs.bzl", "java_test") - java_test( name = "MetaTest", size = "small", -- cgit v1.2.3 From d4a1ce3f2e227bc30bcd2d97b623c193197e293e Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 21 Oct 2021 12:06:58 +0200 Subject: Add Jazzer.consume to the Jazzer API This requires moving AutofuzzConstructionException to api package. --- .../code_intelligence/jazzer/api/AutofuzzTest.java | 45 ++++++++++++++++++++++ .../com/code_intelligence/jazzer/api/BUILD.bazel | 21 ++++++++++ 2 files changed, 66 insertions(+) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java new file mode 100644 index 00000000..b4696ce8 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.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.api; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.Arrays; +import java.util.Collections; +import org.junit.Test; + +public class AutofuzzTest { + public interface UnimplementedInterface {} + + public interface ImplementedInterface {} + public static class ImplementingClass implements ImplementedInterface {} + + @Test + public void testConsume() { + FuzzedDataProvider data = CannedFuzzedDataProvider.create( + Arrays.asList((byte) 1 /* do not return null */, 0 /* first class on the classpath */, + (byte) 1 /* do not return null */, 0 /* first constructor */)); + ImplementedInterface result = Jazzer.consume(data, ImplementedInterface.class); + assertNotNull(result); + } + + @Test + public void testConsumeFailsWithoutException() { + FuzzedDataProvider data = CannedFuzzedDataProvider.create(Collections.singletonList( + (byte) 1 /* do not return null without searching for implementing classes */)); + assertNull(Jazzer.consume(data, UnimplementedInterface.class)); + } +} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel new file mode 100644 index 00000000..9192ff77 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel @@ -0,0 +1,21 @@ +java_test( + name = "AutofuzzTest", + size = "small", + srcs = [ + "AutofuzzTest.java", + ], + env = { + # Also consider implementing classes from com.code_intelligence.jazzer.*. + "JAZZER_AUTOFUZZ_TESTING": "1", + }, + test_class = "com.code_intelligence.jazzer.api.AutofuzzTest", + runtime_deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", + # Needed for JazzerInternal. + "//agent/src/main/java/com/code_intelligence/jazzer/runtime", + ], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "@maven//:junit_junit", + ], +) -- cgit v1.2.3 From 506d2115a10a0e292a11b36c7e99eda6cd21096f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 21 Oct 2021 13:28:47 +0200 Subject: Move ConsumerN and FunctionN to api package --- agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java | 1 + 1 file changed, 1 insertion(+) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index a3c851be..25891aa5 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; +import com.code_intelligence.jazzer.api.Function1; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.google.json.JsonSanitizer; import java.util.Arrays; -- cgit v1.2.3 From afc33e3718f0f399c31dc47001ae90b7eee1a714 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 21 Oct 2021 11:22:26 +0200 Subject: Add Jazzer.autofuzz(FuzzedDataProvider, Function1) to the Jazzer API Also moves AutofuzzInvocationException to the api package. --- .../code_intelligence/jazzer/api/AutofuzzTest.java | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java index b4696ce8..cd2ac875 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java @@ -14,8 +14,10 @@ package com.code_intelligence.jazzer.api; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; import java.util.Arrays; import java.util.Collections; @@ -27,6 +29,14 @@ public class AutofuzzTest { public interface ImplementedInterface {} public static class ImplementingClass implements ImplementedInterface {} + private static boolean implIsNotNull(ImplementedInterface impl) { + return impl != null; + } + + private static boolean implIsNotNull(UnimplementedInterface impl) { + return impl != null; + } + @Test public void testConsume() { FuzzedDataProvider data = CannedFuzzedDataProvider.create( @@ -42,4 +52,26 @@ public class AutofuzzTest { (byte) 1 /* do not return null without searching for implementing classes */)); assertNull(Jazzer.consume(data, UnimplementedInterface.class)); } + + @Test + public void testAutofuzz() { + FuzzedDataProvider data = CannedFuzzedDataProvider.create( + Arrays.asList((byte) 1 /* do not return null */, 0 /* first class on the classpath */, + (byte) 1 /* do not return null */, 0 /* first constructor */)); + assertEquals(Boolean.TRUE, + Jazzer.autofuzz(data, (Function1) AutofuzzTest::implIsNotNull)); + } + + @Test + public void testAutofuzzFailsWithException() { + FuzzedDataProvider data = CannedFuzzedDataProvider.create( + Collections.singletonList((byte) 1 /* do not return null */)); + try { + Jazzer.autofuzz(data, (Function1) AutofuzzTest::implIsNotNull); + } catch (AutofuzzConstructionException e) { + // Pass. + return; + } + fail("should have thrown an AutofuzzConstructionException"); + } } -- cgit v1.2.3 From 71940af7a9c57d00f6a5ec3a9e510d2f47c014e6 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 21 Oct 2021 14:02:07 +0200 Subject: Add Jazzer.autofuzz(FuzzedDataProvider, Consumer{1,2,3,4,5}) to the API Also add a test to catch potential copy&paste issues. --- .../code_intelligence/jazzer/api/AutofuzzTest.java | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java index cd2ac875..66a85db6 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java @@ -37,6 +37,13 @@ public class AutofuzzTest { return impl != null; } + private static void checkAllTheArguments( + String arg1, int arg2, byte arg3, ImplementedInterface arg4) { + if (!arg1.equals("foobar") || arg2 != 42 || arg3 != 5 || arg4 == null) { + throw new IllegalArgumentException(); + } + } + @Test public void testConsume() { FuzzedDataProvider data = CannedFuzzedDataProvider.create( @@ -74,4 +81,27 @@ public class AutofuzzTest { } fail("should have thrown an AutofuzzConstructionException"); } + + @Test + public void testAutofuzzConsumer() { + FuzzedDataProvider data = CannedFuzzedDataProvider.create( + Arrays.asList((byte) 1 /* do not return null */, 6 /* string length */, "foobar", 42, + (byte) 5, (byte) 1 /* do not return null */, 0 /* first class on the classpath */, + (byte) 1 /* do not return null */, 0 /* first constructor */)); + Jazzer.autofuzz(data, AutofuzzTest::checkAllTheArguments); + } + + @Test + public void testAutofuzzConsumerThrowsException() { + FuzzedDataProvider data = + CannedFuzzedDataProvider.create(Arrays.asList((byte) 1 /* do not return null */, + 6 /* string length */, "foobar", 42, (byte) 5, (byte) 0 /* *do* return null */)); + try { + Jazzer.autofuzz(data, AutofuzzTest::checkAllTheArguments); + } catch (IllegalArgumentException e) { + // Pass. + return; + } + fail("should have thrown an IllegalArgumentException"); + } } -- cgit v1.2.3 From 70e9992f37217426952ff48c952bc95e8fc56e34 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 29 Oct 2021 10:14:06 +0200 Subject: Implement code generation for consume and autofuzz Method/Constructor are not yet implemented. --- .../code_intelligence/jazzer/autofuzz/BUILD.bazel | 18 ++- .../jazzer/autofuzz/BuilderPatternTest.java | 46 ++++--- .../jazzer/autofuzz/InterfaceCreationTest.java | 39 +++--- .../jazzer/autofuzz/MetaTest.java | 135 +++++++++++++++++---- .../jazzer/autofuzz/SettersTest.java | 30 +++-- .../jazzer/autofuzz/TestHelpers.java | 85 +++++++++++++ 6 files changed, 261 insertions(+), 92 deletions(-) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java (limited to 'agent/src/test/java/com/code_intelligence/jazzer') diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel index 82de3bc9..f8448f01 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel @@ -6,6 +6,7 @@ java_test( ], test_class = "com.code_intelligence.jazzer.autofuzz.MetaTest", deps = [ + ":test_helpers", "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", "@maven//:com_mikesamuel_json_sanitizer", @@ -25,8 +26,7 @@ java_test( }, test_class = "com.code_intelligence.jazzer.autofuzz.InterfaceCreationTest", deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/api", - "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", + ":test_helpers", "@maven//:junit_junit", ], ) @@ -39,8 +39,7 @@ java_test( ], test_class = "com.code_intelligence.jazzer.autofuzz.BuilderPatternTest", deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/api", - "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", + ":test_helpers", "@maven//:junit_junit", ], ) @@ -52,10 +51,19 @@ java_test( "SettersTest.java", ], test_class = "com.code_intelligence.jazzer.autofuzz.SettersTest", + deps = [ + ":test_helpers", + "//agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata:test_data", + "@maven//:junit_junit", + ], +) + +java_library( + name = "test_helpers", + srcs = ["TestHelpers.java"], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", - "//agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata:test_data", "@maven//:junit_junit", ], ) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java index 4f59832f..a602d712 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java @@ -14,10 +14,8 @@ package com.code_intelligence.jazzer.autofuzz; -import static org.junit.Assert.assertEquals; +import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase; -import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; import java.util.Arrays; import java.util.Objects; import org.junit.Test; @@ -79,29 +77,27 @@ class Employee { } public class BuilderPatternTest { - FuzzedDataProvider data = - CannedFuzzedDataProvider.create(Arrays.asList((byte) 1, // do not return null - 0, // Select the first Builder - 2, // Select two Builder methods returning a builder object (fluent design) - 0, // Select the first build method - 0, // pick the first remaining builder method (withAge) - 0, // pick the first remaining builder method (withJobTitle) - 0, // pick the first build method - (byte) 1, // do not return null - 6, // remaining bytes - "foo", // firstName - (byte) 1, // do not return null - 6, // remaining bytes - "bar", // lastName - 20, // age - (byte) 1, // do not return null - 6, // remaining bytes - "baz" // jobTitle - )); - @Test public void testBuilderPattern() { - assertEquals(Meta.consume(data, Employee.class), - new Employee.Builder("foo", "bar").withAge(20).withJobTitle("baz").build()); + consumeTestCase(new Employee.Builder("foo", "bar").withAge(20).withJobTitle("baz").build(), + "new com.code_intelligence.jazzer.autofuzz.Employee.Builder(\"foo\", \"bar\").withAge(20).withJobTitle(\"baz\").build()", + Arrays.asList((byte) 1, // do not return null + 0, // Select the first Builder + 2, // Select two Builder methods returning a builder object (fluent design) + 0, // Select the first build method + 0, // pick the first remaining builder method (withAge) + 0, // pick the first remaining builder method (withJobTitle) + 0, // pick the first build method + (byte) 1, // do not return null + 6, // remaining bytes + "foo", // firstName + (byte) 1, // do not return null + 6, // remaining bytes + "bar", // lastName + 20, // age + (byte) 1, // do not return null + 6, // remaining bytes + "baz" // jobTitle + )); } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java index 2858d68d..4d85ca6c 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java @@ -14,10 +14,8 @@ package com.code_intelligence.jazzer.autofuzz; -import static org.junit.Assert.assertEquals; +import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase; -import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; import java.util.Arrays; import java.util.Objects; import org.junit.Test; @@ -89,24 +87,25 @@ class ClassB2 implements InterfaceA { } public class InterfaceCreationTest { - FuzzedDataProvider data = - CannedFuzzedDataProvider.create(Arrays.asList((byte) 1, // do not return null - 0, // pick ClassB1 - (byte) 1, // do not return null - 0, // pick first constructor - 5, // arg for ClassB1 constructor - (byte) 1, // do not return null - 1, // pick ClassB2 - (byte) 1, // do not return null - 0, // pick first constructor - (byte) 1, // do not return null - 8, // remaining bytes - "test" // arg for ClassB2 constructor - )); - @Test public void testConsumeInterface() { - assertEquals(Meta.consume(data, InterfaceA.class), new ClassB1(5)); - assertEquals(Meta.consume(data, InterfaceA.class), new ClassB2("test")); + consumeTestCase(InterfaceA.class, new ClassB1(5), + "(com.code_intelligence.jazzer.autofuzz.InterfaceA) new com.code_intelligence.jazzer.autofuzz.ClassB1(5)", + Arrays.asList((byte) 1, // do not return null + 0, // pick ClassB1 + (byte) 1, // do not return null + 0, // pick first constructor + 5 // arg for ClassB1 constructor + )); + consumeTestCase(InterfaceA.class, new ClassB2("test"), + "(com.code_intelligence.jazzer.autofuzz.InterfaceA) new com.code_intelligence.jazzer.autofuzz.ClassB2(\"test\")", + Arrays.asList((byte) 1, // do not return null + 1, // pick ClassB2 + (byte) 1, // do not return null + 0, // pick first constructor + (byte) 1, // do not return null + 8, // remaining bytes + "test" // arg for ClassB2 constructor + )); } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index 25891aa5..0615e9ae 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -14,13 +14,14 @@ package com.code_intelligence.jazzer.autofuzz; +import static com.code_intelligence.jazzer.autofuzz.TestHelpers.autofuzzTestCase; +import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; -import com.code_intelligence.jazzer.api.Function1; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.google.json.JsonSanitizer; +import java.io.ByteArrayInputStream; import java.util.Arrays; import java.util.Collections; import org.junit.Test; @@ -30,35 +31,117 @@ public class MetaTest { return arg == 5; } + public static boolean intEquals(int arg1, int arg2) { + return arg1 == arg2; + } + + public enum TestEnum { + FOO, + BAR, + BAZ, + } + @Test public void testConsume() { - FuzzedDataProvider data = CannedFuzzedDataProvider.create(Collections.singletonList(5)); - assertEquals(5, Meta.consume(data, int.class)); + consumeTestCase(5, "5", Collections.singletonList(5)); + consumeTestCase((short) 5, "(short) 5", Collections.singletonList((short) 5)); + consumeTestCase(5L, "5L", Collections.singletonList(5L)); + consumeTestCase(5.0F, "5.0F", Collections.singletonList(5.0F)); + consumeTestCase('\n', "'\\\\n'", Collections.singletonList('\n')); + consumeTestCase('\'', "'\\\\''", Collections.singletonList('\'')); + consumeTestCase('\\', "'\\\\'", Collections.singletonList('\\')); + + String testString = "foo\n\t\\\"bar"; + // The expected string is obtained from testString by escaping, wrapping into quotes and + // escaping again. + consumeTestCase(testString, "\"foo\\\\n\\\\t\\\\\\\\\"bar\"", + Arrays.asList((byte) 1, // do not return null + testString.length(), testString)); + + consumeTestCase(null, "null", Collections.singletonList((byte) 0)); + + boolean[] testBooleans = new boolean[] {true, false, true}; + consumeTestCase(testBooleans, "new boolean[]{true, false, true}", + Arrays.asList((byte) 1, // do not return null for the array + 2 * 3, testBooleans)); + + char[] testChars = new char[] {'a', '\n', '\''}; + consumeTestCase(testChars, "new char[]{'a', '\\\\n', '\\\\''}", + Arrays.asList((byte) 1, // do not return null for the array + 2 * 3 * Character.BYTES + Character.BYTES, testChars[0], 2 * 3 * Character.BYTES, + 2 * 3 * Character.BYTES, // remaining bytes, 2 times what is needed for 3 chars + testChars[1], testChars[2])); + + char[] testNoChars = new char[] {}; + consumeTestCase(testNoChars, "new char[]{}", + Arrays.asList((byte) 1, // do not return null for the array + 0, 'a', 0, 0)); + + short[] testShorts = new short[] {(short) 1, (short) 2, (short) 3}; + consumeTestCase(testShorts, "new short[]{(short) 1, (short) 2, (short) 3}", + Arrays.asList((byte) 1, // do not return null for the array + 2 * 3 * Short.BYTES, // remaining bytes + testShorts)); + + long[] testLongs = new long[] {1L, 2L, 3L}; + consumeTestCase(testLongs, "new long[]{1L, 2L, 3L}", + Arrays.asList((byte) 1, // do not return null for the array + 2 * 3 * Long.BYTES, // remaining bytes + testLongs)); + + consumeTestCase(new String[] {"foo", "bar", "foo\nbar"}, + "new java.lang.String[]{\"foo\", \"bar\", \"foo\\\\nbar\"}", + Arrays.asList((byte) 1, // do not return null for the array + 32, // remaining bytes + (byte) 1, // do not return null for the string + 31, // remaining bytes + "foo", + 28, // remaining bytes + 28, // array length + (byte) 1, // do not return null for the string + 27, // remaining bytes + "bar", + (byte) 1, // do not return null for the string + 23, // remaining bytes + "foo\nbar")); + + byte[] testInputStreamBytes = new byte[] {(byte) 1, (byte) 2, (byte) 3}; + consumeTestCase(new ByteArrayInputStream(testInputStreamBytes), + "new java.io.ByteArrayInputStream(new byte[]{(byte) 1, (byte) 2, (byte) 3})", + Arrays.asList((byte) 1, // do not return null for the InputStream + 2 * 3, // remaining bytes (twice the desired length) + testInputStreamBytes)); + + consumeTestCase(TestEnum.BAR, + String.format("%s.%s", TestEnum.class.getName(), TestEnum.BAR.name()), + Arrays.asList((byte) 1, // do not return null for the enum value + 1 /* second value */ + )); + + consumeTestCase(YourAverageJavaClass.class, + "com.code_intelligence.jazzer.autofuzz.YourAverageJavaClass.class", + Collections.singletonList((byte) 1)); } @Test - public void testAutofuzz() { - FuzzedDataProvider data = CannedFuzzedDataProvider.create(Arrays.asList(5, - (byte) 1, // do not return null - 6, // remainingBytes - "foo", - (byte) 1, // do not return null - 6, // remainingBytes - "bar", - (byte) 1, // do not return null - 8, // remainingBytes - "buzz", - (byte) 1, // do not return null - 6, // remainingBytes - "jazzer", - (byte) 1, // do not return null - 6, // remainingBytes - "jazzer")); - assertTrue(Meta.autofuzz(data, MetaTest::isFive)); - assertEquals("foobar", Meta.autofuzz(data, String::concat)); + public void testAutofuzz() throws NoSuchMethodException { + autofuzzTestCase(true, "com.code_intelligence.jazzer.autofuzz.MetaTest.isFive(5)", + MetaTest.class.getMethod("isFive", int.class), Collections.singletonList(5)); + autofuzzTestCase(false, "com.code_intelligence.jazzer.autofuzz.MetaTest.intEquals(5, 4)", + MetaTest.class.getMethod("intEquals", int.class, int.class), Arrays.asList(5, 4)); + autofuzzTestCase("foobar", "\"foo\".concat(\"bar\")", + String.class.getMethod("concat", String.class), + Arrays.asList((byte) 1, 6, "foo", (byte) 1, 6, "bar")); + autofuzzTestCase("jazzer", "new java.lang.String(\"jazzer\")", + String.class.getConstructor(String.class), Arrays.asList((byte) 1, 12, "jazzer")); + autofuzzTestCase("\"jazzer\"", "com.google.json.JsonSanitizer.sanitize(\"jazzer\")", + JsonSanitizer.class.getMethod("sanitize", String.class), + Arrays.asList((byte) 1, 12, "jazzer")); + + FuzzedDataProvider data = + CannedFuzzedDataProvider.create(Arrays.asList((byte) 1, // do not return null + 8, // remainingBytes + "buzz")); assertEquals("fizzbuzz", Meta.autofuzz(data, "fizz" ::concat)); - assertEquals("jazzer", Meta.autofuzz(data, (Function1) String::new)); - assertEquals( - "\"jazzer\"", Meta.autofuzz(data, (Function1) JsonSanitizer::sanitize)); } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java index 5403b19e..7c869531 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java @@ -14,32 +14,30 @@ package com.code_intelligence.jazzer.autofuzz; -import static org.junit.Assert.assertEquals; +import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase; -import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters; import java.util.Arrays; import org.junit.Test; public class SettersTest { - FuzzedDataProvider data = CannedFuzzedDataProvider.create( - Arrays.asList((byte) 1, // do not return null for EmployeeWithSetters - 0, // pick first constructor - 2, // pick two setters - 1, // pick second setter - 0, // pick first setter - (byte) 1, // do not return null for String - 6, // remaining bytes - "foo", // setFirstName - 26 // setAge - )); - @Test public void testEmptyConstructorWithSetters() { EmployeeWithSetters employee = new EmployeeWithSetters(); employee.setFirstName("foo"); employee.setAge(26); - assertEquals(Meta.consume(data, EmployeeWithSetters.class), employee); + + consumeTestCase(employee, + "((java.util.function.Supplier) (() -> {com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters autofuzzVariable0 = new com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters(); autofuzzVariable0.setFirstName(\"foo\"); autofuzzVariable0.setAge(26); return autofuzzVariable0;})).get()", + Arrays.asList((byte) 1, // do not return null for EmployeeWithSetters + 0, // pick first constructor + 2, // pick two setters + 1, // pick second setter + 0, // pick first setter + (byte) 1, // do not return null for String + 6, // remaining bytes + "foo", // setFirstName + 26 // setAge + )); } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java new file mode 100644 index 00000000..52f19a74 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java @@ -0,0 +1,85 @@ +// Copyright 2021 Code Intelligence GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.code_intelligence.jazzer.autofuzz; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.io.ByteArrayInputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.List; + +class TestHelpers { + static void assertGeneralEquals(Object expected, Object actual) { + Class type = expected != null ? expected.getClass() : Object.class; + if (type.isArray()) { + if (type.getComponentType() == boolean.class) { + assertArrayEquals((boolean[]) expected, (boolean[]) actual); + } else if (type.getComponentType() == char.class) { + assertArrayEquals((char[]) expected, (char[]) actual); + } else if (type.getComponentType() == short.class) { + assertArrayEquals((short[]) expected, (short[]) actual); + } else if (type.getComponentType() == long.class) { + assertArrayEquals((long[]) expected, (long[]) actual); + } else { + assertArrayEquals((Object[]) expected, (Object[]) actual); + } + } else if (type == ByteArrayInputStream.class) { + ByteArrayInputStream expectedStream = (ByteArrayInputStream) expected; + ByteArrayInputStream actualStream = (ByteArrayInputStream) actual; + assertArrayEquals(readAllBytes(expectedStream), readAllBytes(actualStream)); + } else { + assertEquals(expected, actual); + } + } + + static void consumeTestCase( + Object expectedResult, String expectedResultString, List cannedData) { + Class type = expectedResult != null ? expectedResult.getClass() : Object.class; + consumeTestCase(type, expectedResult, expectedResultString, cannedData); + } + + static void consumeTestCase( + Class type, Object expectedResult, String expectedResultString, List cannedData) { + assertTrue(expectedResult == null || type.isAssignableFrom(expectedResult.getClass())); + AutofuzzCodegenVisitor visitor = new AutofuzzCodegenVisitor(); + FuzzedDataProvider data = CannedFuzzedDataProvider.create(cannedData); + assertGeneralEquals(expectedResult, Meta.consume(data, type, visitor)); + assertEquals(expectedResultString, visitor.generate()); + } + + static void autofuzzTestCase(Object expectedResult, String expectedResultString, Executable func, + List cannedData) { + AutofuzzCodegenVisitor visitor = new AutofuzzCodegenVisitor(); + FuzzedDataProvider data = CannedFuzzedDataProvider.create(cannedData); + if (func instanceof Method) { + assertGeneralEquals(expectedResult, Meta.autofuzz(data, (Method) func, visitor)); + } else { + assertGeneralEquals(expectedResult, Meta.autofuzz(data, (Constructor) func, visitor)); + } + assertEquals(expectedResultString, visitor.generate()); + } + + private static byte[] readAllBytes(ByteArrayInputStream in) { + byte[] result = new byte[in.available()]; + in.read(result, 0, in.available()); + return result; + } +} -- cgit v1.2.3