Support for using honggfuzz with gcc (#288)

This allows running honggfuzz with a gcc toolchain which can be useful
for code bases that don't compile with clang or where no clang toolchain
is available.


Example usage:

```
bazel run //examples:re2_fuzz_test --config asan-honggfuzz
```

---------

Co-authored-by: Simon Resch <simon.resch@code-intelligence.de>
Co-authored-by: Markus Zoppelt <markus@zoppelt.net>
Co-authored-by: Markus Zoppelt <markus.zoppelt@code-intelligence.com>
diff --git a/.bazelrc b/.bazelrc
index f64a009..b666ec6 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Force the use of Clang for all builds.
+# Set clang as the default compiler for most configurations.
 build --repo_env=CC=clang
 
 # Strict dependency check for C++ includes.
@@ -68,6 +68,12 @@
 build:ubsan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=honggfuzz
 build:ubsan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_sanitizer=ubsan
 
+# Honggfuzz + ASAN (GCC)
+build:asan-honggfuzz-gcc --//fuzzing:cc_engine=//fuzzing/engines:honggfuzz
+build:asan-honggfuzz-gcc --@rules_fuzzing//fuzzing:cc_engine_instrumentation=honggfuzz
+build:asan-honggfuzz-gcc --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan
+build:asan-honggfuzz-gcc --repo_env=CC=gcc
+
 # Replay + ASAN
 build:asan-replay --//fuzzing:cc_engine=//fuzzing/engines:replay
 build:asan-replay --@rules_fuzzing//fuzzing:cc_engine_instrumentation=none
diff --git a/README.md b/README.md
index 0aa2822..44f6fca 100644
--- a/README.md
+++ b/README.md
@@ -35,10 +35,10 @@
 
 The fuzzing rules have been tested on Bazel 4.0.0 or later. Check your Bazel version by running `bazel --version`.
 
-C++ fuzzing requires a Clang compiler. The libFuzzer engine requires at least Clang 6.0. In addition, the Honggfuzz engine requires the `libunwind-dev` and `libblocksruntime-dev` packages:
+The libFuzzer engine requires at least Clang 6.0. Honggfuzz works with both clang and gcc (8 or later) and requires the `libunwind-dev` and `libblocksruntime-dev` packages:
 
 ```sh
-$ sudo apt-get install clang libunwind-dev libblocksruntime-dev
+$ sudo apt-get install libunwind-dev libblocksruntime-dev
 ```
 
 Java fuzzing requires Clang and the LLD linker:
diff --git a/fuzzing/instrum_opts.bzl b/fuzzing/instrum_opts.bzl
index a6eadbe..19272e9 100644
--- a/fuzzing/instrum_opts.bzl
+++ b/fuzzing/instrum_opts.bzl
@@ -29,21 +29,35 @@
     "oss_fuzz_opts",
 )
 
-# Fuzz test binary instrumentation configurations.
+# Fuzz test binary instrumentation configurations by compiler type.
 instrum_configs = {
-    "none": instrum_opts.make(),
-    "libfuzzer": instrum_defaults.libfuzzer,
-    "jazzer": instrum_defaults.jazzer,
-    "honggfuzz": instrum_defaults.honggfuzz,
-    "oss-fuzz": oss_fuzz_opts,
+    "clang": {
+        "none": instrum_opts.make(),
+        "libfuzzer": instrum_defaults.libfuzzer,
+        "jazzer": instrum_defaults.jazzer,
+        "honggfuzz": instrum_defaults.honggfuzz_clang,
+        "oss-fuzz": oss_fuzz_opts,
+    },
+    "gcc": {
+        "none": instrum_opts.make(),
+        "honggfuzz": instrum_defaults.honggfuzz_gcc,
+    },
 }
 
