pw_toolchain: Add OSS-Fuzz support

This CL adds an GN arg to set `oss_fuzz_enabled`. When true, the clang
toolchains will use environment variables like CC and CFLAGS passed by
OSS-Fuzz, as documented in
https://google.github.io/oss-fuzz/getting-started/new-project-guide/#buildsh

Additionally, it moves the `pw_sanitizer` from pw_fuzzer to the same arg
scope, as that argument should configure clang independently from
fuzzing.

Finally, it adds some documentation for how to use OSS-Fuzz outputs.

Change-Id: I0ca316d498d9dcd96156cbf19009eb734c3986c1
diff --git a/pw_fuzzer/BUILD.gn b/pw_fuzzer/BUILD.gn
index 5a43021..dec32bd 100644
--- a/pw_fuzzer/BUILD.gn
+++ b/pw_fuzzer/BUILD.gn
@@ -15,12 +15,6 @@
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_fuzzer/fuzzer.gni")
 
-declare_args() {
-  # Sets the sanitizer to pass to clang. Valid values are those for "-fsanitize"
-  # listed in https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html.
-  pw_sanitizer = ""
-}
-
 config("default_config") {
   include_dirs = [ "public" ]
 }
diff --git a/pw_fuzzer/docs.rst b/pw_fuzzer/docs.rst
index 355e2ff..e383566 100644
--- a/pw_fuzzer/docs.rst
+++ b/pw_fuzzer/docs.rst
@@ -64,6 +64,8 @@
   those **only** when fuzzing by using LLVM's
   `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`_
 
+.. _build:
+
 Building fuzzers
 ================
 
@@ -98,8 +100,10 @@
 
 3. Build normally, e.g. using ``pw watch``.
 
-Running fuzzers
-===============
+.. _run:
+
+Running fuzzers locally
+=======================
 
 Based on the example above, the fuzzer output will be at
 ``out/host/obj/my_module/my_fuzzer``. It can be invoked using the normal
@@ -157,9 +161,61 @@
   MS: 1 CrossOver-; base unit: 9f479f7a6e3a21363397a25da3168218ba182a16
   0x68,0x65,0x6c,0x6c,0x6f,0x0,0x77,0x6f,0x72,0x6c,0x64,0x0,0x0,0x0,
   hello\x00world\x00\x00\x00
-  artifact_prefix='artifacts'; Test unit written to artifactscrash-6e4fdc7ffd04131ea15dd243a0890b1b606f4831
+  artifact_prefix='artifacts'; Test unit written to artifacts/crash-6e4fdc7ffd04131ea15dd243a0890b1b606f4831
   Base64: aGVsbG8Ad29ybGQAAAA=
 
+Running fuzzers on OSS-Fuzz
+===========================
+
+Pigweed is integrated with `OSS-Fuzz`_, a continuous fuzzing infrastructure for
+open source software. Fuzzers listed in in ``pw_test_groups`` will automatically
+start being run within a day or so of appearing in the git repository.
+
+Bugs produced by OSS-Fuzz can be found in its `Monorail instance`_. These bugs
+include:
+
+* A detailed report, including a symbolized backtrace.
+* A revision range indicating when the bug has been detected.
+* A minimized testcase, which is a fuzzer input that can be used to reproduce
+  the bug.
+
+To reproduce a bug:
+
+#. Build_ the fuzzers as described above.
+#. Download the minimized testcase.
+#. Run_ the fuzzer with the testcase as an argument.
+
+For example, if the testcase is saved as "~/Downloads/testcase"
+and the fuzzer is the same as in the examples above, you could run:
+
+.. code::
+
+  $ ./out/host/obj/pw_fuzzer/toy_fuzzer ~/Downloads/testcase
+
+If you need to recreate the OSS-Fuzz environment locally, you can use its
+documentation on `reproducing`_ issues.
+
+In particular, you can recreate the OSS-Fuzz environment using:
+
+.. code::
+
+  $ python infra/helper.py pull_images
+  $ python infra/helper.py build_image pigweed
+  $ python infra/helper.py build_fuzzers --sanitizer <address/undefined> pigweed
+
+With that environment, you can run the reproduce bugs using:
+
+.. code::
+
+  python infra/helper.py reproduce pigweed <pw_module>_<fuzzer_name> ~/Downloads/testcase
+
+You can even verify fixes in your local source checkout:
+
+.. code::
+
+  $ python infra/helper.py build_fuzzers --sanitizer <address/undefined> pigweed $PW_ROOT
+  $ python infra/helper.py reproduce pigweed <pw_module>_<fuzzer_name> ~/Downloads/testcase
+
 .. _compiler_rt: https://compiler-rt.llvm.org/
 .. _corpus: https://llvm.org/docs/LibFuzzer.html#corpus
 .. _FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION: https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode
