OSS-Fuzz integration

This commit integrates OSS-Fuzz targets directly into the libjpeg-turbo
source tree, thus obsoleting and improving code coverage relative to
Google's OSS-Fuzz target for libjpeg-turbo (previously available here:
https://github.com/google/oss-fuzz).

I hope to eventually create fuzz targets for the BMP, GIF, and PPM
readers as well, which would allow for fuzz-testing compression, but
since those readers all require an input file, it is unclear how to
build an efficient fuzzer around them.  It doesn't make sense to
fuzz-test compression in isolation, because compression can't accept
arbitrary input data.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 962ecd7..f34d0e1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -192,6 +192,7 @@
 boolean_number(WITH_SIMD)
 option(WITH_TURBOJPEG "Include the TurboJPEG API library and associated test programs" TRUE)
 boolean_number(WITH_TURBOJPEG)
+option(WITH_FUZZ "Build fuzz targets" FALSE)
 
 macro(report_option var desc)
   if(${var})
@@ -704,6 +705,10 @@
 # TESTS
 ###############################################################################
 
+if(WITH_FUZZ)
+  add_subdirectory(fuzz)
+endif()
+
 add_subdirectory(md5)
 
 if(MSVC_IDE OR XCODE)
diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt
new file mode 100644
index 0000000..6af371b
--- /dev/null
+++ b/fuzz/CMakeLists.txt
@@ -0,0 +1,36 @@
+if(NOT WITH_TURBOJPEG)
+  message(FATAL_ERROR "Fuzz targets require the TurboJPEG API library.")
+endif()
+
+set(FUZZ_BINDIR "" CACHE PATH
+  "Directory into which fuzz targets should be installed")
+if(NOT FUZZ_BINDIR)
+  message(FATAL_ERROR "FUZZ_BINDIR must be specified.")
+endif()
+message(STATUS "FUZZ_BINDIR = ${FUZZ_BINDIR}")
+
+set(FUZZ_LIBRARY "" CACHE STRING
+  "Path to fuzzer library or flags necessary to link with it")
+if(NOT FUZZ_LIBRARY)
+  message(FATAL_ERROR "FUZZ_LIBRARY must be specified.")
+endif()
+message(STATUS "FUZZ_LIBRARY = ${FUZZ_LIBRARY}")
+
+macro(add_fuzz_target target source_file)
+  add_executable(${target}_fuzzer ${source_file})
+  if(NOT ENABLE_SHARED)
+    target_link_libraries(${target}_fuzzer ${FUZZ_LIBRARY} turbojpeg-static)
+  else()
+    target_link_libraries(${target}_fuzzer ${FUZZ_LIBRARY} turbojpeg)
+  endif()
+  install(TARGETS ${target}_fuzzer RUNTIME DESTINATION ${FUZZ_BINDIR})
+endmacro()
+
+# NOTE: This target is named libjpeg_turbo_fuzzer instead of decompress_fuzzer
+# in order to preserve the corpora from Google's OSS-Fuzz target for
+# libjpeg-turbo, which this target replaces.
+add_fuzz_target(libjpeg_turbo decompress.c)
+
+add_fuzz_target(decompress_yuv decompress_yuv.c)
+
+add_fuzz_target(transform transform.c)
diff --git a/fuzz/build.sh b/fuzz/build.sh
new file mode 100644
index 0000000..cf8597b
--- /dev/null
+++ b/fuzz/build.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -u
+set -e
+
+cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_STATIC=1 -DENABLE_SHARED=0 \
+	-DCMAKE_C_FLAGS_RELWITHDEBINFO="-g -DNDEBUG" -DCMAKE_INSTALL_PREFIX=$WORK \
+	-DWITH_FUZZ=1 -DFUZZ_BINDIR=$OUT -DFUZZ_LIBRARY=$LIB_FUZZING_ENGINE
+make "-j$(nproc)" "--load-average=$(nproc)"
+make install
+
+cp $SRC/decompress_fuzzer_seed_corpus.zip $OUT/libjpeg_turbo_fuzzer_seed_corpus.zip
+cp $SRC/decompress_fuzzer_seed_corpus.zip $OUT/decompress_yuv_fuzzer_seed_corpus.zip
+cp $SRC/decompress_fuzzer_seed_corpus.zip $OUT/transform_fuzzer_seed_corpus.zip
diff --git a/fuzz/decompress.c b/fuzz/decompress.c
new file mode 100644
index 0000000..a84cdac
--- /dev/null
+++ b/fuzz/decompress.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C)2021 D. R. Commander.  All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the libjpeg-turbo Project nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <turbojpeg.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+
+#define NUMPF  4
+
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+  tjhandle handle = NULL;
+  unsigned char *dstBuf = NULL;
+  int width, height, jpegSubsamp, jpegColorspace, pfi;
+  /* TJPF_RGB-TJPF_BGR share the same code paths, as do TJPF_RGBX-TJPF_XRGB and
+     TJPF_RGBA-TJPF_ARGB.  Thus, the pixel formats below should be the minimum
+     necessary to achieve full coverage. */
+  enum TJPF pixelFormats[NUMPF] =
+    { TJPF_RGB, TJPF_BGRX, TJPF_GRAY, TJPF_CMYK };
+
+  if ((handle = tjInitDecompress()) == NULL)
+    goto bailout;
+
+  /* We ignore the return value of tjDecompressHeader3(), because some JPEG
+     images may have unusual subsampling configurations that the TurboJPEG API
+     cannot identify but can still decompress. */
+  tjDecompressHeader3(handle, data, size, &width, &height, &jpegSubsamp,
+                      &jpegColorspace);
+
+  /* Ignore 0-pixel images and images larger than 1 Megapixel, as Google's
+     OSS-Fuzz target for libjpeg-turbo did.  Casting width to (uint64_t)
+     prevents integer overflow if width * height > INT_MAX. */
+  if (width < 1 || height < 1 || (uint64_t)width * height > 1048576)
+    goto bailout;
+
+  for (pfi = 0; pfi < NUMPF; pfi++) {
+    int pf = pixelFormats[pfi], flags = 0, i, sum = 0, w = width, h = height;
+
+    /* Test non-default decompression options on the first iteration. */
+    if (pf == TJPF_RGB)
+      flags = TJFLAG_BOTTOMUP | TJFLAG_FASTUPSAMPLE | TJFLAG_FASTDCT;
+    /* Test IDCT scaling on the second iteration. */
+    if (pf == TJPF_BGRX) {
+      w = (width + 1) / 2;
+      h = (height + 1) / 2;
+    }
+
+    if ((dstBuf = (unsigned char *)malloc(w * h * tjPixelSize[pf])) == NULL)
+      goto bailout;
+
+    tjDecompress2(handle, data, size, dstBuf, w, 0, h, pf, flags);
+
+    /* Touch all of the output pixels in order to catch uninitialized reads
+       when using MemorySanitizer. */
+    for (i = 0; i < w * h * tjPixelSize[pf]; i++)
+      sum += dstBuf[i];
+
+    free(dstBuf);
+    dstBuf = NULL;
+
+    /* Prevent the code above from being optimized out.  This test should never
+       be true, but the compiler doesn't know that. */
+    if (sum > 255 * 1048576 * tjPixelSize[pf])
+      goto bailout;
+  }
+
+bailout:
+  free(dstBuf);
+  if (handle) tjDestroy(handle);
+  return 0;
+}
diff --git a/fuzz/decompress_yuv.c b/fuzz/decompress_yuv.c
new file mode 100644
index 0000000..3fc1b89
--- /dev/null
+++ b/fuzz/decompress_yuv.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C)2021 D. R. Commander.  All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the libjpeg-turbo Project nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <turbojpeg.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+
+#define NUMPF  3
+
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+  tjhandle handle = NULL;
+  unsigned char *dstBuf = NULL, *yuvBuf = NULL;
+  int width, height, jpegSubsamp, jpegColorspace, pfi;
+  /* TJPF_RGB-TJPF_BGR share the same code paths, as do TJPF_RGBX-TJPF_XRGB and
+     TJPF_RGBA-TJPF_ARGB.  Thus, the pixel formats below should be the minimum
+     necessary to achieve full coverage. */
+  enum TJPF pixelFormats[NUMPF] =
+    { TJPF_BGR, TJPF_XRGB, TJPF_GRAY };
+
+  if ((handle = tjInitDecompress()) == NULL)
+    goto bailout;
+
+  if (tjDecompressHeader3(handle, data, size, &width, &height, &jpegSubsamp,
+                          &jpegColorspace) < 0)
+    goto bailout;
+
+  /* Ignore 0-pixel images and images larger than 1 Megapixel.  Casting width
+     to (uint64_t) prevents integer overflow if width * height > INT_MAX. */
+  if (width < 1 || height < 1 || (uint64_t)width * height > 1048576)
+    goto bailout;
+
+  for (pfi = 0; pfi < NUMPF; pfi++) {
+    int pf = pixelFormats[pfi], flags = 0, i, sum = 0, w = width, h = height;
+
+    /* Test non-default decompression options on the first iteration. */
+    if (pf == TJPF_RGB)
+      flags = TJFLAG_BOTTOMUP | TJFLAG_FASTUPSAMPLE | TJFLAG_FASTDCT;
+    /* Test IDCT scaling on the second iteration. */
+    if (pf == TJPF_XRGB) {
+      w = (width + 3) / 4;
+      h = (height + 3) / 4;
+    }
+
+    if ((dstBuf = (unsigned char *)malloc(w * h * tjPixelSize[pf])) == NULL)
+      goto bailout;
+    if ((yuvBuf =
+         (unsigned char *)malloc(tjBufSizeYUV2(w, 1, h, jpegSubsamp))) == NULL)
+      goto bailout;
+
+    tjDecompressToYUV2(handle, data, size, yuvBuf, w, 1, h, flags);
+    tjDecodeYUV(handle, yuvBuf, 1, jpegSubsamp, dstBuf, w, 0, h, pf, flags);
+
+    /* Touch all of the output pixels in order to catch uninitialized reads
+       when using MemorySanitizer. */
+    for (i = 0; i < w * h * tjPixelSize[pf]; i++)
+      sum += dstBuf[i];
+
+    free(dstBuf);
+    dstBuf = NULL;
+    free(yuvBuf);
+    yuvBuf = NULL;
+
+    /* Prevent the code above from being optimized out.  This test should never
+       be true, but the compiler doesn't know that. */
+    if (sum > 255 * 1048576 * tjPixelSize[pf])
+      goto bailout;
+  }
+
+bailout:
+  free(dstBuf);
+  free(yuvBuf);
+  if (handle) tjDestroy(handle);
+  return 0;
+}
diff --git a/fuzz/transform.c b/fuzz/transform.c
new file mode 100644
index 0000000..aa4673f
--- /dev/null
+++ b/fuzz/transform.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C)2021 D. R. Commander.  All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the libjpeg-turbo Project nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <turbojpeg.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+
+#define NUMXFORMS  3
+
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+  tjhandle handle = NULL;
+  unsigned char *dstBufs[NUMXFORMS] = { NULL, NULL, NULL };
+  unsigned long dstSizes[NUMXFORMS] = { 0, 0, 0 }, maxBufSize;
+  int width, height, jpegSubsamp, jpegColorspace, i, t;
+  tjtransform transforms[NUMXFORMS];
+
+  if ((handle = tjInitTransform()) == NULL)
+    goto bailout;
+
+  /* We ignore the return value of tjDecompressHeader3(), because some JPEG
+     images may have unusual subsampling configurations that the TurboJPEG API
+     cannot identify but can still transform. */
+  tjDecompressHeader3(handle, data, size, &width, &height, &jpegSubsamp,
+                      &jpegColorspace);
+
+  /* Ignore 0-pixel images and images larger than 1 Megapixel.  Casting width
+     to (uint64_t) prevents integer overflow if width * height > INT_MAX. */
+  if (width < 1 || height < 1 || (uint64_t)width * height > 1048576)
+    goto bailout;
+
+  if (jpegSubsamp < 0 || jpegSubsamp >= TJ_NUMSAMP)
+    jpegSubsamp = TJSAMP_444;
+
+  for (t = 0; t < NUMXFORMS; t++)
+    memset(&transforms[t], 0, sizeof(tjtransform));
+
+  transforms[0].op = TJXOP_NONE;
+  transforms[0].options = TJXOPT_PROGRESSIVE | TJXOPT_COPYNONE;
+  dstBufs[0] = (unsigned char *)malloc(tjBufSize(width, height, jpegSubsamp));
+  if (!dstBufs[0])
+    goto bailout;
+
+  transforms[1].r.w = (width + 1) / 2;
+  transforms[1].r.h = (height + 1) / 2;
+  transforms[1].op = TJXOP_TRANSPOSE;
+  transforms[1].options = TJXOPT_GRAY | TJXOPT_CROP | TJXOPT_COPYNONE;
+  dstBufs[1] =
+    (unsigned char *)malloc(tjBufSize((width + 1) / 2, (height + 1) / 2,
+                                      jpegSubsamp));
+  if (!dstBufs[1])
+    goto bailout;
+
+  transforms[2].op = TJXOP_ROT90;
+  transforms[2].options = TJXOPT_TRIM | TJXOPT_COPYNONE;
+  dstBufs[2] = (unsigned char *)malloc(tjBufSize(height, width, jpegSubsamp));
+  if (!dstBufs[2])
+    goto bailout;
+
+  tjTransform(handle, data, size, NUMXFORMS, dstBufs, dstSizes, transforms,
+              TJFLAG_NOREALLOC);
+
+  maxBufSize = tjBufSize(width, height, jpegSubsamp);
+
+  /* Touch all of the output pixels in order to catch uninitialized reads
+     when using MemorySanitizer. */
+  for (t = 0; t < NUMXFORMS; t++) {
+    int sum = 0;
+
+    for (i = 0; i < dstSizes[t]; i++)
+      sum += dstBufs[t][i];
+
+    /* Prevent the code above from being optimized out.  This test should never
+       be true, but the compiler doesn't know that. */
+    if (sum > 255 * maxBufSize)
+      goto bailout;
+  }
+
+bailout:
+  for (t = 0; t < NUMXFORMS; t++)
+    free(dstBufs[t]);
+  if (handle) tjDestroy(handle);
+  return 0;
+}