-# Sanitizer configurations.
+# Sanitizer configurations by compiler type.
 sanitizer_configs = {
-    "none": instrum_opts.make(),
-    "asan": instrum_defaults.asan,
-    "msan": instrum_defaults.msan,
-    "msan-origin-tracking": instrum_defaults.msan_origin_tracking,
-    "ubsan": instrum_defaults.ubsan,
-    "asan-ubsan": instrum_opts.merge(instrum_defaults.asan, instrum_defaults.ubsan),
+    "clang": {
+        "none": instrum_opts.make(),
+        "asan": instrum_defaults.asan,
+        "msan": instrum_defaults.msan,
+        "msan-origin-tracking": instrum_defaults.msan_origin_tracking,
+        "ubsan": instrum_defaults.ubsan_clang,
+        "asan-ubsan": instrum_opts.merge(instrum_defaults.asan, instrum_defaults.ubsan_clang),
+    },
+    "gcc": {
+        "none": instrum_opts.make(),
+        "asan": instrum_defaults.asan,
+        "ubsan": instrum_defaults.ubsan_gcc,
+        "asan-ubsan": instrum_opts.merge(instrum_defaults.asan, instrum_defaults.ubsan_gcc),
+    },
 }
diff --git a/fuzzing/private/binary.bzl b/fuzzing/private/binary.bzl
index 8ff9723..622788d 100644
--- a/fuzzing/private/binary.bzl
+++ b/fuzzing/private/binary.bzl
@@ -47,29 +47,30 @@
     },
 )
 
-def _fuzzing_binary_transition_impl(settings, _attr):
+def _fuzzing_binary_transition_impl(settings, attr):
     opts = instrum_opts.make(
         copts = settings["//command_line_option:copt"],
         conlyopts = settings["//command_line_option:conlyopt"],
         cxxopts = settings["//command_line_option:cxxopt"],
         linkopts = settings["//command_line_option:linkopt"],
     )
+    compiler_type = getattr(attr, "compiler", "clang")
 
     is_fuzzing_build_mode = settings["@rules_fuzzing//fuzzing:cc_fuzzing_build_mode"]
     if is_fuzzing_build_mode:
         opts = instrum_opts.merge(opts, instrum_defaults.fuzzing_build)
 
     instrum_config = settings["@rules_fuzzing//fuzzing:cc_engine_instrumentation"]
-    if instrum_config in instrum_configs:
-        opts = instrum_opts.merge(opts, instrum_configs[instrum_config])
+    if instrum_config in instrum_configs[compiler_type]:
+        opts = instrum_opts.merge(opts, instrum_configs[compiler_type][instrum_config])
     else:
-        fail("unsupported engine instrumentation '%s'" % instrum_config)
+        fail("unsupported engine instrumentation '%s' for compiler '%s'" % (instrum_config, compiler_type))
 
     sanitizer_config = settings["@rules_fuzzing//fuzzing:cc_engine_sanitizer"]
-    if sanitizer_config in sanitizer_configs:
-        opts = instrum_opts.merge(opts, sanitizer_configs[sanitizer_config])
+    if sanitizer_config in sanitizer_configs[compiler_type]:
+        opts = instrum_opts.merge(opts, sanitizer_configs[compiler_type][sanitizer_config])
     else:
-        fail("unsupported sanitizer '%s'" % sanitizer_config)
+        fail("unsupported sanitizer '%s' for compiler '%s'" % (sanitizer_config, compiler_type))
 
     return {
         "//command_line_option:copt": opts.copts,
@@ -185,6 +186,11 @@
             cfg = fuzzing_binary_transition,
             mandatory = True,
         ),
+        "compiler": attr.string(
+            default = "clang",
+            values = ["clang", "gcc"],
+            doc = "The compiler used to build the fuzz test executable.",
+        ),
         "_allowlist_function_transition": attr.label(
             default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
         ),
