Add regex fuzzer

A fuzzer that mutates a WGSL code by finding two regions enclosed by
semicolons and swapping them randomly.

Change-Id: I5b14eb21fd2924227d05ac516f806c6e2efa6198
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/58395
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Alastair Donaldson <afdx@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Alastair Donaldson <afdx@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ca7501d..308f1e7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,6 +52,7 @@
 option(TINT_BUILD_FUZZERS "Build fuzzers" OFF)
 option(TINT_BUILD_SPIRV_TOOLS_FUZZER "Build SPIRV-Tools fuzzer" OFF)
 option(TINT_BUILD_AST_FUZZER "Build AST fuzzer" OFF)
+option(TINT_BUILD_REGEX_FUZZER "Build regex fuzzer" OFF)
 option(TINT_BUILD_TESTS "Build tests" ${TINT_BUILD_TESTS_DEFAULT})
 option(TINT_BUILD_AS_OTHER_OS "Override OS detection to force building of *_other.cc files" OFF)
 option(TINT_BUILD_REMOTE_COMPILE "Build the remote-compile tool for validating shaders on a remote machine" OFF)
@@ -75,6 +76,7 @@
 message(STATUS "Tint build fuzzers: ${TINT_BUILD_FUZZERS}")
 message(STATUS "Tint build SPIRV-Tools fuzzer: ${TINT_BUILD_SPIRV_TOOLS_FUZZER}")
 message(STATUS "Tint build AST fuzzer: ${TINT_BUILD_AST_FUZZER}")
+message(STATUS "Tint build regex fuzzer: ${TINT_BUILD_REGEX_FUZZER}")
 message(STATUS "Tint build tests: ${TINT_BUILD_TESTS}")
 message(STATUS "Tint build with ASAN: ${TINT_ENABLE_ASAN}")
 message(STATUS "Tint build with MSAN: ${TINT_ENABLE_MSAN}")
@@ -119,6 +121,22 @@
   set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "Build HLSL writer" FORCE)
 endif()
 
+if (${TINT_BUILD_REGEX_FUZZER})
+  message(STATUS "TINT_BUILD_REGEX_FUZZER is ON - setting
+      TINT_BUILD_FUZZERS
+      TINT_BUILD_WGSL_READER
+      TINT_BUILD_WGSL_WRITER
+      TINT_BUILD_SPV_WRITER
+      TINT_BUILD_MSL_WRITER
+      TINT_BUILD_HLSL_WRITER to ON")
+      set(TINT_BUILD_FUZZERS ON CACHE BOOL "Build tint fuzzers" FORCE)
+      set(TINT_BUILD_WGSL_READER ON CACHE BOOL "Build WGSL reader" FORCE)
+      set(TINT_BUILD_WGSL_WRITER ON CACHE BOOL "Build WGSL writer" FORCE)
+      set(TINT_BUILD_SPV_WRITER ON CACHE BOOL "Build SPIR-V writer" FORCE)
+      set(TINT_BUILD_MSL_WRITER ON CACHE BOOL "Build MSL writer" FORCE)
+      set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "Build HLSL writer" FORCE)
+endif()
+
 set(TINT_ROOT_SOURCE_DIR ${PROJECT_SOURCE_DIR})
 
 # CMake < 3.15 sets /W3 in CMAKE_CXX_FLAGS. Remove it if it's there.
diff --git a/fuzzers/CMakeLists.txt b/fuzzers/CMakeLists.txt
index 8847be2..a497f72 100644
--- a/fuzzers/CMakeLists.txt
+++ b/fuzzers/CMakeLists.txt
@@ -81,3 +81,7 @@
 if (${TINT_BUILD_AST_FUZZER})
   add_subdirectory(tint_ast_fuzzer)
 endif()
