aboutsummaryrefslogtreecommitdiff
path: root/src/jmh/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/jmh/java/com')
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel6
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel105
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java178
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java32
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java36
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt81
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java66
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java44
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java55
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java55
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java59
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java54
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/jmh.bzl24
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel55
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java29
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java219
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java46
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java46
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java59
-rw-r--r--src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java31
20 files changed, 1280 insertions, 0 deletions
diff --git a/src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel b/src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel
new file mode 100644
index 00000000..0a88ca09
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel
@@ -0,0 +1,6 @@
+java_plugin(
+ name = "JmhGeneratorAnnotationProcessor",
+ processor_class = "org.openjdk.jmh.generators.BenchmarkProcessor",
+ visibility = ["//src/jmh/java:__subpackages__"],
+ deps = ["@maven//:org_openjdk_jmh_jmh_generator_annprocess"],
+)
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
new file mode 100644
index 00000000..1dbcdbff
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
@@ -0,0 +1,105 @@
+load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library")
+load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
+load("//bazel:kotlin.bzl", "ktlint")
+load("//src/jmh/java/com/code_intelligence/jazzer:jmh.bzl", "JMH_TEST_ARGS")
+
+java_binary(
+ name = "CoverageInstrumentationBenchmark",
+ main_class = "org.openjdk.jmh.Main",
+ runtime_deps = [
+ ":coverage_instrumentation_benchmark",
+ ],
+)
+
+java_test(
+ name = "CoverageInstrumentationBenchmarkTest",
+ args = JMH_TEST_ARGS,
+ jvm_flags = [
+ "-XX:CompileCommand=print,*CoverageMap.recordCoverage",
+ ],
+ main_class = "org.openjdk.jmh.Main",
+ # Directly invoke JMH's main without using a testrunner.
+ use_testrunner = False,
+ runtime_deps = [
+ ":coverage_instrumentation_benchmark",
+ ],
+)
+
+java_library(
+ name = "coverage_instrumentation_benchmark",
+ srcs = ["CoverageInstrumentationBenchmark.java"],
+ plugins = ["//src/jmh/java/com/code_intelligence/jazzer:JmhGeneratorAnnotationProcessor"],
+ runtime_deps = [
+ "@maven//:com_mikesamuel_json_sanitizer",
+ ],
+ deps = [
+ ":kotlin_strategies",
+ ":strategies",
+ "//src/main/java/com/code_intelligence/jazzer/instrumentor",
+ "@maven//:org_openjdk_jmh_jmh_core",
+ ],
+)
+
+java_library(
+ name = "strategies",
+ srcs = [
+ "DirectByteBuffer2CoverageMap.java",
+ "DirectByteBufferCoverageMap.java",
+ "Unsafe2CoverageMap.java",
+ "UnsafeBranchfreeCoverageMap.java",
+ "UnsafeCoverageMap.java",
+ "UnsafeSimpleIncrementCoverageMap.java",
+ ],
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/instrumentor",
+ "@jazzer_jacoco//:jacoco_internal",
+ "@org_ow2_asm_asm//jar",
+ ],
+)
+
+kt_jvm_library(
+ name = "kotlin_strategies",
+ srcs = ["DirectByteBufferStrategy.kt"],
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/instrumentor",
+ "@jazzer_jacoco//:jacoco_internal",
+ "@org_ow2_asm_asm//jar",
+ ],
+)
+
+java_binary(
+ name = "EdgeCoverageInstrumentationBenchmark",
+ main_class = "org.openjdk.jmh.Main",
+ runtime_deps = [
+ ":edge_coverage_instrumentation_benchmark",
+ ],
+)
+
+java_test(
+ name = "EdgeCoverageInstrumentationBenchmarkTest",
+ args = JMH_TEST_ARGS,
+ main_class = "org.openjdk.jmh.Main",
+ # Directly invoke JMH's main without using a testrunner.
+ use_testrunner = False,
+ runtime_deps = [
+ ":edge_coverage_instrumentation_benchmark",
+ ],
+)
+
+java_jni_library(
+ name = "edge_coverage_instrumentation_benchmark",
+ srcs = [
+ "EdgeCoverageInstrumentation.java",
+ "EdgeCoverageTarget.java",
+ ],
+ native_libs = ["//src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver"],
+ plugins = ["//src/jmh/java/com/code_intelligence/jazzer:JmhGeneratorAnnotationProcessor"],
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/instrumentor",
+ "//src/main/java/com/code_intelligence/jazzer/runtime:coverage_map",
+ "//src/test/java/com/code_intelligence/jazzer/instrumentor:patch_test_utils",
+ "@maven//:org_openjdk_jmh_jmh_core",
+ ],
+)
+
+ktlint()
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java
new file mode 100644
index 00000000..4401da7d
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java
@@ -0,0 +1,178 @@
+// Copyright 2022 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.instrumentor;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * This benchmark compares the throughput of a typical fuzz target when instrumented with different
+ * edge coverage instrumentation strategies and coverage map implementations.
+ *
+ * The benchmark currently uses the OWASP json-sanitizer as its target, which has the following
+ * desirable properties for a benchmark:
+ * - It is a reasonably sized project that does not consist of many different classes.
+ * - It is very heavy on computation with a high density of branching.
+ * - It is entirely CPU bound with no IO and does not call expensive methods from the standard
+ * library.
+ * With these properties, results obtained from this benchmark should provide reasonable lower
+ * bounds on the relative slowdown introduced by the various approaches to instrumentations.
+ */
+@State(Scope.Benchmark)
+public class CoverageInstrumentationBenchmark {
+ private static final String TARGET_CLASSNAME = "com.google.json.JsonSanitizer";
+ private static final String TARGET_PACKAGE =
+ TARGET_CLASSNAME.substring(0, TARGET_CLASSNAME.lastIndexOf('.'));
+ private static final String TARGET_METHOD = "sanitize";
+ private static final MethodType TARGET_TYPE = MethodType.methodType(String.class, String.class);
+
+ // This is part of the benchmark's state and not a constant to prevent constant folding.
+ String TARGET_ARG =
+ "{\"foo\":1123987,\"bar\":[true, false],\"baz\":{\"foo\":\"132รค3\",\"bar\":1.123e-005}}";
+
+ MethodHandle uninstrumented_sanitize;
+ MethodHandle local_DirectByteBuffer_NeverZero_sanitize;
+ MethodHandle staticMethod_DirectByteBuffer_NeverZero_sanitize;
+ MethodHandle staticMethod_DirectByteBuffer2_NeverZero_sanitize;
+ MethodHandle staticMethod_Unsafe_NeverZero_sanitize;
+ MethodHandle staticMethod_Unsafe_NeverZero2_sanitize;
+ MethodHandle staticMethod_Unsafe_NeverZeroBranchfree_sanitize;
+ MethodHandle staticMethod_Unsafe_SimpleIncrement_sanitize;
+
+ public static MethodHandle handleForTargetMethod(ClassLoader classLoader)
+ throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException {
+ Class<?> targetClass = classLoader.loadClass(TARGET_CLASSNAME);
+ return MethodHandles.lookup().findStatic(targetClass, TARGET_METHOD, TARGET_TYPE);
+ }
+
+ public static MethodHandle instrumentWithStrategy(
+ EdgeCoverageStrategy strategy, Class<?> coverageMapClass)
+ throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException {
+ if (strategy == null) {
+ // Do not instrument the code by using the benchmark class' ClassLoader.
+ return handleForTargetMethod(CoverageInstrumentationBenchmark.class.getClassLoader());
+ }
+ // It's fine to reuse a single instrumentor here as we don't want to know which class received
+ // how many counters.
+ Instrumentor instrumentor = new EdgeCoverageInstrumentor(strategy, coverageMapClass, 0);
+ return handleForTargetMethod(new InstrumentingClassLoader(instrumentor, TARGET_PACKAGE));
+ }
+
+ @Setup
+ public void instrumentWithStrategies()
+ throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException {
+ uninstrumented_sanitize = instrumentWithStrategy(null, null);
+ local_DirectByteBuffer_NeverZero_sanitize = instrumentWithStrategy(
+ DirectByteBufferStrategy.INSTANCE, DirectByteBufferCoverageMap.class);
+ staticMethod_DirectByteBuffer_NeverZero_sanitize =
+ instrumentWithStrategy(new StaticMethodStrategy(), DirectByteBufferCoverageMap.class);
+ staticMethod_DirectByteBuffer2_NeverZero_sanitize =
+ instrumentWithStrategy(new StaticMethodStrategy(), DirectByteBuffer2CoverageMap.class);
+ staticMethod_Unsafe_NeverZero_sanitize =
+ instrumentWithStrategy(new StaticMethodStrategy(), UnsafeCoverageMap.class);
+ staticMethod_Unsafe_NeverZero2_sanitize =
+ instrumentWithStrategy(new StaticMethodStrategy(), Unsafe2CoverageMap.class);
+ staticMethod_Unsafe_SimpleIncrement_sanitize =
+ instrumentWithStrategy(new StaticMethodStrategy(), UnsafeSimpleIncrementCoverageMap.class);
+ staticMethod_Unsafe_NeverZeroBranchfree_sanitize =
+ instrumentWithStrategy(new StaticMethodStrategy(), UnsafeBranchfreeCoverageMap.class);
+ }
+
+ @Benchmark
+ public String uninstrumented() throws Throwable {
+ return (String) uninstrumented_sanitize.invokeExact(TARGET_ARG);
+ }
+
+ @Benchmark
+ public String local_DirectByteBuffer_NeverZero() throws Throwable {
+ return (String) local_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG);
+ }
+
+ @Benchmark
+ public String staticMethod_DirectByteBuffer_NeverZero() throws Throwable {
+ return (String) staticMethod_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG);
+ }
+
+ @Benchmark
+ public String staticMethod_DirectByteBuffer2_NeverZero() throws Throwable {
+ return (String) staticMethod_DirectByteBuffer2_NeverZero_sanitize.invokeExact(TARGET_ARG);
+ }
+
+ @Benchmark
+ public String staticMethod_Unsafe_NeverZero() throws Throwable {
+ return (String) staticMethod_Unsafe_NeverZero_sanitize.invokeExact(TARGET_ARG);
+ }
+
+ @Benchmark
+ public String staticMethod_Unsafe_NeverZero2() throws Throwable {
+ return (String) staticMethod_Unsafe_NeverZero2_sanitize.invokeExact(TARGET_ARG);
+ }
+
+ @Benchmark
+ public String staticMethod_Unsafe_SimpleIncrement() throws Throwable {
+ return (String) staticMethod_Unsafe_SimpleIncrement_sanitize.invokeExact(TARGET_ARG);
+ }
+
+ @Benchmark
+ public String staticMethod_Unsafe_NeverZeroBranchfree() throws Throwable {
+ return (String) staticMethod_Unsafe_NeverZeroBranchfree_sanitize.invokeExact(TARGET_ARG);
+ }
+}
+
+class InstrumentingClassLoader extends ClassLoader {
+ private final Instrumentor instrumentor;
+ private final String classNamePrefix;
+
+ InstrumentingClassLoader(Instrumentor instrumentor, String packageToInstrument) {
+ super(InstrumentingClassLoader.class.getClassLoader());
+ this.instrumentor = instrumentor;
+ this.classNamePrefix = packageToInstrument + ".";
+ }
+
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ if (!name.startsWith(classNamePrefix)) {
+ return super.loadClass(name);
+ }
+ try (InputStream stream = super.getResourceAsStream(name.replace('.', '/') + ".class")) {
+ if (stream == null) {
+ throw new ClassNotFoundException(String.format("Failed to find class file for %s", name));
+ }
+ byte[] bytecode = readAllBytes(stream);
+ byte[] instrumentedBytecode = instrumentor.instrument(name.replace('.', '/'), bytecode);
+ return defineClass(name, instrumentedBytecode, 0, instrumentedBytecode.length);
+ } catch (IOException e) {
+ throw new ClassNotFoundException(String.format("Failed to read class file for %s", name), e);
+ }
+ }
+
+ private static byte[] readAllBytes(InputStream in) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] buffer = new byte[64 * 104 * 1024];
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ out.write(buffer, 0, read);
+ }
+ return out.toByteArray();
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java
new file mode 100644
index 00000000..c57babb5
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java
@@ -0,0 +1,32 @@
+// Copyright 2022 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;
+
+public final class DirectByteBuffer2CoverageMap {
+ // The current target, JsonSanitizer, uses less than 2048 coverage counters.
+ private static final int NUM_COUNTERS = 4096;
+ public static final ByteBuffer counters = ByteBuffer.allocateDirect(NUM_COUNTERS);
+
+ public static void enlargeIfNeeded(int nextId) {
+ // Statically sized counters buffer.
+ }
+
+ public static void recordCoverage(final int id) {
+ final byte counter = counters.get(id);
+ counters.put(id, (byte) (counter == -1 ? 1 : counter + 1));
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java
new file mode 100644
index 00000000..e5e66abb
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java
@@ -0,0 +1,36 @@
+// Copyright 2022 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;
+
+public final class DirectByteBufferCoverageMap {
+ // The current target, JsonSanitizer, uses less than 2048 coverage counters.
+ private static final int NUM_COUNTERS = 4096;
+ public static final ByteBuffer counters = ByteBuffer.allocateDirect(NUM_COUNTERS);
+
+ public static void enlargeIfNeeded(int nextId) {
+ // Statically sized counters buffer.
+ }
+
+ public static void recordCoverage(final int id) {
+ final byte counter = counters.get(id);
+ if (counter == -1) {
+ counters.put(id, (byte) 1);
+ } else {
+ counters.put(id, (byte) (counter + 1));
+ }
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt
new file mode 100644
index 00000000..14f5041f
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt
@@ -0,0 +1,81 @@
+// Copyright 2022 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.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+
+object DirectByteBufferStrategy : EdgeCoverageStrategy {
+
+ override fun instrumentControlFlowEdge(
+ mv: MethodVisitor,
+ edgeId: Int,
+ variable: Int,
+ coverageMapInternalClassName: String,
+ ) {
+ mv.apply {
+ visitVarInsn(Opcodes.ALOAD, variable)
+ // Stack: counters
+ push(edgeId)
+ // Stack: counters | edgeId
+ visitInsn(Opcodes.DUP2)
+ // Stack: counters | edgeId | counters | edgeId
+ visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "get", "(I)B", false)
+ // Increment the counter, but ensure that it never stays at 0 after an overflow by incrementing it again in
+ // that case.
+ // This approach performs better than saturating the counter at 255 (see Section 3.3 of
+ // https://www.usenix.org/system/files/woot20-paper-fioraldi.pdf)
+ // Stack: counters | edgeId | counter (sign-extended to int)
+ push(0xff)
+ // Stack: counters | edgeId | counter (sign-extended to int) | 0x000000ff
+ visitInsn(Opcodes.IAND)
+ // Stack: counters | edgeId | counter (zero-extended to int)
+ push(1)
+ // Stack: counters | edgeId | counter | 1
+ visitInsn(Opcodes.IADD)
+ // Stack: counters | edgeId | counter + 1
+ visitInsn(Opcodes.DUP)
+ // Stack: counters | edgeId | counter + 1 | counter + 1
+ push(8)
+ // Stack: counters | edgeId | counter + 1 | counter + 1 | 8 (maxStack: +5)
+ visitInsn(Opcodes.ISHR)
+ // Stack: counters | edgeId | counter + 1 | 1 if the increment overflowed to 0, 0 otherwise
+ visitInsn(Opcodes.IADD)
+ // Stack: counters | edgeId | counter + 2 if the increment overflowed, counter + 1 otherwise
+ visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "put", "(IB)Ljava/nio/ByteBuffer;", false)
+ // Stack: counters
+ visitInsn(Opcodes.POP)
+ }
+ }
+
+ override val instrumentControlFlowEdgeStackSize = 5
+
+ override val localVariableType get() = "java/nio/ByteBuffer"
+
+ override fun loadLocalVariable(mv: MethodVisitor, variable: Int, coverageMapInternalClassName: String) {
+ mv.apply {
+ visitFieldInsn(
+ Opcodes.GETSTATIC,
+ coverageMapInternalClassName,
+ "counters",
+ "Ljava/nio/ByteBuffer;",
+ )
+ // Stack: counters (maxStack: 1)
+ visitVarInsn(Opcodes.ASTORE, variable)
+ }
+ }
+
+ override val loadLocalVariableStackSize = 1
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java
new file mode 100644
index 00000000..97f6b43b
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java
@@ -0,0 +1,66 @@
+// Copyright 2022 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 static com.code_intelligence.jazzer.instrumentor.PatchTestUtils.*;
+import static java.lang.invoke.MethodHandles.lookup;
+import static java.lang.invoke.MethodType.methodType;
+
+import com.code_intelligence.jazzer.runtime.CoverageMap;
+import java.lang.invoke.*;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.*;
+
+@Warmup(iterations = 10, time = 3)
+@Measurement(iterations = 10, time = 3)
+@Fork(value = 3)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@BenchmarkMode(Mode.AverageTime)
+@State(Scope.Benchmark)
+@SuppressWarnings("unused")
+public class EdgeCoverageInstrumentation {
+ private MethodHandle exampleMethod;
+
+ @Setup
+ public void setupInstrumentation() throws Throwable {
+ String outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR");
+ if (outDir == null || outDir.isEmpty()) {
+ outDir =
+ Files.createTempDirectory(EdgeCoverageInstrumentation.class.getSimpleName()).toString();
+ }
+
+ byte[] originalBytecode = classToBytecode(EdgeCoverageTarget.class);
+ dumpBytecode(outDir, EdgeCoverageTarget.class.getName(), originalBytecode);
+
+ byte[] patchedBytecode = applyInstrumentation(originalBytecode);
+ dumpBytecode(outDir, EdgeCoverageTarget.class.getName() + ".patched", patchedBytecode);
+
+ Class<?> patchedClass = bytecodeToClass(EdgeCoverageTarget.class.getName(), patchedBytecode);
+ Object obj = lookup().findConstructor(patchedClass, methodType(void.class)).invoke();
+ exampleMethod = lookup().bind(obj, "exampleMethod", methodType(List.class));
+ }
+
+ private byte[] applyInstrumentation(byte[] bytecode) {
+ return new EdgeCoverageInstrumentor(new StaticMethodStrategy(), CoverageMap.class, 0)
+ .instrument(EdgeCoverageTarget.class.getName().replace('.', '/'), bytecode);
+ }
+
+ @Benchmark
+ public Object benchmarkInstrumentedMethodCall() throws Throwable {
+ return exampleMethod.invoke();
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java
new file mode 100644
index 00000000..57eb8807
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 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;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+public class EdgeCoverageTarget {
+ private final Random rnd = new Random();
+
+ @SuppressWarnings("unused")
+ public List<Integer> exampleMethod() {
+ ArrayList<Integer> rnds = new ArrayList<>();
+ rnds.add(rnd.nextInt());
+ rnds.add(rnd.nextInt());
+ rnds.add(rnd.nextInt());
+ rnds.add(rnd.nextInt());
+ rnds.add(rnd.nextInt());
+ int i = rnd.nextInt() + rnd.nextInt();
+ if (i > 0 && i < Integer.MAX_VALUE / 2) {
+ i--;
+ } else {
+ i++;
+ }
+ rnds.add(i);
+ return rnds.stream().map(n -> n + 1).collect(Collectors.toList());
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java
new file mode 100644
index 00000000..030d9a95
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java
@@ -0,0 +1,55 @@
+// Copyright 2022 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.lang.reflect.Field;
+import sun.misc.Unsafe;
+
+public final class Unsafe2CoverageMap {
+ private static final Unsafe UNSAFE;
+
+ static {
+ Unsafe unsafe;
+ try {
+ Field f = Unsafe.class.getDeclaredField("theUnsafe");
+ f.setAccessible(true);
+ unsafe = (Unsafe) f.get(null);
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ e.printStackTrace();
+ System.exit(1);
+ // Not reached.
+ unsafe = null;
+ }
+ UNSAFE = unsafe;
+ }
+
+ // The current target, JsonSanitizer, uses less than 2048 coverage counters.
+ private static final long NUM_COUNTERS = 4096;
+ private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS);
+
+ static {
+ UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0);
+ }
+
+ public static void enlargeIfNeeded(int nextId) {
+ // Statically sized counters buffer.
+ }
+
+ public static void recordCoverage(final int id) {
+ final long address = countersAddress + id;
+ final byte counter = UNSAFE.getByte(address);
+ UNSAFE.putByte(address, (byte) (counter == -1 ? 1 : counter + 1));
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java
new file mode 100644
index 00000000..3694b95f
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java
@@ -0,0 +1,55 @@
+// Copyright 2022 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.lang.reflect.Field;
+import sun.misc.Unsafe;
+
+public final class UnsafeBranchfreeCoverageMap {
+ private static final Unsafe UNSAFE;
+
+ static {
+ Unsafe unsafe;
+ try {
+ Field f = Unsafe.class.getDeclaredField("theUnsafe");
+ f.setAccessible(true);
+ unsafe = (Unsafe) f.get(null);
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ e.printStackTrace();
+ System.exit(1);
+ // Not reached.
+ unsafe = null;
+ }
+ UNSAFE = unsafe;
+ }
+
+ // The current target, JsonSanitizer, uses less than 2048 coverage counters.
+ private static final long NUM_COUNTERS = 4096;
+ private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS);
+
+ static {
+ UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0);
+ }
+
+ public static void enlargeIfNeeded(int nextId) {
+ // Statically sized counters buffer.
+ }
+
+ public static void recordCoverage(final int id) {
+ final long address = countersAddress + id;
+ final int incrementedCounter = UNSAFE.getByte(address) + 1;
+ UNSAFE.putByte(address, (byte) (incrementedCounter ^ (incrementedCounter >>> 8)));
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java
new file mode 100644
index 00000000..cf73928d
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java
@@ -0,0 +1,59 @@
+// Copyright 2022 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.lang.reflect.Field;
+import sun.misc.Unsafe;
+
+public final class UnsafeCoverageMap {
+ private static final Unsafe UNSAFE;
+
+ static {
+ Unsafe unsafe;
+ try {
+ Field f = Unsafe.class.getDeclaredField("theUnsafe");
+ f.setAccessible(true);
+ unsafe = (Unsafe) f.get(null);
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ e.printStackTrace();
+ System.exit(1);
+ // Not reached.
+ unsafe = null;
+ }
+ UNSAFE = unsafe;
+ }
+
+ // The current target, JsonSanitizer, uses less than 2048 coverage counters.
+ private static final long NUM_COUNTERS = 4096;
+ private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS);
+
+ static {
+ UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0);
+ }
+
+ public static void enlargeIfNeeded(int nextId) {
+ // Statically sized counters buffer.
+ }
+
+ public static void recordCoverage(final int id) {
+ final long address = countersAddress + id;
+ final byte counter = UNSAFE.getByte(address);
+ if (counter == -1) {
+ UNSAFE.putByte(address, (byte) 1);
+ } else {
+ UNSAFE.putByte(address, (byte) (counter + 1));
+ }
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java
new file mode 100644
index 00000000..60fb8c8d
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java
@@ -0,0 +1,54 @@
+// Copyright 2022 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.lang.reflect.Field;
+import sun.misc.Unsafe;
+
+public final class UnsafeSimpleIncrementCoverageMap {
+ private static final Unsafe UNSAFE;
+
+ static {
+ Unsafe unsafe;
+ try {
+ Field f = Unsafe.class.getDeclaredField("theUnsafe");
+ f.setAccessible(true);
+ unsafe = (Unsafe) f.get(null);
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ e.printStackTrace();
+ System.exit(1);
+ // Not reached.
+ unsafe = null;
+ }
+ UNSAFE = unsafe;
+ }
+
+ // The current target, JsonSanitizer, uses less than 2048 coverage counters.
+ private static final long NUM_COUNTERS = 4096;
+ private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS);
+
+ static {
+ UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0);
+ }
+
+ public static void enlargeIfNeeded(int nextId) {
+ // Statically sized counters buffer.
+ }
+
+ public static void recordCoverage(final int id) {
+ final long address = countersAddress + id;
+ UNSAFE.putByte(address, (byte) (UNSAFE.getByte(address) + 1));
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/jmh.bzl b/src/jmh/java/com/code_intelligence/jazzer/jmh.bzl
new file mode 100644
index 00000000..5391a46b
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/jmh.bzl
@@ -0,0 +1,24 @@
+# Copyright 2022 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.
+
+JMH_TEST_ARGS = [
+ # Fail fast on any exceptions produced by benchmarks.
+ "-foe true",
+ "-wf 0",
+ "-f 1",
+ "-wi 0",
+ "-i 1",
+ "-r 1s",
+ "-w 1s",
+]
diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
new file mode 100644
index 00000000..7595d660
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
@@ -0,0 +1,55 @@
+load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library")
+load("//src/jmh/java/com/code_intelligence/jazzer:jmh.bzl", "JMH_TEST_ARGS")
+
+java_binary(
+ name = "FuzzerCallbacksBenchmark",
+ main_class = "org.openjdk.jmh.Main",
+ runtime_deps = [
+ ":fuzzer_callbacks_benchmark",
+ ],
+)
+
+java_test(
+ name = "FuzzerCallbacksBenchmarkTest",
+ args = JMH_TEST_ARGS,
+ main_class = "org.openjdk.jmh.Main",
+ # CriticalJNINatives have been removed in Java 18.
+ tags = [
+ "exclusive-if-local",
+ "no-linux-jdk19",
+ ],
+ # Directly invoke JMH's main without using a testrunner.
+ use_testrunner = False,
+ runtime_deps = [
+ ":fuzzer_callbacks_benchmark",
+ ],
+)
+
+java_library(
+ name = "fuzzer_callbacks_benchmark",
+ srcs = ["FuzzerCallbacksBenchmark.java"],
+ plugins = ["//src/jmh/java/com/code_intelligence/jazzer:JmhGeneratorAnnotationProcessor"],
+ deps = [
+ ":fuzzer_callbacks",
+ "@maven//:org_openjdk_jmh_jmh_core",
+ ],
+)
+
+java_jni_library(
+ name = "fuzzer_callbacks",
+ srcs = [
+ "FuzzerCallbacks.java",
+ "FuzzerCallbacksOptimizedCritical.java",
+ "FuzzerCallbacksOptimizedNonCritical.java",
+ # Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+).
+ # "FuzzerCallbacksPanama.java",
+ "FuzzerCallbacksWithPc.java",
+ ],
+ javacopts = [
+ # Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+).
+ # "--add-modules",
+ # "jdk.incubator.foreign",
+ ],
+ native_libs = ["//src/jmh/native/com/code_intelligence/jazzer/runtime:fuzzer_callbacks"],
+ visibility = ["//src/jmh/native/com/code_intelligence/jazzer/runtime:__pkg__"],
+)
diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java
new file mode 100644
index 00000000..6e8343ce
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java
@@ -0,0 +1,29 @@
+// Copyright 2022 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.runtime;
+
+import com.github.fmeum.rules_jni.RulesJni;
+
+public final class FuzzerCallbacks {
+ static {
+ RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacks.class);
+ }
+
+ static native void traceCmpInt(int arg1, int arg2, int pc);
+ static native void traceSwitch(long val, long[] cases, int pc);
+
+ static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc);
+ static native void traceStrstr(String s1, String s2, int pc);
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java
new file mode 100644
index 00000000..81a8f56e
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java
@@ -0,0 +1,219 @@
+// Copyright 2022 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.runtime;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+
+@Warmup(iterations = 5, time = 1)
+@Measurement(iterations = 5, time = 1)
+@Fork(value = 3)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@BenchmarkMode(Mode.AverageTime)
+public class FuzzerCallbacksBenchmark {
+ @State(Scope.Benchmark)
+ public static class TraceCmpIntState {
+ int arg1 = 0xCAFECAFE;
+ int arg2 = 0xFEEDFEED;
+ int pc = 0x12345678;
+ }
+
+ @Benchmark
+ public void traceCmpInt(TraceCmpIntState state) {
+ FuzzerCallbacks.traceCmpInt(state.arg1, state.arg2, state.pc);
+ }
+
+ @Benchmark
+ public void traceCmpIntWithPc(TraceCmpIntState state) {
+ FuzzerCallbacksWithPc.traceCmpInt(state.arg1, state.arg2, state.pc);
+ }
+
+ @Benchmark
+ @Fork(jvmArgsAppend = {"-XX:+IgnoreUnrecognizedVMOptions", "-XX:+CriticalJNINatives"})
+ public void traceCmpIntOptimizedCritical(TraceCmpIntState state) {
+ FuzzerCallbacksOptimizedCritical.traceCmpInt(state.arg1, state.arg2, state.pc);
+ }
+
+ // Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+).
+ // @Benchmark
+ // @Fork(jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "--add-modules",
+ // "jdk.incubator.foreign"})
+ // public void
+ // traceCmpIntPanama(TraceCmpIntState state) throws Throwable {
+ // FuzzerCallbacksPanama.traceCmpInt(state.arg1, state.arg2, state.pc);
+ // }
+
+ @State(Scope.Benchmark)
+ public static class TraceSwitchState {
+ @Param({"5", "10"}) int numCases;
+
+ long val;
+ long[] cases;
+ int pc = 0x12345678;
+
+ @Setup
+ public void setup() {
+ cases = new long[2 + numCases];
+ Random random = ThreadLocalRandom.current();
+ Arrays.setAll(cases, i -> {
+ switch (i) {
+ case 0:
+ return numCases;
+ case 1:
+ return 32;
+ default:
+ return random.nextInt();
+ }
+ });
+ Arrays.sort(cases, 2, cases.length);
+ val = random.nextInt();
+ }
+ }
+
+ @Benchmark
+ public void traceSwitch(TraceSwitchState state) {
+ FuzzerCallbacks.traceSwitch(state.val, state.cases, state.pc);
+ }
+
+ @Benchmark
+ public void traceSwitchWithPc(TraceSwitchState state) {
+ FuzzerCallbacksWithPc.traceSwitch(state.val, state.cases, state.pc);
+ }
+
+ @Benchmark
+ @Fork(jvmArgsAppend = {"-XX:+IgnoreUnrecognizedVMOptions", "-XX:+CriticalJNINatives"})
+ public void traceSwitchOptimizedCritical(TraceSwitchState state) {
+ FuzzerCallbacksOptimizedCritical.traceSwitch(state.val, state.cases, state.pc);
+ }
+
+ @Benchmark
+ public void traceSwitchOptimizedNonCritical(TraceSwitchState state) {
+ FuzzerCallbacksOptimizedNonCritical.traceSwitch(state.val, state.cases, state.pc);
+ }
+
+ // Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+).
+ // @Benchmark
+ // @Fork(jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "--add-modules",
+ // "jdk.incubator.foreign"})
+ // public void
+ // traceCmpSwitchPanama(TraceSwitchState state) throws Throwable {
+ // FuzzerCallbacksPanama.traceCmpSwitch(state.val, state.cases, state.pc);
+ // }
+
+ @State(Scope.Benchmark)
+ public static class TraceMemcmpState {
+ @Param({"10", "100", "1000"}) int length;
+
+ byte[] array1;
+ byte[] array2;
+ int pc = 0x12345678;
+
+ @Setup
+ public void setup() {
+ array1 = new byte[length];
+ array2 = new byte[length];
+
+ Random random = ThreadLocalRandom.current();
+ random.nextBytes(array1);
+ random.nextBytes(array2);
+ // Make the arrays agree unil the midpoint to benchmark the "average"
+ // case of an interesting memcmp.
+ System.arraycopy(array1, 0, array2, 0, length / 2);
+ }
+ }
+
+ @Benchmark
+ public void traceMemcmp(TraceMemcmpState state) {
+ FuzzerCallbacks.traceMemcmp(state.array1, state.array2, 1, state.pc);
+ }
+
+ @Benchmark
+ @Fork(jvmArgsAppend = {"-XX:+IgnoreUnrecognizedVMOptions", "-XX:+CriticalJNINatives"})
+ public void traceMemcmpOptimizedCritical(TraceMemcmpState state) {
+ FuzzerCallbacksOptimizedCritical.traceMemcmp(state.array1, state.array2, 1, state.pc);
+ }
+
+ @Benchmark
+ public void traceMemcmpOptimizedNonCritical(TraceMemcmpState state) {
+ FuzzerCallbacksOptimizedNonCritical.traceMemcmp(state.array1, state.array2, 1, state.pc);
+ }
+
+ @State(Scope.Benchmark)
+ public static class TraceStrstrState {
+ @Param({"10", "100", "1000"}) int length;
+ @Param({"true", "false"}) boolean asciiOnly;
+
+ String haystack;
+ String needle;
+ int pc = 0x12345678;
+
+ @Setup
+ public void setup() {
+ haystack = randomString(length, asciiOnly);
+ needle = randomString(length, asciiOnly);
+ }
+
+ private String randomString(int length, boolean asciiOnly) {
+ String asciiString =
+ ThreadLocalRandom.current()
+ .ints('a', 'z' + 1)
+ .limit(length)
+ .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
+ .toString();
+ if (asciiOnly) {
+ return asciiString;
+ }
+ // Force String to be non-Latin-1 to preclude compact string optimization.
+ return "\uFFFD" + asciiString.substring(1);
+ }
+ }
+
+ @Benchmark
+ public void traceStrstr(TraceStrstrState state) {
+ FuzzerCallbacks.traceStrstr(state.haystack, state.needle, state.pc);
+ }
+
+ @Benchmark
+ public void traceStrstrOptimizedNonCritical(TraceStrstrState state) {
+ FuzzerCallbacksOptimizedNonCritical.traceStrstr(state.haystack, state.needle, state.pc);
+ }
+
+ @Benchmark
+ @Fork(jvmArgsAppend = {"-XX:+IgnoreUnrecognizedVMOptions", "-XX:+CriticalJNINatives"})
+ public void traceStrstrOptimizedJavaCritical(TraceStrstrState state)
+ throws UnsupportedEncodingException {
+ FuzzerCallbacksOptimizedCritical.traceStrstrJava(state.haystack, state.needle, state.pc);
+ }
+
+ @Benchmark
+ public void traceStrstrOptimizedJavaNonCritical(TraceStrstrState state)
+ throws UnsupportedEncodingException {
+ FuzzerCallbacksOptimizedNonCritical.traceStrstrJava(state.haystack, state.needle, state.pc);
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java
new file mode 100644
index 00000000..1c09e9ad
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java
@@ -0,0 +1,46 @@
+// Copyright 2022 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.runtime;
+
+import com.github.fmeum.rules_jni.RulesJni;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Optimized implementations of the libFuzzer callbacks that do rely on the deprecated
+ * CriticalJNINatives feature. Methods with `Java` in their name implement some parts in Java.
+ */
+public final class FuzzerCallbacksOptimizedCritical {
+ static {
+ RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacksOptimizedCritical.class);
+ }
+
+ static native void traceCmpInt(int arg1, int arg2, int pc);
+
+ static native void traceSwitch(long val, long[] cases, int pc);
+
+ static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc);
+
+ static void traceStrstrJava(String haystack, String needle, int pc)
+ throws UnsupportedEncodingException {
+ // Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently
+ // converts CESU8 into modified UTF-8 by coding null bytes on two bytes. Since the fuzzer is
+ // more likely to insert literal null bytes, having both the fuzzer input and the reported
+ // string comparisons be CESU8 should perform even better than the current implementation using
+ // modified UTF-8.
+ traceStrstrInternal(needle.substring(0, Math.min(needle.length(), 64)).getBytes("CESU8"), pc);
+ }
+
+ private static native void traceStrstrInternal(byte[] needle, int pc);
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java
new file mode 100644
index 00000000..25fad3bf
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java
@@ -0,0 +1,46 @@
+// Copyright 2022 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.runtime;
+
+import com.github.fmeum.rules_jni.RulesJni;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Optimized implementations of the libFuzzer callbacks that do not rely on the deprecated
+ * CriticalJNINatives feature. Methods with `Java` in their name implement some parts in Java.
+ */
+public final class FuzzerCallbacksOptimizedNonCritical {
+ static {
+ RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacksOptimizedNonCritical.class);
+ }
+
+ static native void traceSwitch(long val, long[] cases, int pc);
+
+ static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc);
+
+ static native void traceStrstr(String s1, String s2, int pc);
+
+ static void traceStrstrJava(String haystack, String needle, int pc)
+ throws UnsupportedEncodingException {
+ // Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently
+ // converts CESU8 into modified UTF-8 by coding null bytes on two bytes. Since the fuzzer is
+ // more likely to insert literal null bytes, having both the fuzzer input and the reported
+ // string comparisons be CESU8 should perform even better than the current implementation using
+ // modified UTF-8.
+ traceStrstrInternal(needle.substring(0, Math.min(needle.length(), 64)).getBytes("CESU8"), pc);
+ }
+
+ private static native void traceStrstrInternal(byte[] needle, int pc);
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java
new file mode 100644
index 00000000..ce3d6290
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java
@@ -0,0 +1,59 @@
+// Copyright 2022 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.runtime;
+
+import com.github.fmeum.rules_jni.RulesJni;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import jdk.incubator.foreign.CLinker;
+import jdk.incubator.foreign.FunctionDescriptor;
+import jdk.incubator.foreign.MemoryAddress;
+import jdk.incubator.foreign.MemoryLayout;
+import jdk.incubator.foreign.MemorySegment;
+import jdk.incubator.foreign.ResourceScope;
+import jdk.incubator.foreign.SymbolLookup;
+
+/**
+ * Pure-Java implementation of the fuzzer callbacks backed by Project Panama (requires JDK 16+).
+ * To include the implementation in the benchmark on a supported JDK, uncomment the relevant lines
+ * in BUILD.bazel.
+ */
+public class FuzzerCallbacksPanama {
+ static {
+ RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacks.class);
+ }
+
+ private static final MethodHandle traceCmp4 = CLinker.getInstance().downcallHandle(
+ SymbolLookup.loaderLookup().lookup("__sanitizer_cov_trace_cmp4").get(),
+ MethodType.methodType(void.class, int.class, int.class),
+ FunctionDescriptor.ofVoid(CLinker.C_INT, CLinker.C_INT));
+ private static final MethodHandle traceSwitch = CLinker.getInstance().downcallHandle(
+ SymbolLookup.loaderLookup().lookup("__sanitizer_cov_trace_switch").get(),
+ MethodType.methodType(void.class, long.class, MemoryAddress.class),
+ FunctionDescriptor.ofVoid(CLinker.C_LONG, CLinker.C_POINTER));
+
+ static void traceCmpInt(int arg1, int arg2, int pc) throws Throwable {
+ traceCmp4.invokeExact(arg1, arg2);
+ }
+
+ static void traceCmpSwitch(long val, long[] cases, int pc) throws Throwable {
+ try (ResourceScope scope = ResourceScope.newConfinedScope()) {
+ MemorySegment nativeCopy = MemorySegment.allocateNative(
+ MemoryLayout.sequenceLayout(cases.length, CLinker.C_LONG), scope);
+ nativeCopy.copyFrom(MemorySegment.ofArray(cases));
+ traceSwitch.invokeExact(val, nativeCopy.address());
+ }
+ }
+}
diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java
new file mode 100644
index 00000000..21f416cf
--- /dev/null
+++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java
@@ -0,0 +1,31 @@
+// Copyright 2022 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.runtime;
+
+import com.github.fmeum.rules_jni.RulesJni;
+
+/**
+ * Unoptimized implementation of the libFuzzer callbacks that use the trampoline construction to
+ * inject fake PCs.
+ */
+public final class FuzzerCallbacksWithPc {
+ static {
+ RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacksWithPc.class);
+ }
+
+ static native void traceCmpInt(int arg1, int arg2, int pc);
+
+ static native void traceSwitch(long val, long[] cases, int pc);
+}