@@ -167,6 +223,9 @@
 .. _libFuzzer: https://llvm.org/docs/LibFuzzer.html
 .. _libFuzzer options: https://llvm.org/docs/LibFuzzer.html#options
 .. _LLVMFuzzerTestOneInput: https://llvm.org/docs/LibFuzzer.html#fuzz-target
+.. _monorail instance: https://bugs.chromium.org/p/oss-fuzz
+.. _oss-fuzz: https://github.com/google/oss-fuzz
+.. _reproducing: https://google.github.io/oss-fuzz/advanced-topics/reproducing/
 .. _running a fuzzer: https://llvm.org/docs/LibFuzzer.html#running
 .. _sanitizer runtime flags: https://github.com/google/sanitizers/wiki/SanitizerCommonFlags
 .. _split a fuzzing input: https://github.com/google/fuzzing/blob/master/docs/split-inputs.md
diff --git a/pw_fuzzer/fuzzer.gni b/pw_fuzzer/fuzzer.gni
index a123e0d..73461da 100644
--- a/pw_fuzzer/fuzzer.gni
+++ b/pw_fuzzer/fuzzer.gni
@@ -12,6 +12,7 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+import("$dir_pw_toolchain/host_clang.gni")
 import("$dir_pw_unit_test/test.gni")
 
 # Creates a libFuzzer-based fuzzer executable target.
diff --git a/pw_toolchain/host_clang.gni b/pw_toolchain/host_clang.gni
index f4d2803..a842622 100644
--- a/pw_toolchain/host_clang.gni
+++ b/pw_toolchain/host_clang.gni
@@ -12,6 +12,17 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+declare_args() {
+  # Sets the sanitizer to pass to clang. Valid values are those for "-fsanitize"
+  # listed in https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html.
+  pw_sanitizer = ""
+
+  # Indicates if this build is a part of OSS-Fuzz, which needs to be able to
+  # provide its own compiler and flags. This violates the build hermeticisim and
+  # should only be used for OSS-Fuzz.
+  oss_fuzz_enabled = false
+}
+
 # Generates a host clang toolchain for a specific target.
 #
 # Args:
@@ -40,13 +51,17 @@
 
   _toolchain_cflags = string_join(" ", _cflags_list)
 
-  # Specify the default C++ version, which targets can override with a config.
-  _toolchain_cflags_cc = "-std=c++17 -Wno-register"
+  # Toolchain C++ flags
+  _cflags_cc_list = [
+    # Specify the default C++ version, which targets can override with a config.
+    "-std=c++17",
+    "-Wno-register",
+  ]
 
   # Toolchain LD flags
-  _toolchain_ldflags = ""
+  _ldflags_list = []
   if (defined(invoker.toolchain_ldflags)) {
-    _toolchain_ldflags = string_join(" ", invoker.toolchain_ldflags)
+    _ldflags_list += invoker.toolchain_ldflags
   }
   if (host_os == "mac") {
     # The CIPD provided Clang/LLVM toolchain must link against the matched
@@ -58,13 +73,13 @@
     assert(getenv("PW_PIGWEED_CIPD_INSTALL_DIR") != "",
            "You forgot to activate the Pigweed environment; " +
                "did you source pw_env_setup/setup.sh?")
+    _ldflags_list += [
+      # Force dropping the system libc++
+      "-nostdlib++",
 
-    # Force dropping the system libc++
-    _toolchain_ldflags += "-nostdlib++ "
-
-    # Use the libc++ from CIPD.
-    _toolchain_ldflags += getenv("PW_PIGWEED_CIPD_INSTALL_DIR")
-    _toolchain_ldflags += "/lib/libc++.a"
+      # Use the libc++ from CIPD.
+      getenv("PW_PIGWEED_CIPD_INSTALL_DIR") + "/lib/libc++.a",
+    ]
   }
 
   # Note: On macOS, there is no "llvm-ar", only "ar", which happens to be LLVM
@@ -73,6 +88,19 @@
   _cc = "clang"
   _cxx = "clang++"
 
+  # OSS-Fuzz needs to be able to specify its own compilers and add flags.
+  if (oss_fuzz_enabled) {
+    _cc = getenv("CC")
+    _cxx = getenv("CXX")
+    _cflags_list += string_split(getenv("CFLAGS"))
+    _cflags_cc_list += string_split(getenv("CXXFLAGS"))
+    _ldflags_list += string_split(getenv("LDFLAGS"))
+  }
+
+  _toolchain_cflags = string_join(" ", _cflags_list)
+  _toolchain_cflags_cc = string_join(" ", _cflags_cc_list)
+  _toolchain_ldflags = string_join(" ", _ldflags_list)
+
   toolchain(target_name) {
     tool("asm") {
       depfile = "{{output}}.d"