diff --git a/fuzzing/private/extensions.bzl b/fuzzing/private/extensions.bzl
index 3700810..a6b0b68 100644
--- a/fuzzing/private/extensions.bzl
+++ b/fuzzing/private/extensions.bzl
@@ -18,7 +18,7 @@
 load("//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
 
 def _non_module_dependencies(mctx):
-    rules_fuzzing_dependencies()
+    rules_fuzzing_dependencies(bzlmod = True)
 
     if bazel_features.external_deps.extension_metadata_has_reproducible:
         return mctx.extension_metadata(reproducible = True)
diff --git a/fuzzing/private/fuzz_test.bzl b/fuzzing/private/fuzz_test.bzl
index a2e4cb5..60d2157 100644
--- a/fuzzing/private/fuzz_test.bzl
+++ b/fuzzing/private/fuzz_test.bzl
@@ -88,6 +88,10 @@
         fuzzing_binary(
             name = instrum_binary_name,
             binary = raw_binary,
+            compiler = select({
+                str(Label("@rules_cc//cc/compiler:gcc")): "gcc",
+                "//conditions:default": "clang",
+            }),
             engine = engine,
             corpus = corpus_name,
             dictionary = dict_name if dicts else None,
diff --git a/fuzzing/private/instrum_opts.bzl b/fuzzing/private/instrum_opts.bzl
index 60cea93..1c723bb 100644
--- a/fuzzing/private/instrum_opts.bzl
+++ b/fuzzing/private/instrum_opts.bzl
@@ -91,7 +91,7 @@
     ),
     # Reflects the set of options at
     # https://github.com/google/honggfuzz/blob/master/hfuzz_cc/hfuzz-cc.c
-    honggfuzz = _make_opts(
+    honggfuzz_clang = _make_opts(
         copts = [
             "-mllvm",
             "-inline-threshold=2000",
@@ -105,6 +105,16 @@
             "-fno-sanitize=fuzzer",
         ],
     ),
+    honggfuzz_gcc = _make_opts(
+        copts = [
+            "-finline-limit=1000",
+            "-fsanitize-coverage=trace-pc,trace-cmp",
+            "-fno-builtin",
+            "-fno-omit-frame-pointer",
+            "-D__NO_STRING_INLINES",
+        ],
+        linkopts = [],
+    ),
     asan = _make_opts(
         copts = ["-fsanitize=address"],
         linkopts = ["-fsanitize=address"],
@@ -120,7 +130,7 @@
         ],
         linkopts = ["-fsanitize=memory"],
     ),
-    ubsan = _make_opts(
+    ubsan_clang = _make_opts(
         copts = [
             "-fsanitize=undefined",
         ],
@@ -133,4 +143,12 @@
             "-fsanitize-link-c++-runtime",
         ],
     ),
+    ubsan_gcc = _make_opts(
+        copts = [
+            "-fsanitize=undefined",
+        ],
+        linkopts = [
+            "-fsanitize=undefined",
+        ],
+    ),
 )
diff --git a/fuzzing/repositories.bzl b/fuzzing/repositories.bzl
index 2c5a367..388ded9 100644
--- a/fuzzing/repositories.bzl
+++ b/fuzzing/repositories.bzl
@@ -18,47 +18,49 @@
 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 = True):
+def rules_fuzzing_dependencies(oss_fuzz = True, honggfuzz = True, jazzer = True, bzlmod = False):
     """Instantiates the dependencies of the fuzzing rules.
 
     Args:
       oss_fuzz: Include OSS-Fuzz dependencies.
       honggfuzz: Include Honggfuzz dependencies.
       jazzer: Include Jazzer dependencies.
+      bzlmod: Whether to exclude dependencies that are already provided by bazel_deps.
     """
 
