Update and simplify Jazzer integration (#218)

* Update Jazzer to v0.17.1

* Add support for sanitizer runtimes

* Refactor oss-fuzz support

* Address review comments
diff --git a/.bazelrc b/.bazelrc
index ebe78ed..5623e30 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -16,6 +16,9 @@
 build --action_env=CC=clang-10
 build --action_env=CXX=clang++-10
 
+# Workaround for https://github.com/bazelbuild/bazel/issues/3236
+build --sandbox_tmpfs_path=/tmp
+
 # Strict dependency check for C++ includes.
 build --features=layering_check
 
@@ -64,6 +67,7 @@
 build:asan-replay --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan
 
 build:oss-fuzz --//fuzzing:cc_engine=@rules_fuzzing_oss_fuzz//:oss_fuzz_engine
+build:oss-fuzz --//fuzzing:java_engine=@rules_fuzzing_oss_fuzz//:oss_fuzz_java_engine
 build:oss-fuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=oss-fuzz
 build:oss-fuzz --@rules_fuzzing//fuzzing:cc_engine_sanitizer=none
 
diff --git a/README.md b/README.md
index c3fd6e3..0aa2822 100644
--- a/README.md
+++ b/README.md
@@ -156,25 +156,7 @@
 
 ### Java fuzzing
 
-You can write `java_fuzz_test`s through the [Jazzer][jazzer-doc] fuzzing engine. You will need to enable it in your WORKSPACE `rules_fuzzing_dependencies` call:
-
-```python
-load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
-
-rules_fuzzing_dependencies(jazzer = True)
-
-load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")
-
-rules_fuzzing_init()
-
-load("@jazzer//:repositories.bzl", "jazzer_dependencies")
-
-jazzer_dependencies()
-
-load("@jazzer//:init.bzl", "jazzer_init")
-
-jazzer_init()
-```
+You can write `java_fuzz_test`s through the [Jazzer][jazzer-doc] fuzzing engine.
 
 To use Jazzer, it is convenient to also define a `.bazelrc` configuration, similar to the C++ libFuzzer one above:
 
diff --git a/WORKSPACE b/WORKSPACE
index 14d26a2..e893f5c 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -20,7 +20,7 @@
 
 load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
 
-rules_fuzzing_dependencies(jazzer = True)
+rules_fuzzing_dependencies()
 
 load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")
 
@@ -30,14 +30,6 @@
 
 install_deps()
 
-load("@jazzer//:repositories.bzl", "jazzer_dependencies")
-
-jazzer_dependencies()
-
-load("@jazzer//:init.bzl", "jazzer_init")
-
-jazzer_init()
-
 # The support for running the examples and unit tests.
 
 http_archive(
diff --git a/fuzzing/engines/BUILD b/fuzzing/engines/BUILD
index 82247fc..541b265 100644
--- a/fuzzing/engines/BUILD
+++ b/fuzzing/engines/BUILD
@@ -68,6 +68,16 @@
     name = "jazzer",
     display_name = "Jazzer",
     launcher = "jazzer_launcher.sh",
-    library = "@jazzer//agent:jazzer_api_compile_only",
+    library = ":jazzer_stub",
     visibility = ["//visibility:public"],
 )
+
+# This wrapper target is needed as Jazzer consists of two separate Java targets,
+# but java_fuzzing_engine's library attribute only accepts a single target.
+java_library(
+    name = "jazzer_stub",
+    exports = [
+        "@rules_fuzzing_jazzer//jar",
+        "@rules_fuzzing_jazzer_api//jar",
+    ],
+)
diff --git a/fuzzing/private/BUILD b/fuzzing/private/BUILD
index 3eb5186..2c41ca3 100644
--- a/fuzzing/private/BUILD
+++ b/fuzzing/private/BUILD
@@ -29,36 +29,28 @@
     "util.bzl",
 ])
 