+
+if (${TINT_BUILD_REGEX_FUZZER})
+  add_subdirectory(tint_regex_fuzzer)
+endif()
diff --git a/fuzzers/tint_regex_fuzzer/CMakeLists.txt b/fuzzers/tint_regex_fuzzer/CMakeLists.txt
new file mode 100644
index 0000000..4b981c0
--- /dev/null
+++ b/fuzzers/tint_regex_fuzzer/CMakeLists.txt
@@ -0,0 +1,57 @@
+# Copyright 2021 The Tint Authors.
+#
+# 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.
+
+set(LIBTINT_REGEX_FUZZER_SOURCES
+        wgsl_mutator.cc
+        wgsl_mutator.h)
+
+# Add static library target.
+add_library(libtint_regex_fuzzer STATIC ${LIBTINT_REGEX_FUZZER_SOURCES})
+tint_default_compile_options(libtint_regex_fuzzer)
+
+set(REGEX_FUZZER_SOURCES
+        cli.cc
+        cli.h
+        fuzzer.cc
+        ../tint_common_fuzzer.cc
+        ../tint_common_fuzzer.h)
+
+# Add libfuzzer target.
+add_executable(tint_regex_fuzzer ${REGEX_FUZZER_SOURCES})
+target_compile_options(tint_regex_fuzzer PRIVATE
+        -Wno-missing-prototypes)
+target_link_libraries(tint_regex_fuzzer libtint-fuzz libtint_regex_fuzzer)
+tint_default_compile_options(tint_regex_fuzzer)
+target_compile_definitions(tint_regex_fuzzer PUBLIC CUSTOM_MUTATOR)
+
+# Add tests.
+if (${TINT_BUILD_TESTS})
+    set(TEST_SOURCES
+            regex_fuzzer_tests.cc)
+
+    add_executable(tint_regex_fuzzer_unittests ${TEST_SOURCES})
+
+    target_include_directories(
+            tint_regex_fuzzer_unittests PRIVATE ${gmock_SOURCE_DIR}/include)
+    target_link_libraries(tint_regex_fuzzer_unittests gmock_main libtint_regex_fuzzer)
+    tint_default_compile_options(tint_regex_fuzzer_unittests)
+    target_compile_options(tint_regex_fuzzer_unittests PRIVATE
+            -Wno-global-constructors
+            -Wno-weak-vtables
+            -Wno-covered-switch-default)
+
+    target_include_directories(tint_regex_fuzzer_unittests PRIVATE ${CMAKE_BINARY_DIR})
+
+    add_test(NAME tint_regex_fuzzer_unittests COMMAND tint_regex_fuzzer_unittests)
+endif ()
diff --git a/fuzzers/tint_regex_fuzzer/cli.cc b/fuzzers/tint_regex_fuzzer/cli.cc
new file mode 100644
index 0000000..3e96689
--- /dev/null
+++ b/fuzzers/tint_regex_fuzzer/cli.cc
@@ -0,0 +1,126 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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.
+
+#include "fuzzers/tint_regex_fuzzer/cli.h"
+
+#include <cstring>
+#include <iostream>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <utility>
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+namespace {
+
+const char* const kHelpMessage = R"(
+This is a fuzzer for the Tint compiler that works by mutating a WGSL shader.
+
+Below is a list of all supported parameters for this fuzzer. You may want to
+run it with -help=1 to check out libfuzzer parameters.
+
+  -tint_fuzzing_target=
+                       Specifies the shading language to target during fuzzing.
+                       This must be one or a combination of `wgsl`, `spv`, `hlsl`,
+                       `msl` (without `) separated by commas. By default it's
+                       `wgsl,msl,hlsl,spv`.
+
+  -tint_help
+                       Show this message. Note that there is also a -help=1
+                       parameter that will display libfuzzer's help message.
+)";
+
+bool HasPrefix(const char* str, const char* prefix) {
+  return strncmp(str, prefix, strlen(prefix)) == 0;
+}
+
+[[noreturn]] void InvalidParam(const char* param) {
+  std::cout << "Invalid value for " << param << std::endl;
+  std::cout << kHelpMessage << std::endl;
+  exit(1);
+}
+
+bool ParseFuzzingTarget(const char* value, FuzzingTarget* out) {
+  if (!strcmp(value, "wgsl")) {
+    *out = FuzzingTarget::kWgsl;
+  } else if (!strcmp(value, "spv")) {
+    *out = FuzzingTarget::kSpv;
+  } else if (!strcmp(value, "msl")) {
+    *out = FuzzingTarget::kMsl;
+  } else if (!strcmp(value, "hlsl")) {
+    *out = FuzzingTarget::kHlsl;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+CliParams ParseCliParams(int* argc, char** argv) {
+  CliParams cli_params;
+  auto help = false;
+
+  for (int i = *argc - 1; i > 0; --i) {
+    auto param = argv[i];
+    auto recognized_parameter = true;
+
+    if (HasPrefix(param, "-tint_fuzzing_target=")) {
+      auto result = FuzzingTarget::kNone;
+
+      std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1);
+      for (std::string value; std::getline(ss, value, ',');) {
+        auto tmp = FuzzingTarget::kNone;
+        if (!ParseFuzzingTarget(value.c_str(), &tmp)) {
+          InvalidParam(param);
+        }
+        result = result | tmp;
+      }
+
+      if (result == FuzzingTarget::kNone) {
+        InvalidParam(param);
+      }
+
+      cli_params.fuzzing_target = result;
+    } else if (!strcmp(param, "-tint_help")) {
+      help = true;
+    } else {
+      recognized_parameter = false;
+    }
+
+    if (recognized_parameter) {
+      // Remove the recognized parameter from the list of all parameters by
+      // swapping it with the last one. This will suppress warnings in the
+      // libFuzzer about unrecognized parameters. By default, libFuzzer thinks
+      // that all user-defined parameters start with two dashes. However, we are
+      // forced to use a single one to make the fuzzer compatible with the
+      // ClusterFuzz.
+      std::swap(argv[i], argv[*argc - 1]);
+      *argc -= 1;
+    }
+  }
+
+  if (help) {
+    std::cout << kHelpMessage << std::endl;
+    exit(0);
+  }
+
+  return cli_params;
+}
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/cli.h b/fuzzers/tint_regex_fuzzer/cli.h
new file mode 100644
index 0000000..9bad9d2
--- /dev/null
+++ b/fuzzers/tint_regex_fuzzer/cli.h
@@ -0,0 +1,64 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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.
+
+#ifndef FUZZERS_TINT_REGEX_FUZZER_CLI_H_
+#define FUZZERS_TINT_REGEX_FUZZER_CLI_H_
+
+#include <cstdint>
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+/// The backend this fuzzer will test.
+enum class FuzzingTarget {
+  kNone = 0,
+  kHlsl = 1 << 0,
+  kMsl = 1 << 1,
+  kSpv = 1 << 2,
+  kWgsl = 1 << 3,
+  kAll = kHlsl | kMsl | kSpv | kWgsl
+};
+
+inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) {
+  return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b));
+}
+
+inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) {
+  return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b));
+}
+
+/// CLI parameters accepted by the fuzzer. Type -tint_help in the CLI to see the
+/// help message
+struct CliParams {
+  /// Compiler backends we want to fuzz.
+  FuzzingTarget fuzzing_target = FuzzingTarget::kAll;
+};
+
+/// @brief Parses CLI parameters.
+///
+/// This function will exit the process with non-zero return code if some
+/// parameters are invalid. This function will remove recognized parameters from
+/// `argv` and adjust `argc` accordingly.
+///
+/// @param argc - the total number of parameters.
+/// @param argv - array of all CLI parameters.
+/// @return parsed parameters.
+CliParams ParseCliParams(int* argc, char** argv);
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // FUZZERS_TINT_REGEX_FUZZER_CLI_H_
diff --git a/fuzzers/tint_regex_fuzzer/fuzzer.cc b/fuzzers/tint_regex_fuzzer/fuzzer.cc
new file mode 100644
index 0000000..fabf90c
--- /dev/null
+++ b/fuzzers/tint_regex_fuzzer/fuzzer.cc
@@ -0,0 +1,85 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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.
+
+#include <cstddef>
+#include <cstdint>
+
+#include "fuzzers/tint_common_fuzzer.h"
+#include "fuzzers/tint_regex_fuzzer/cli.h"
+
+#include "fuzzers/tint_regex_fuzzer/wgsl_mutator.h"
+
+#include "src/reader/wgsl/parser.h"
+#include "src/writer/wgsl/generator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+namespace {
+
+CliParams cli_params{};
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
+  // Parse CLI parameters. `ParseCliParams` will call `exit` if some parameter
+  // is invalid.
+  cli_params = ParseCliParams(argc, *argv);
+  return 0;
+}
+
+extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
+                                          size_t size,
+                                          size_t max_size,
+                                          unsigned seed) {
+  const std::vector<std::string> delimiters{";"};
+  std::mt19937 generator(seed);
+  std::uniform_int_distribution<size_t> distribution(0, delimiters.size() - 1);
+  size_t ind = distribution(generator);
+
+  return FuzzEnclosedRegions(size, max_size, delimiters[ind], data, &generator);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size == 0) {
+    return 0;
+  }
+
+  struct Target {
+    FuzzingTarget fuzzing_target;
+    OutputFormat output_format;
+    const char* name;
+  };
+
+  Target targets[] = {{FuzzingTarget::kWgsl, OutputFormat::kWGSL, "WGSL"},
+                      {FuzzingTarget::kHlsl, OutputFormat::kHLSL, "HLSL"},
+                      {FuzzingTarget::kMsl, OutputFormat::kMSL, "MSL"},
+                      {FuzzingTarget::kSpv, OutputFormat::kSpv, "SPV"}};
+
+  for (auto target : targets) {
+    if ((target.fuzzing_target & cli_params.fuzzing_target) !=
+        target.fuzzing_target) {
+      continue;
+    }
+
+    CommonFuzzer fuzzer(InputFormat::kWGSL, target.output_format);
+    fuzzer.EnableInspector();
+    fuzzer.Run(data, size);
+  }
+
+  return 0;
+}
+
+}  // namespace
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc b/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc
new file mode 100644
index 0000000..8ed2eb4
--- /dev/null
+++ b/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc
@@ -0,0 +1,91 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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.
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "fuzzers/tint_regex_fuzzer/wgsl_mutator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+namespace {
+
+// Swaps two non-consecutive regions in the edge
+TEST(SwapRegionsTest, SwapIntervalsEdgeNonConsecutive) {
+  std::string R1 = "|region1|", R2 = "; region2;",
+              R3 = "---------region3---------";
+  std::string all_regions = R1 + R2 + R3;
+
+  // this call should swap R1 with R3.
+  SwapIntervals(0, R1.length() - 1, R1.length() + R2.length(),
+                all_regions.length() - 1, &all_regions);
+
+  ASSERT_EQ(R3 + R2 + R1, all_regions);
+}
+
+// Swaps two non-consecutive regions not in the edge
+TEST(SwapRegionsTest, SwapIntervalsNonConsecutiveNonEdge) {
+  std::string R1 = "|region1|", R2 = "; region2;",
+              R3 = "---------region3---------", R4 = "++region4++",
+              R5 = "***region5***";
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // this call should swap R2 with R4.
+  SwapIntervals(R1.length(), R1.length() + R2.length() - 1,
+                R1.length() + R2.length() + R3.length(),
+                R1.length() + R2.length() + R3.length() + R4.length() - 1,
+                &all_regions);
+
+  ASSERT_EQ(R1 + R4 + R3 + R2 + R5, all_regions);
+}
+
+// Swaps two consecutive regions not in the edge (sorrounded by other regions)
+TEST(SwapRegionsTest, SwapIntervalsConsecutiveEdge) {
+  std::string R1 = "|region1|", R2 = "; region2;", R3 = "++++region3++++",
+              R4 = "---------region4---------";
+  std::string all_regions = R1 + R2 + R3 + R4;
+
+  // this call should swap R2 with R3.
+  SwapIntervals(R1.length(), R1.length() + R2.length() - 1,
+                R1.length() + R2.length(),
+                R1.length() + R2.length() + R3.length() - 1, &all_regions);
+
+  ASSERT_EQ(R1 + R3 + R2 + R4, all_regions);
+}
+
+// Swaps two consecutive regions not in the edge (not sorrounded by other
+// regions)
+TEST(SwapRegionsTest, SwapIntervalsConsecutiveNonEdge) {
+  std::string R1 = "|region1|", R2 = "; region2;",
+              R3 = "---------region3---------", R4 = "++region4++",
+              R5 = "***region5***";
+  std::string all_regions = R1 + R2 + R3 + R4 + R5;
+
+  // this call should swap R4 with R5.
+  SwapIntervals(
+      R1.length() + R2.length() + R3.length(),
+      R1.length() + R2.length() + R3.length() + R4.length() - 1,
+      R1.length() + R2.length() + R3.length() + R4.length(),
+      R1.length() + R2.length() + R3.length() + R4.length() + R5.length() - 1,
+      &all_regions);
+
+  ASSERT_EQ(R1 + R2 + R3 + R5 + R4, all_regions);
+}
+
+}  // namespace
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc b/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc
new file mode 100644
index 0000000..ca199fa
--- /dev/null
+++ b/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc
@@ -0,0 +1,107 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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.
+
+#include "fuzzers/tint_regex_fuzzer/wgsl_mutator.h"
+
+#include <cassert>
+#include <cstring>
+#include <iostream>
+#include <map>
+#include <random>
+#include <regex>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+namespace {
+
+size_t GetRandomIntFromRange(size_t lower_bound,
+                             size_t upper_bound,
+                             std::mt19937* generator) {
+  std::uniform_int_distribution<size_t> dist(lower_bound, upper_bound);
+  return dist(*generator);
+}
+
+}  //  namespace
+
+std::vector<size_t> FindDelimiterIndices(const std::string& delimiter,
+                                         const std::string& wgsl_code) {
+  std::vector<size_t> result;
+  for (size_t pos = wgsl_code.find(delimiter, 0); pos != std::string::npos;
+       pos = wgsl_code.find(delimiter, pos + 1)) {
+    result.push_back(pos);
+  }
+
+  return result;
+}
+
+void SwapIntervals(size_t idx1,
+                   size_t idx2,
+                   size_t idx3,
+                   size_t idx4,
+                   std::string* wgsl_code) {
+  std::string region_1 = wgsl_code->substr(idx1, idx2 - idx1 + 1);
+
+  std::string region_2 = wgsl_code->substr(idx3, idx4 - idx3 + 1);
+
+  // The second transformation is done first as it doesn't affect ind1 and ind2
+  wgsl_code->replace(idx3, region_2.size(), region_1);
+
+  wgsl_code->replace(idx1, region_1.size(), region_2);
+}
+
+size_t FuzzEnclosedRegions(size_t size,
+                           size_t max_size,
+                           const std::string& delimiter,
+                           uint8_t* wgsl_code,
+                           std::mt19937* generator) {
+  std::string init_program(wgsl_code, wgsl_code + size);
+
+  std::vector<size_t> delimiter_positions =
+      FindDelimiterIndices(delimiter, init_program);
+
+  // Need to have at least 3 indices
+  if (delimiter_positions.size() < 3) {
+    return 0;
+  }
+
+  // When generating the i-th random number, we should make sure that there are
+  // at least (3-i) numbers greater than this number.
+  size_t ind1 =
+      GetRandomIntFromRange(0, delimiter_positions.size() - 3U, generator);
+  size_t ind2 = GetRandomIntFromRange(
+      ind1 + 1U, delimiter_positions.size() - 2U, generator);
+  size_t ind3 =
+      GetRandomIntFromRange(ind2, delimiter_positions.size() - 2U, generator);
+  size_t ind4 = GetRandomIntFromRange(
+      ind3 + 1U, delimiter_positions.size() - 1U, generator);
+
+  SwapIntervals(delimiter_positions[ind1], delimiter_positions[ind2],
+                delimiter_positions[ind3], delimiter_positions[ind4],
+                &init_program);
+
+  if (init_program.size() > max_size) {
+    return 0;
+  }
+  memcpy(wgsl_code, init_program.c_str(), init_program.size());
+  return init_program.size();
+}
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/fuzzers/tint_regex_fuzzer/wgsl_mutator.h b/fuzzers/tint_regex_fuzzer/wgsl_mutator.h
new file mode 100644
index 0000000..2c760bf
--- /dev/null
+++ b/fuzzers/tint_regex_fuzzer/wgsl_mutator.h
@@ -0,0 +1,68 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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.
+
+#ifndef FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_
+#define FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_
+
+#include <random>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace tint {
+namespace fuzzers {
+namespace regex_fuzzer {
+
+/// A function that given a delimiter, returns a vector that contains
+/// all the positions of the delimiter in the WGSL code.
+/// @param delimiter - the delimiter of the enclosed region.
+/// @param wgsl_code - the initial string (WGSL code) that will be mutated.
+/// @return a vector with the positions of the delimiter in the WGSL code.
+std::vector<size_t> FindDelimiterIndices(const std::string& delimiter,
+                                         const std::string& wgsl_code);
+
+/// Given 4 indices, idx1, idx2, idx3 and idx4 it swaps the regions
+/// in the interval [idx1, idx2] with the region in the interval [idx3, idx4]
+/// in wgsl_text.
+/// @param idx1 - starting index of the first region.
+/// @param idx2 - terminating index of the second region.
+/// @param idx3 - starting index of the second region.
+/// @param idx4 - terminating index of the second region.
+/// @param wgsl_code - the string where the swap will occur.
+void SwapIntervals(size_t idx1,
+                   size_t idx2,
+                   size_t idx3,
+                   size_t idx4,
+                   std::string* wgsl_code);
+
+/// A function that, given an initial string (valid WGSL code) and a delimiter,
+/// generates a new set of strings (valid or invalid WGSL code) by
+/// picking two random regions and swapping them.
+/// @param wgsl_code - the initial string (WGSL code) that will be mutated.
+/// @param size - size of the string that will be mutated.
+/// @param max_size - maximal allowed mutation size.
+/// @param delimiter - the delimiter that will be used to find enclosed regions.
+/// @param generator - the random number generator.
+/// @return size of the mutated string.
+size_t FuzzEnclosedRegions(size_t size,
+                           size_t max_size,
+                           const std::string& delimiter,
+                           uint8_t* wgsl_code,
+                           std::mt19937* generator);
+
+}  // namespace regex_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_