-    maybe(
-        http_archive,
-        name = "platforms",
-        urls = [
-            "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz",
-            "https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz",
-        ],
-        sha256 = "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74",
-    )
-    maybe(
-        http_archive,
-        name = "rules_python",
-        sha256 = "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8",
-        strip_prefix = "rules_python-0.28.0",
-        url = "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz",
-    )
-    maybe(
-        http_archive,
-        name = "bazel_skylib",
-        sha256 = "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94",
-        urls = [
-            "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz",
-            "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz",
-        ],
-    )
-    maybe(
-        http_archive,
-        name = "com_google_absl",
-        urls = ["https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.1.zip"],
-        strip_prefix = "abseil-cpp-20240116.1",
-        integrity = "sha256-7capMWOvWyoYbUaHF/b+I2U6XLMaHmky8KugWvfXYuk=",
-    )
+    if not bzlmod:
+        maybe(
+            http_archive,
+            name = "platforms",
+            urls = [
+                "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz",
+                "https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz",
+            ],
+            sha256 = "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74",
+        )
+        maybe(
+            http_archive,
+            name = "rules_python",
+            sha256 = "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8",
+            strip_prefix = "rules_python-0.28.0",
+            url = "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz",
+        )
+        maybe(
+            http_archive,
+            name = "bazel_skylib",
+            sha256 = "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94",
+            urls = [
+                "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz",
+                "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz",
+            ],
+        )
+        maybe(
+            http_archive,
+            name = "com_google_absl",
+            urls = ["https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.1.zip"],
+            strip_prefix = "abseil-cpp-20240116.1",
+            integrity = "sha256-7capMWOvWyoYbUaHF/b+I2U6XLMaHmky8KugWvfXYuk=",
+        )
 
     if oss_fuzz:
         maybe(
@@ -71,9 +73,9 @@
             http_archive,
             name = "honggfuzz",
             build_file = "@rules_fuzzing//:honggfuzz.BUILD",
-            sha256 = "6b18ba13bc1f36b7b950c72d80f19ea67fbadc0ac0bb297ec89ad91f2eaa423e",
-            url = "https://github.com/google/honggfuzz/archive/2.5.zip",
-            strip_prefix = "honggfuzz-2.5",
+            integrity = "sha256-d+DpDrzBMqmSNCuz0+/Vi3jTUiIFNnulWGAeBJaaHJM=",
+            url = "https://github.com/google/honggfuzz/archive/4cfa62f4fdb56e3027c1cb3aecf04812e786f0fd.zip",
+            strip_prefix = "honggfuzz-4cfa62f4fdb56e3027c1cb3aecf04812e786f0fd",
         )
 
     if jazzer:
diff --git a/honggfuzz.BUILD b/honggfuzz.BUILD
index 583e3f5..1b70a08 100644
--- a/honggfuzz.BUILD
+++ b/honggfuzz.BUILD
@@ -37,20 +37,31 @@
     "-Wall",
     "-Wextra",
     "-Werror",
-    "-Wno-override-init",
-    "-Wno-initializer-overrides",
-    "-Wno-gnu-empty-initializer",
-    "-Wno-format-pedantic",
-    "-Wno-gnu-statement-expression",
-    "-mllvm",
-    "-inline-threshold=2000",
-    "-fblocks",
-
-    # Do not instrument Honggfuzz itself, in order to avoid recursive
-    # instrumentation calls that would crash the fuzz test binary.
-    "-fsanitize-coverage=0",
-    "-fno-sanitize=all",
-]
+] + select({
+    "@rules_cc//cc/compiler:gcc": [
+        "-Wno-override-init",
+        "-Wno-format-truncation",
+        # Do not instrument Honggfuzz itself, in order to avoid recursive
+        # instrumentation calls that would crash the fuzz test binary.
+        "-fno-sanitize-coverage=trace-pc,trace-cmp",
+        "-fno-sanitize=all",
+    ],
+    # Default to clang compiler flags
+    "//conditions:default": [
+        "-mllvm",
+        "-inline-threshold=2000",
+        "-fblocks",
+        "-Wno-override-init",
+        "-Wno-initializer-overrides",
+        "-Wno-gnu-empty-initializer",
+        "-Wno-format-pedantic",
+        "-Wno-gnu-statement-expression",
+        # Do not instrument Honggfuzz itself, in order to avoid recursive
+        # instrumentation calls that would crash the fuzz test binary.
+        "-fsanitize-coverage=0",
+        "-fno-sanitize=all",
+    ],
+})
 
 LIBRARY_COPTS = [
     "-fno-stack-protector",
@@ -116,6 +127,10 @@
         "-Wl,--wrap=Curl_safe_strcasecompare",
         "-Wl,--wrap=Curl_strncasecompare",
         "-Wl,--wrap=curl_strnequal",
+        # SQLite3
+        "-Wl,--wrap=sqlite3_stricmp",
+        "-Wl,--wrap=sqlite3_strnicmp",
+        "-Wl,--wrap=sqlite3StrICmp",
     ],
 })
 
@@ -147,6 +162,10 @@
     deps = [
         ":honggfuzz_common",
     ],
+    target_compatible_with = select({
+        "@platforms//os:linux": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
     alwayslink = 1,
 )