-# Config settings needed for prebuilt engines.
 config_setting(
-    name = "use_sanitizer_none",
+    name = "is_oss_fuzz",
     flag_values = {
-        "@rules_fuzzing//fuzzing:cc_engine_sanitizer": "none",
+        "@rules_fuzzing//fuzzing:cc_engine": "@rules_fuzzing_oss_fuzz//:oss_fuzz_engine",
     },
+    visibility = ["//visibility:public"],
 )
 
 config_setting(
-    name = "use_sanitizer_asan",
+    name = "use_asan",
     flag_values = {
         "@rules_fuzzing//fuzzing:cc_engine_sanitizer": "asan",
     },
+    visibility = ["//visibility:public"],
 )
 
 config_setting(
-    name = "use_sanitizer_ubsan",
+    name = "use_ubsan",
     flag_values = {
         "@rules_fuzzing//fuzzing:cc_engine_sanitizer": "ubsan",
     },
-)
-
-config_setting(
-    name = "use_oss_fuzz",
-    flag_values = {
-        "@rules_fuzzing//fuzzing:cc_engine": "@rules_fuzzing_oss_fuzz//:oss_fuzz_engine",
-        # This is required to make the setting an unambiguous specialization of
-        # the use_sanitizer_* settings.
-        "@rules_fuzzing//fuzzing:cc_engine_sanitizer": "none",
-    },
+    visibility = ["//visibility:public"],
 )
 
 exports_files([
diff --git a/fuzzing/private/fuzz_test.bzl b/fuzzing/private/fuzz_test.bzl
index 3a21942..b35024a 100644
--- a/fuzzing/private/fuzz_test.bzl
+++ b/fuzzing/private/fuzz_test.bzl
@@ -14,6 +14,7 @@
 
 """The implementation of the {cc, java}_fuzz_test rules."""
 
+load("@rules_fuzzing_oss_fuzz//:instrum.bzl", "native_library_sanitizer")
 load("@rules_cc//cc:defs.bzl", "cc_binary")
 
 # FIXME: Including this leads to a Stardoc error since defs.bzl is not visible. As a workaround, use native.java_binary.
@@ -213,6 +214,15 @@
         test_timeout = timeout,
     )
 
+_ASAN_RUNTIME = Label("//fuzzing/private/runtime:asan")
+_UBSAN_RUNTIME = Label("//fuzzing/private/runtime:ubsan")
+_RUNTIME_BY_NAME = {
+    "asan": _ASAN_RUNTIME,
+    "ubsan": _UBSAN_RUNTIME,
+    "none": None,
+}
+
+# buildifier: disable=list-append
 def java_fuzz_test(
         name,
         srcs = None,
@@ -264,6 +274,8 @@
     # this target directly. Instead, the binary should be built through the
     # instrumented configuration.
     raw_target_name = name + "_target_"
+    metadata_binary_name = name + "_metadata_"
+    metadata_deploy_jar_name = metadata_binary_name + "_deploy.jar"
 
     # Determine a value for target_class heuristically using the same rules as
     # those used by Bazel internally for main_class.
@@ -277,62 +289,71 @@
             name = name,
         ))
     target_class_manifest_line = "Jazzer-Fuzz-Target-Class: %s" % target_class
-    binary_kwargs.setdefault("deps", [])
 
-    # Use += rather than append to allow users to pass in select() expressions for
+    native.java_binary(
+        name = metadata_binary_name,
+        deploy_manifest_lines = [target_class_manifest_line],
+        tags = ["manual"],
+    )
+
+    # use += rather than append to allow users to pass in select() expressions for
     # deps, which only support concatenation with +.
-    # Workaround for https://github.com/bazelbuild/bazel/issues/14157.
-    # buildifier: disable=list-append
-    binary_kwargs["deps"] += [engine]
-    binary_kwargs.setdefault("deploy_manifest_lines", [])
+    # workaround for https://github.com/bazelbuild/bazel/issues/14157.
+    if srcs:
+        binary_kwargs.setdefault("deps", [])
+        binary_kwargs["deps"] += [engine, metadata_deploy_jar_name]
+    else:
+        binary_kwargs.setdefault("runtime_deps", [])
+        binary_kwargs["runtime_deps"] += [engine, metadata_deploy_jar_name]
 
-    # buildifier: disable=list-append
-    binary_kwargs["deploy_manifest_lines"] += [target_class_manifest_line]
+    binary_kwargs.setdefault("jvm_flags", [])
+    binary_kwargs["jvm_flags"] = [
+        # Ensures that full stack traces are emitted for findings even in highly
+        # optimized code.
+        "-XX:-OmitStackTraceInFastThrow",
+        # Optimized for throughput rather than latency.
+        "-XX:+UseParallelGC",
+        # Ignore CriticalJNINatives if not available (JDK 18+).
+        "-XX:+IgnoreUnrecognizedVMOptions",
+        # Improves performance of Jazzer's native compare instrumentation.
+        "-XX:+CriticalJNINatives",
+    ] + binary_kwargs["jvm_flags"]
 
     # tags is not configurable and can thus use append.
     binary_kwargs.setdefault("tags", []).append("manual")
     native.java_binary(
         name = raw_target_name,
         srcs = srcs,
-        create_executable = False,
+        main_class = "com.code_intelligence.jazzer.Jazzer",
         **binary_kwargs
     )
 
     raw_binary_name = name + "_raw_"
     jazzer_fuzz_binary(
         name = raw_binary_name,
-        agent = select({
-            "@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing_oss_fuzz//:jazzer_agent_deploy.jar",
-            "//conditions:default": "@jazzer//agent:jazzer_agent_deploy.jar",
+        sanitizer = select({
+            "@rules_fuzzing//fuzzing/private:is_oss_fuzz": native_library_sanitizer,
+            "@rules_fuzzing//fuzzing/private:use_asan": "asan",
+            "@rules_fuzzing//fuzzing/private:use_ubsan": "ubsan",
+            "//conditions:default": "none",
         }),
-        # Since the choice of sanitizer is explicit for local fuzzing, we also
-        # let it apply to projects with no native dependencies.
-        driver_java_only = select({
-            "@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing_oss_fuzz//:jazzer_driver",
-            "@rules_fuzzing//fuzzing/private:use_sanitizer_none": "@jazzer//driver:jazzer_driver",
-            "@rules_fuzzing//fuzzing/private:use_sanitizer_asan": "@jazzer//driver:jazzer_driver_asan",
-            "@rules_fuzzing//fuzzing/private:use_sanitizer_ubsan": "@jazzer//driver:jazzer_driver_ubsan",
-        }, no_match_error = "Jazzer only supports the sanitizer settings: \"none\", \"asan\", \"ubsan\""),
-        driver_with_native = select({
-            "@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing_oss_fuzz//:jazzer_driver_with_sanitizer",
-            "@rules_fuzzing//fuzzing/private:use_sanitizer_none": "@jazzer//driver:jazzer_driver",
-            "@rules_fuzzing//fuzzing/private:use_sanitizer_asan": "@jazzer//driver:jazzer_driver_asan",
-            "@rules_fuzzing//fuzzing/private:use_sanitizer_ubsan": "@jazzer//driver:jazzer_driver_ubsan",
-        }, no_match_error = "Jazzer only supports the sanitizer settings: \"none\", \"asan\", \"ubsan\""),
         sanitizer_options = select({
-            "@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing//fuzzing/private:oss_fuzz_jazzer_sanitizer_options.sh",
-            "//conditions:default": "@rules_fuzzing//fuzzing/private:local_jazzer_sanitizer_options.sh",
+            "@rules_fuzzing//fuzzing/private:is_oss_fuzz": Label("//fuzzing/private:oss_fuzz_jazzer_sanitizer_options.sh"),
+            "//conditions:default": Label("//fuzzing/private:local_jazzer_sanitizer_options.sh"),
         }),
-        tags = ["manual"],
+        sanitizer_runtime = select({
+            "@rules_fuzzing//fuzzing/private:is_oss_fuzz": _RUNTIME_BY_NAME[native_library_sanitizer],
+            "@rules_fuzzing//fuzzing/private:use_asan": _ASAN_RUNTIME,
+            "@rules_fuzzing//fuzzing/private:use_ubsan": _UBSAN_RUNTIME,
+            "//conditions:default": None,
+        }),
         target = raw_target_name,
-        target_deploy_jar = raw_target_name + "_deploy.jar",
+        tags = ["manual"],
     )
 
     fuzzing_decoration(
         name = name,
         raw_binary = raw_binary_name,
-        # jazzer_fuzz_binary already instrumented the native dependencies.
-        instrument_binary = False,
         engine = engine,
         corpus = corpus,
         dicts = dicts,
diff --git a/fuzzing/private/java_utils.bzl b/fuzzing/private/java_utils.bzl
index 70c4e24..10b4bb7 100644
--- a/fuzzing/private/java_utils.bzl
+++ b/fuzzing/private/java_utils.bzl
@@ -81,7 +81,7 @@
 
     return root_index
 
-def _jazzer_fuzz_binary_script(ctx, native_libs, driver):
+def _jazzer_fuzz_binary_script(ctx, target, sanitizer_flags):
     script = ctx.actions.declare_file(ctx.label.name)
 
     # The script is split into two parts: The first is emitted as-is, the second
@@ -93,16 +93,16 @@
 
 # Bazel-provided code snippet that should be copy-pasted as is at use sites.
 # Taken from @bazel_tools//tools/bash/runfiles.
-# --- begin runfiles.bash initialization v2 ---
-# Copy-pasted from the Bazel Bash runfiles library v2.
-set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+# --- begin runfiles.bash initialization v3 ---
+# Copy-pasted from the Bazel Bash runfiles library v3.
+set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
 source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
-source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
-source "$0.runfiles/$f" 2>/dev/null || \
-source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
-source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
-{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
-# --- end runfiles.bash initialization v2 ---
+  source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+  source "$0.runfiles/$f" 2>/dev/null || \
+  source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+  source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+  { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v3 ---
 
 # Export the env variables required for subprocesses to find their runfiles.
 runfiles_export_envvars
@@ -116,127 +116,43 @@
 
     script_format_part = """
 source "$(rlocation {sanitizer_options})"
-exec "$(rlocation {driver})" \
-    --agent_path="$(rlocation {agent})" \
-    --cp="$(rlocation {deploy_jar})" \
-    --jvm_args="-Djava.library.path={native_dirs}" \
-    "$@"
+if [[ ! -z "{sanitizer_runtime}" ]]; then
+  export JAZZER_NATIVE_SANITIZERS_DIR=$(dirname "$(rlocation "{sanitizer_runtime}")")
+fi
+exec "$(rlocation {target})" {sanitizer_flags} "$@"
 """
 
-    native_dirs = [
-        "$(dirname \"$(rlocation %s)\")" % runfile_path(ctx, lib)
-        for lib in native_libs
-    ]
-
     script_content = script_literal_part + script_format_part.format(
-        agent = runfile_path(ctx, ctx.file.agent),
-        deploy_jar = runfile_path(ctx, ctx.file.target_deploy_jar),
-        driver = runfile_path(ctx, driver),
-        # Jazzer requires the path separator to be escaped in --jvm_args.
-        # See:
-        # https://github.com/CodeIntelligenceTesting/jazzer#passing-jvm-arguments
-        native_dirs = "\\:".join(native_dirs),
+        target = runfile_path(ctx, target),
+        sanitizer_flags = " ".join(sanitizer_flags),
         sanitizer_options = runfile_path(ctx, ctx.file.sanitizer_options),
+        sanitizer_runtime = runfile_path(ctx, ctx.file.sanitizer_runtime) if ctx.file.sanitizer_runtime else "",
     )
     ctx.actions.write(script, script_content, is_executable = True)
     return script
 
-def _is_required_runfile(runfile, runtime_classpath = []):
-    # The jars in the runtime classpath are all merged into the deploy jar and
-    # thus don't need to be included in the runfiles for the fuzzer.
-    if runfile in runtime_classpath:
-        return False
-
-    # A java_binary target has a dependency on the local JDK. Since the Jazzer
-    # driver launches its own JVM, these runfiles are not needed.
-    if runfile.owner != None and runfile.owner.workspace_name == "local_jdk":
-        return False
-    return True
-
-def _filter_target_runfiles(ctx, target):
-    compilation_info = target[JavaInfo].compilation_info
-    runtime_classpath = compilation_info.runtime_classpath.to_list()
-    all_runfiles = target[DefaultInfo].default_runfiles
-    return ctx.runfiles([
-        runfile
-        for runfile in all_runfiles.files.to_list()
-        if _is_required_runfile(runfile, runtime_classpath)
-    ])
-
-def _is_potential_native_dependency(file):
-    if file.extension not in ["dll", "dylib", "so"]:
-        return False
-    if not _is_required_runfile(file):
-        return False
-    return True
-
-def _native_library_files(ctx):
-    target_info = ctx.attr.target[0][DefaultInfo]
-    target_java_info = ctx.attr.target[0][JavaInfo]
-
-    # Perform feature detection for
-    # https://github.com/bazelbuild/bazel/commit/381a519dfc082d4c62096c4ce77ead1c2e0410d8.
-    if hasattr(target_java_info, "transitive_native_libraries"):
-        # The current version of Bazel contains the commit, which means that
-        # the JavaInfo of the target includes information about all transitive
-        # native library dependencies.
-        native_libraries_list = target_java_info.transitive_native_libraries.to_list()
-        return [
-            lib.dynamic_library
-            for lib in native_libraries_list
-            if lib.dynamic_library != None
-        ]
-    else:
-        # If precise information about transitive native libraries is not
-        # available, fall back to an overapproximation that includes all
-        # runfiles with file extensions indicating a shared library.
-        runfiles_list = target_info.default_runfiles.files.to_list()
-        return [
-            runfile
-            for runfile in runfiles_list
-            if _is_potential_native_dependency(runfile)
-        ]
-
 def _jazzer_fuzz_binary_impl(ctx):
-    native_libs = _native_library_files(ctx)
-
-    # Use a driver with a linked in sanitizer if the fuzz test has native
-    # dependencies.
-    if native_libs:
-        driver = ctx.executable.driver_with_native
-        driver_info = ctx.attr.driver_with_native[DefaultInfo]
-    else:
-        driver = ctx.executable.driver_java_only
-        driver_info = ctx.attr.driver_java_only[DefaultInfo]
-
-    # The DefaultInfo's default_runfiles of an executable file target do not
-    # contain the executable itself, which thus needs to be added explicitly.
-    driver_runfiles = driver_info.default_runfiles
-    driver_executable = driver_info.files_to_run.executable
-    driver_runfiles = driver_runfiles.merge(ctx.runfiles([driver_executable]))
-
-    runfiles = ctx.runfiles()
-    runfiles = runfiles.merge(driver_runfiles)
+    sanitizer = ctx.attr.sanitizer
+    sanitizer_flags = []
+    if sanitizer in ["asan", "ubsan"]:
+        sanitizer_flags.append("--" + sanitizer)
+    if not sanitizer_flags and ctx.attr.target[0][JavaInfo].transitive_native_libraries:
+        sanitizer_flags.append("--native")
 
     # Used by the wrapper script created in _jazzer_fuzz_binary_script.
-    runfiles = runfiles.merge(ctx.attr._bash_runfiles_library[DefaultInfo].default_runfiles)
+    transitive_runfiles = [
+        ctx.attr.target[0][DefaultInfo].default_runfiles,
+        ctx.attr._bash_runfiles_library[DefaultInfo].default_runfiles,
+        ctx.runfiles(
+            [
+                ctx.file.sanitizer_options,
+            ] + ([ctx.file.sanitizer_runtime] if ctx.file.sanitizer_runtime else []),
+        ),
+    ]
+    runfiles = ctx.runfiles().merge_all(transitive_runfiles)
 
-    # While the Jazzer agent is already included in the runfiles of
-    # @jazzer//driver:jazzer_driver, it has to be added here explicitly for the
-    # case where both are provided by OSS-Fuzz.
-    runfiles = runfiles.merge(ctx.runfiles([ctx.file.agent]))
-
-    # The Java fuzz target packaged as a jar including all Java dependencies.
-    # This does not include e.g. data runfiles and shared libraries.
-    runfiles = runfiles.merge(ctx.runfiles([ctx.file.target_deploy_jar]))
-
-    # The full runfiles of the Java fuzz target, but with the files of the local
-    # JDK and all jar files excluded.
-    runfiles = runfiles.merge(_filter_target_runfiles(ctx, ctx.attr.target[0]))
-
-    runfiles = runfiles.merge(ctx.runfiles([ctx.file.sanitizer_options]))
-
-    script = _jazzer_fuzz_binary_script(ctx, native_libs, driver)
+    target = ctx.attr.target[0][DefaultInfo].files_to_run.executable
+    script = _jazzer_fuzz_binary_script(ctx, target, sanitizer_flags)
     return [DefaultInfo(executable = script, runfiles = runfiles)]
 
 jazzer_fuzz_binary = rule(
@@ -245,52 +161,31 @@
 Rule that creates a binary that invokes Jazzer on the specified target.
 """,
     attrs = {
-        "agent": attr.label(
-            doc = "The Jazzer agent used to instrument the target.",
-            allow_single_file = [".jar"],
-        ),
-        "_bash_runfiles_library": attr.label(
-            default = "@bazel_tools//tools/bash/runfiles",
-        ),
-        "driver_java_only": attr.label(
-            doc = "The Jazzer driver binary used to fuzz a Java-only target.",
-            allow_single_file = True,
-            executable = True,
-            # Build in target configuration rather than host because the driver
-            # uses transitions to set the correct C++ standard for its
-            # dependencies.
-            cfg = "target",
-        ),
-        "driver_with_native": attr.label(
-            doc = "The Jazzer driver binary used to fuzz a Java target with " +
-                  "native dependencies.",
-            allow_single_file = True,
-            executable = True,
-            # Build in target configuration rather than host because the driver
-            # uses transitions to set the correct C++ standard for its
-            # dependencies.
-            cfg = "target",
+        "sanitizer": attr.string(
+            values = ["asan", "ubsan", "none"],
         ),
         "sanitizer_options": attr.label(
             doc = "A shell script that can export environment variables with " +
                   "sanitizer options.",
             allow_single_file = [".sh"],
         ),
+        "sanitizer_runtime": attr.label(
+            doc = "The sanitizer runtime to preload.",
+            allow_single_file = [".dylib", ".so"],
+        ),
         "target": attr.label(
             doc = "The fuzz target.",
             mandatory = True,
             providers = [JavaInfo],
             cfg = fuzzing_binary_transition,
         ),
-        "target_deploy_jar": attr.label(
-            doc = "The deploy jar of the fuzz target.",
-            allow_single_file = [".jar"],
-            mandatory = True,
-            cfg = fuzzing_binary_transition,
-        ),
+        "use_oss_fuzz": attr.bool(),
         "_allowlist_function_transition": attr.label(
             default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
         ),
+        "_bash_runfiles_library": attr.label(
+            default = "@bazel_tools//tools/bash/runfiles",
+        ),
     },
     executable = True,
 )
diff --git a/fuzzing/private/oss_fuzz/BUILD.tpl b/fuzzing/private/oss_fuzz/BUILD.tpl
index aa41c48..2082067 100644
--- a/fuzzing/private/oss_fuzz/BUILD.tpl
+++ b/fuzzing/private/oss_fuzz/BUILD.tpl
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzzing_engine")
+load("@rules_fuzzing//fuzzing:java_defs.bzl", "java_fuzzing_engine")
 load("@rules_cc//cc:defs.bzl", "cc_library")
 
 cc_fuzzing_engine(
@@ -29,6 +30,18 @@
     linkopts = [%{stub_linkopts}],
 )
 
-exports_files([
-    "instrum.bzl", %{exported_files}
-])
\ No newline at end of file
+java_fuzzing_engine(
+    name = "oss_fuzz_java_engine",
+    display_name = "OSS-Fuzz (Java)",
+    launcher = "oss_fuzz_launcher.sh",
+    library = ":oss_fuzz_java_stub",
+    visibility = ["//visibility:public"],
+)
+
+java_import(
+    name = "oss_fuzz_java_stub",
+    jars = [%{jazzer_jars}],
+)
+
+
+exports_files(["instrum.bzl"])
diff --git a/fuzzing/private/oss_fuzz/instrum.bzl.tpl b/fuzzing/private/oss_fuzz/instrum.bzl.tpl
index 04f1e54..14a34d4 100644
--- a/fuzzing/private/oss_fuzz/instrum.bzl.tpl
+++ b/fuzzing/private/oss_fuzz/instrum.bzl.tpl
@@ -20,3 +20,5 @@
     conlyopts = [%{conlyopts}],
     cxxopts = [%{cxxopts}],
 )
+
+native_library_sanitizer = "%{sanitizer}"
diff --git a/fuzzing/private/oss_fuzz/package.bzl b/fuzzing/private/oss_fuzz/package.bzl
index e5e9dc4..e0ae8ed 100644
--- a/fuzzing/private/oss_fuzz/package.bzl
+++ b/fuzzing/private/oss_fuzz/package.bzl
@@ -34,8 +34,9 @@
         # the runfiles here. This deviates from the usual Bazel runfiles layout,
         # but is required since ClusterFuzz executes fuzz targets in
         # subdirectories and would thus duplicate every C++ fuzz target.
+        # We also exclude the local JDK as OSS-Fuzz provides one.
         for runfile in binary_runfiles
-        if runfile != binary_info.binary_file
+        if runfile != binary_info.binary_file and not runfile_path(ctx, runfile).startswith("local_jdk/")
     ])
     ctx.actions.write(runfiles_manifest, runfiles_manifest_content, False)
     archive_inputs.append(runfiles_manifest)
diff --git a/fuzzing/private/oss_fuzz/repository.bzl b/fuzzing/private/oss_fuzz/repository.bzl
index 45212e5..0f81811 100644
--- a/fuzzing/private/oss_fuzz/repository.bzl
+++ b/fuzzing/private/oss_fuzz/repository.bzl
@@ -70,22 +70,13 @@
         instrum_cxxopts = instrum_cxxopts,
     )
 
-# The filenames under which the various Jazzer binaries are available in $OUT
-# and in @rules_fuzzing_oss_fuzz.
-_JAZZER_BINARIES = [
-    "jazzer_agent_deploy.jar",
-    "jazzer_driver",
-    "jazzer_driver_with_sanitizer",
-]
+_JAZZER_JAR = "jazzer_agent_deploy.jar"
 
-def _export_jazzer(repository_ctx, out_path):
+def _link_jazzer_jars(repository_ctx, out_path):
     if out_path == None:
         return []
-    exported_files = []
-    for jazzer_binary in _JAZZER_BINARIES:
-        repository_ctx.symlink(out_path + "/" + jazzer_binary, jazzer_binary)
-        exported_files.append(jazzer_binary)
-    return exported_files
+    repository_ctx.symlink(out_path + "/" + _JAZZER_JAR, _JAZZER_JAR)
+    return [_JAZZER_JAR]
 
 def _oss_fuzz_repository(repository_ctx):
     environ = repository_ctx.os.environ
@@ -102,7 +93,6 @@
         cflags.split(" "),
         cxxflags.split(" "),
     )
-    exported_files = _export_jazzer(repository_ctx, out_path)
 
     repository_ctx.template(
         "BUILD",
@@ -110,7 +100,7 @@
         {
             "%{stub_srcs}": _to_list_repr(build_params.stub_srcs),
             "%{stub_linkopts}": _to_list_repr(build_params.stub_linkopts),
-            "%{exported_files}": _to_list_repr(exported_files),
+            "%{jazzer_jars}": _to_list_repr(_link_jazzer_jars(repository_ctx, out_path)),
         },
     )
     repository_ctx.template(
@@ -119,6 +109,10 @@
         {
             "%{conlyopts}": _to_list_repr(build_params.instrum_conlyopts),
             "%{cxxopts}": _to_list_repr(build_params.instrum_cxxopts),
+            "%{sanitizer}": {
+                "address": "asan",
+                "undefined": "ubsan",
+            }.get(sanitizer, "none"),
         },
     )
     repository_ctx.file(
diff --git a/fuzzing/private/runtime/BUILD b/fuzzing/private/runtime/BUILD
new file mode 100644
index 0000000..1e35809
--- /dev/null
+++ b/fuzzing/private/runtime/BUILD
@@ -0,0 +1,53 @@
+load(":clang_runtime_lib.bzl", "clang_runtime_lib")
+
+alias(
+    name = "asan",
+    actual = select({
+        "@platforms//os:linux": ":asan_linux",
+        "@platforms//os:macos": ":asan_macos",
+    }),
+    visibility = ["//visibility:public"],
+)
+
+alias(
+    name = "ubsan",
+    actual = select({
+        "@platforms//os:linux": ":ubsan_linux",
+        "@platforms//os:macos": ":ubsan_macos",
+    }),
+    visibility = ["//visibility:public"],
+)
+
+clang_runtime_lib(
+    name = "asan_linux",
+    basenames = [
+        # LLVM 15+
+        "libclang_rt.asan.so",
+        # LLVM 14 and earlier
+        "libclang_rt.asan-x86_64.so",
+    ],
+    tags = ["manual"],
+)
+
+clang_runtime_lib(
+    name = "asan_macos",
+    basenames = ["libclang_rt.asan_osx_dynamic.dylib"],
+    tags = ["manual"],
+)
+
+clang_runtime_lib(
+    name = "ubsan_linux",
+    basenames = [
+        # LLVM 15+
+        "libclang_rt.ubsan_standalone.so",
+        # LLVM 14 and earlier
+        "libclang_rt.ubsan_standalone-x86_64.so",
+    ],
+    tags = ["manual"],
+)
+
+clang_runtime_lib(
+    name = "ubsan_macos",
+    basenames = ["libclang_rt.ubsan_osx_dynamic.dylib"],
+    tags = ["manual"],
+)
diff --git a/fuzzing/private/runtime/clang_runtime_lib.bzl b/fuzzing/private/runtime/clang_runtime_lib.bzl
new file mode 100644
index 0000000..6098ff2
--- /dev/null
+++ b/fuzzing/private/runtime/clang_runtime_lib.bzl
@@ -0,0 +1,29 @@
+# Copyright 2023 Google LLC
+#
+# 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
+#
+#     https://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.
+
+"""Macro that extracts clang runtime libraries from the current cc_toolchain."""
+
+def clang_runtime_lib(*, name, basenames, **kwargs):
+    """Provide the first available clang runtime library with any of the given basenames as output.
+
+    The basename of the output file is always the first of the given basenames.
+    """
+    native.genrule(
+        name = name,
+        outs = basenames[:1],
+        cmd = "\n".join(["""cp -f "$$($(CC) --print-file-name {})" $@ 2> /dev/null || true""".format(basename) for basename in basenames]),
+        toolchains = ["@bazel_tools//tools/cpp:current_cc_toolchain"],
+        tools = ["@bazel_tools//tools/cpp:current_cc_toolchain"],
+        **kwargs
+    )
diff --git a/fuzzing/repositories.bzl b/fuzzing/repositories.bzl
index b823967..f107b1b 100644
--- a/fuzzing/repositories.bzl
+++ b/fuzzing/repositories.bzl
@@ -14,19 +14,17 @@
 
 """Contains the external dependencies."""
 
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_jar")
 load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
 load("//fuzzing/private/oss_fuzz:repository.bzl", "oss_fuzz_repository")
 
-def rules_fuzzing_dependencies(oss_fuzz = True, honggfuzz = True, jazzer = False):
+def rules_fuzzing_dependencies(oss_fuzz = True, honggfuzz = True, jazzer = True):
     """Instantiates the dependencies of the fuzzing rules.
 
     Args:
       oss_fuzz: Include OSS-Fuzz dependencies.
       honggfuzz: Include Honggfuzz dependencies.
-      jazzer: Include Jazzer repository. Instantiating all Jazzer dependencies
-        additionally requires invoking jazzer_dependencies() in
-        @jazzer//:repositories.bzl and jazzer_init() in @jazzer//:init.bzl.
+      jazzer: Include Jazzer dependencies.
     """
 
     maybe(
@@ -80,9 +78,15 @@
 
     if jazzer:
         maybe(
-            http_archive,
-            name = "jazzer",
-            sha256 = "c55889c235501498ca7436f57974ea59f0dc43e9effd64e13ce0c535265b8224",
-            strip_prefix = "jazzer-4434041f088365acf2a561e678bf9d61a7aa5dff",
-            url = "https://github.com/CodeIntelligenceTesting/jazzer/archive/4434041f088365acf2a561e678bf9d61a7aa5dff.zip",
+            http_jar,
+            name = "rules_fuzzing_jazzer",
+            sha256 = "ccf5379c8c296bdcf0dda9b2253a7a34ce0726aa69c00a09bc66c38146167a30",
+            url = "https://repo1.maven.org/maven2/com/code-intelligence/jazzer/0.17.1/jazzer-0.17.1.jar",
+        )
+
+        maybe(
+            http_jar,
+            name = "rules_fuzzing_jazzer_api",
+            sha256 = "b73cbbbda3b9eba14b3060d706f173e59c3512fb84fd0a4f3b0906541232d6e4",
+            url = "https://repo1.maven.org/maven2/com/code-intelligence/jazzer-api/0.17.1/jazzer-api-0.17.1.jar",
         )