ソースを参照

Do cleanup on explorer fuzzing infrastructure. (#2790)

I'm partly doing this because the current setup would be difficult to share with the toolchain. e.g., ProtoToCarbon isn't explorer-specific, but the only way to run it via CLI is the explorer's fuzzverter. I want a separate tool.

This change:

- Adds a //common/fuzzing:proto_to_carbon tool.
  - The rest of fuzzverter is now just //explorer/fuzzing:ast_to_proto.
  - The change simplifies overall handling and removes a LLVM CLI dependency.
- Stops allowing unknown fields in the proto.
  - This has mostly led to forgetting to remove fuzzer inputs that were for removed features.
- Moves more non-explorer-specific bits to //common/fuzzing.
- Cleans up remaining pieces in //explorer/fuzzing
  - Merges the //explorer/fuzzing proto tests, which deduplicates AstToString copies.
    - These tests also had duplicate dependencies, etc -- and all complete in ~6s.
  - Updates and fixes regen_corpus which was previously broken by other changes.
  - Updates the README to reflect changes.
- Removes obsolete proto-fuzzer build configuration (AFAICT this is no longer needed).
Jon Ross-Perkins 3 年 前
コミット
d12583fc08

+ 0 - 3
.bazelrc

@@ -62,9 +62,6 @@ build:asan --features=asan
 # Configuration for enabling LibFuzzer (along with ASan).
 build:fuzzer --features=fuzzer
 
-# Proto fuzzer specific configuration.
-build:proto-fuzzer --features=proto-fuzzer
-
 # Always allow tests to symbolize themselves with whatever `llvm-symbolize` is
 # in the users environment.
 build --test_env=ASAN_SYMBOLIZER_PATH

+ 0 - 8
bazel/cc_toolchains/clang_cc_toolchain_config.bzl

@@ -509,13 +509,6 @@ def _impl(ctx):
         )],
     )
 
-    proto_fuzzer = feature(
-        name = "proto-fuzzer",
-        enabled = False,
-        requires = [feature_set(["nonhost"])],
-        implies = ["fuzzer"],
-    )
-
     # With clang 14 and lower, we expect it to be built with libc++ debug
     # support. In later LLVM versions, we expect the assertions define to work.
     if clang_version and clang_version <= 14:
@@ -851,7 +844,6 @@ def _impl(ctx):
         asan,
         enable_asan_in_fastbuild,
         fuzzer,
-        proto_fuzzer,
         layering_check,
         module_maps,
         use_module_maps,

+ 27 - 0
common/fuzzing/BUILD

@@ -25,6 +25,33 @@ cc_library(
     hdrs = ["proto_to_carbon.h"],
     deps = [
         ":carbon_cc_proto",
+        "//common:error",
         "@llvm-project//llvm:Support",
     ],
 )
+
+cc_binary(
+    name = "proto_to_carbon",
+    testonly = 1,
+    srcs = ["proto_to_carbon_main.cpp"],
+    deps = [
+        ":carbon_cc_proto",
+        ":proto_to_carbon_lib",
+        "//common:bazel_working_dir",
+        "//common:error",
+        "@com_google_protobuf//:protobuf_headers",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
+cc_test(
+    name = "proto_to_carbon_test",
+    srcs = ["proto_to_carbon_test.cpp"],
+    deps = [
+        ":carbon_cc_proto",
+        ":proto_to_carbon_lib",
+        "//common:error",
+        "//common:gtest_main",
+        "@com_google_googletest//:gtest",
+    ],
+)

+ 33 - 15
common/fuzzing/proto_to_carbon.cpp

@@ -4,6 +4,8 @@
 
 #include "common/fuzzing/proto_to_carbon.h"
 
+#include <google/protobuf/text_format.h>
+
 #include <string_view>
 
 #include "common/fuzzing/carbon.pb.h"
@@ -966,28 +968,44 @@ static auto DeclarationToCarbon(const Fuzzing::Declaration& declaration,
   }
 }
 
-static auto ProtoToCarbon(const Fuzzing::CompilationUnit& compilation_unit,
-                          llvm::raw_ostream& out) -> void {
-  out << "// Generated by proto_to_carbon.\n\n";
-  out << "package ";
-  LibraryNameToCarbon(compilation_unit.package_statement(), out);
-  out << (compilation_unit.is_api() ? " api" : " impl") << ";\n";
+auto ProtoToCarbon(const Fuzzing::Carbon& proto, bool maybe_add_main)
+    -> std::string {
+  std::string source;
+  llvm::raw_string_ostream out(source);
+
+  out << "// Generated by proto_to_carbon.\n"
+         "package ";
+  const auto& unit = proto.compilation_unit();
+  LibraryNameToCarbon(unit.package_statement(), out);
+  out << (unit.is_api() ? " api" : " impl") << ";\n\n";
 
-  if (!compilation_unit.declarations().empty()) {
-    out << "\n";
-    for (const auto& declaration : compilation_unit.declarations()) {
+  bool has_main = false;
+  if (!unit.declarations().empty()) {
+    for (const auto& declaration : unit.declarations()) {
       DeclarationToCarbon(declaration, out);
+      if (declaration.kind_case() == Fuzzing::Declaration::kFunction &&
+          declaration.function().name().name() == "Main") {
+        has_main = true;
+      }
       out << "\n";
     }
   }
-}
 
-auto ProtoToCarbon(const Fuzzing::CompilationUnit& compilation_unit)
-    -> std::string {
-  std::string source;
-  llvm::raw_string_ostream out(source);
-  ProtoToCarbon(compilation_unit, out);
+  if (maybe_add_main && !has_main) {
+    out << "fn Main() -> i32 { return 0; }\n";
+  }
+
   return source;
 }
 
+auto ParseCarbonTextProto(const std::string& contents)
+    -> ErrorOr<Fuzzing::Carbon> {
+  google::protobuf::TextFormat::Parser parser;
+  Fuzzing::Carbon carbon_proto;
+  if (!parser.ParseFromString(contents, &carbon_proto)) {
+    return ErrorBuilder() << "Couldn't parse Carbon text proto";
+  }
+  return carbon_proto;
+}
+
 }  // namespace Carbon

+ 6 - 1
common/fuzzing/proto_to_carbon.h

@@ -5,6 +5,7 @@
 #ifndef CARBON_COMMON_FUZZING_PROTO_TO_CARBON_H_
 #define CARBON_COMMON_FUZZING_PROTO_TO_CARBON_H_
 
+#include "common/error.h"
 #include "common/fuzzing/carbon.pb.h"
 
 namespace Carbon {
@@ -14,9 +15,13 @@ namespace Carbon {
 // buffer is invalid (like a variable declaration with an empty `name` field).
 // This is done to reduce the number of inputs the fuzzer framework generates
 // when trying to produce lexically valid source.
-auto ProtoToCarbon(const Fuzzing::CompilationUnit& compilation_unit)
+auto ProtoToCarbon(const Fuzzing::Carbon& proto, bool maybe_add_main)
     -> std::string;
 
+// Parses the textproto into a proto object.
+auto ParseCarbonTextProto(const std::string& contents)
+    -> ErrorOr<Fuzzing::Carbon>;
+
 }  // namespace Carbon
 
 #endif  // CARBON_COMMON_FUZZING_PROTO_TO_CARBON_H_

+ 51 - 0
common/fuzzing/proto_to_carbon_main.cpp

@@ -0,0 +1,51 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// To convert a crashing input in text proto to Carbon source:
+// `proto_to_carbon <file.textproto>`
+
+#include <google/protobuf/text_format.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include "common/bazel_working_dir.h"
+#include "common/error.h"
+#include "common/fuzzing/proto_to_carbon.h"
+
+namespace Carbon {
+
+auto Main(int argc, char** argv) -> ErrorOr<Success> {
+  Carbon::SetWorkingDirForBazel();
+
+  if (argc != 2) {
+    return Error("Syntax: proto_to_carbon <file.textproto>");
+  }
+  if (!std::filesystem::is_regular_file(argv[1])) {
+    return Error("Argument must be a file.");
+  }
+
+  // Read the input file.
+  std::ifstream proto_file(argv[1]);
+  std::stringstream buffer;
+  buffer << proto_file.rdbuf();
+  proto_file.close();
+
+  CARBON_ASSIGN_OR_RETURN(Fuzzing::Carbon proto,
+                          Carbon::ParseCarbonTextProto(buffer.str()));
+  std::cout << Carbon::ProtoToCarbon(proto, /*maybe_add_main=*/true);
+  return Success();
+}
+
+}  // namespace Carbon
+
+auto main(int argc, char** argv) -> int {
+  auto err = Carbon::Main(argc, argv);
+  if (!err.ok()) {
+    std::cerr << err.error().message() << "\n";
+    return EXIT_FAILURE;
+  }
+  return EXIT_SUCCESS;
+}

+ 73 - 0
common/fuzzing/proto_to_carbon_test.cpp

@@ -0,0 +1,73 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "common/fuzzing/proto_to_carbon.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "common/error.h"
+#include "common/fuzzing/carbon.pb.h"
+
+namespace Carbon::Testing {
+namespace {
+
+TEST(FuzzerUtilTest, ProtoToCarbon) {
+  const ErrorOr<Fuzzing::Carbon> carbon_proto = ParseCarbonTextProto(R"(
+    compilation_unit {
+      package_statement { package_name: "P" }
+      is_api: true
+      declarations {
+        function {
+          name {
+            name: "Main"
+          }
+          param_pattern {}
+          return_term {
+            kind: Expression
+            type { int_type_literal {} }
+          }
+          body {
+            statements {
+              return_expression_statement {
+                expression { int_literal { value: 0 } }
+              }
+            }
+          }
+        }
+      }
+    })");
+  ASSERT_TRUE(carbon_proto.ok());
+  static constexpr char SourceCode[] = R"(// Generated by proto_to_carbon.
+package P api;
+
+fn Main() -> i32
+{
+return 0;
+}
+
+)";
+  EXPECT_THAT(ProtoToCarbon(*carbon_proto, /*maybe_add_main=*/false),
+              testing::Eq(SourceCode));
+  EXPECT_THAT(ProtoToCarbon(*carbon_proto, /*maybe_add_main=*/true),
+              testing::Eq(SourceCode));
+}
+
+TEST(FuzzerUtilTest, ParseCarbonTextProtoWithUnknownField) {
+  const ErrorOr<Fuzzing::Carbon> carbon_proto = ParseCarbonTextProto(R"(
+    compilation_unit {
+      garbage: "value"
+      declarations {
+        choice {
+          name {
+            name: "Ch"
+          }
+        }
+      }
+    })");
+  ASSERT_FALSE(carbon_proto.ok());
+}
+
+}  // namespace
+}  // namespace Carbon::Testing

+ 19 - 66
explorer/fuzzing/BUILD

@@ -16,21 +16,18 @@ cc_library(
     ],
 )
 
-cc_library(
-    name = "fuzzer_util",
+cc_binary(
+    name = "ast_to_proto",
     testonly = 1,
-    srcs = ["fuzzer_util.cpp"],
-    hdrs = ["fuzzer_util.h"],
+    srcs = ["ast_to_proto_main.cpp"],
     deps = [
-        "//common:check",
+        ":ast_to_proto_lib",
+        "//common:bazel_working_dir",
         "//common:error",
         "//common/fuzzing:carbon_cc_proto",
-        "//common/fuzzing:proto_to_carbon_lib",
-        "//explorer/interpreter:exec_program",
-        "//explorer/interpreter:trace_stream",
+        "//explorer/ast",
+        "//explorer/common:arena",
         "//explorer/syntax",
-        "//explorer/syntax:prelude",
-        "@bazel_tools//tools/cpp/runfiles",
         "@com_google_protobuf//:protobuf_headers",
         "@llvm-project//llvm:Support",
     ],
@@ -50,35 +47,34 @@ cc_test(
     deps = [
         ":ast_to_proto_lib",
         "//common/fuzzing:carbon_cc_proto",
+        "//common/fuzzing:proto_to_carbon_lib",
         "//explorer/syntax",
         "@com_google_googletest//:gtest",
         "@com_google_protobuf//:protobuf_headers",
-        "@llvm-project//llvm:Support",
     ],
 )
 
-cc_binary(
-    name = "fuzzverter",
+cc_library(
+    name = "fuzzer_util",
     testonly = 1,
-    srcs = ["fuzzverter.cpp"],
+    srcs = ["fuzzer_util.cpp"],
+    hdrs = ["fuzzer_util.h"],
     deps = [
-        ":ast_to_proto_lib",
-        ":fuzzer_util",
+        "//common:check",
         "//common:error",
         "//common/fuzzing:carbon_cc_proto",
-        "//explorer/common:error_builders",
-        "//explorer/common:nonnull",
+        "//common/fuzzing:proto_to_carbon_lib",
+        "//explorer/ast",
+        "//explorer/interpreter:exec_program",
+        "//explorer/interpreter:trace_stream",
         "//explorer/syntax",
+        "//explorer/syntax:prelude",
+        "@bazel_tools//tools/cpp/runfiles",
         "@com_google_protobuf//:protobuf_headers",
         "@llvm-project//llvm:Support",
     ],
 )
 
-filegroup(
-    name = "fuzzer_corpus_files",
-    srcs = glob(["fuzzer_corpus/**"]),
-)
-
 cc_test(
     name = "fuzzer_util_test",
     srcs = ["fuzzer_util_test.cpp"],
@@ -88,50 +84,7 @@ cc_test(
     deps = [
         ":fuzzer_util",
         "//common:gtest_main",
-        "@com_google_googletest//:gtest",
-        "@com_google_protobuf//:protobuf_headers",
-        "@llvm-project//llvm:Support",
-    ],
-)
-
-cc_test(
-    name = "proto_to_carbon_test",
-    srcs = ["proto_to_carbon_test.cpp"],
-    args = [
-        "$(locations //explorer:standard_libraries)",
-        "$(locations //explorer/testdata:carbon_files)",
-    ],
-    data = [
-        "//explorer:standard_libraries",
-        "//explorer/testdata:carbon_files",
-    ],
-    deps = [
-        ":ast_to_proto_lib",
-        "//common/fuzzing:carbon_cc_proto",
         "//common/fuzzing:proto_to_carbon_lib",
-        "//explorer/syntax",
-        "@com_google_googletest//:gtest",
-        "@com_google_protobuf//:protobuf_headers",
-        "@llvm-project//llvm:Support",
-    ],
-)
-
-cc_test(
-    name = "clone_test",
-    srcs = ["clone_test.cpp"],
-    args = [
-        "$(locations //explorer:standard_libraries)",
-        "$(locations //explorer/testdata:carbon_files)",
-    ],
-    data = [
-        "//explorer:standard_libraries",
-        "//explorer/testdata:carbon_files",
-    ],
-    deps = [
-        ":ast_to_proto_lib",
-        "//common/fuzzing:carbon_cc_proto",
-        "//explorer/ast",
-        "//explorer/syntax",
         "@com_google_googletest//:gtest",
         "@com_google_protobuf//:protobuf_headers",
         "@llvm-project//llvm:Support",

+ 30 - 46
explorer/fuzzing/README.md

@@ -36,44 +36,29 @@ message definition in `common/fuzzing/carbon.proto`.
 
 ## Incorporating AST changes into the fuzzer
 
-Fuzzer AST representation in
-[carbon.proto](https://github.com/carbon-language/carbon-lang/blob/trunk/common/fuzzing/carbon.proto)
-needs to be updated when changes are made to the AST, like adding a new AST node
+Fuzzer AST representation in [carbon.proto](/common/fuzzing/carbon.proto) needs
+to be updated when changes are made to the AST, like adding a new AST node
 classes or changing relevant data members of existing nodes.
 
-There are two unit tests which normally should not require direct changes, as
-both tests work off of Carbon test files in
-[testdata](https://github.com/carbon-language/carbon-lang/tree/trunk/explorer/testdata).
-
--   [ast_to_proto_test.cpp](https://github.com/carbon-language/carbon-lang/blob/trunk/explorer/fuzzing/ast_to_proto_test.cpp)
-    is a 'smoke' test which verifies that each field of Carbon proto is
-    populated at least once after converting all of test Carbon files and
-    merging the results into a single protocol buffer.
-
--   [proto_to_carbon_test.cpp](https://github.com/carbon-language/carbon-lang/blob/trunk/explorer/fuzzing/proto_to_carbon_test.cpp)
-    uses a 'roundtrip' approach, by converting each parseable Carbon file to a
-    proto representation, then back to Carbon source, parsing this source into a
-    second instance of an AST, and comparing the second AST with the original
-    AST using `AST::Dump()` method. The goal of the test is to ensure that
-    `carbon.proto` is able to represent ASTs correctly without information loss.
+[ast_to_proto_test](ast_to_proto_test.cpp) normally should not require direct
+changes, as tests work off of Carbon test files in
+[testdata](/explorer/testdata).
 
 To incorporate AST changes into fuzzing logic:
 
 1. Add appropriate AST information to
-   [carbon.proto](https://github.com/carbon-language/carbon-lang/blob/trunk/common/fuzzing/carbon.proto).
-   Use existing similar cases as examples.
-
-1. Add logic to populate the proto to
-   [ast_to_proto.cpp](https://github.com/carbon-language/carbon-lang/blob/trunk/explorer/fuzzing/ast_to_proto.cpp).
+   [carbon.proto](/common/fuzzing/carbon.proto). Use existing similar cases as
+   examples.
 
-1. Make sure `ast_to_proto_test` passes with the new changes.
+2. Modify [proto_to_carbon.cpp](/common/fuzzing/proto_to_carbon.cpp) which
+   handles printing of a Carbon proto instance as a Carbon source string. For
+   example, add code to print newly introduced proto fields.
 
-1. Modify
-   [proto_to_carbon.cpp](https://github.com/carbon-language/carbon-lang/blob/trunk/common/fuzzing/proto_to_carbon.cpp)
-   which handles printing of a Carbon proto instance as a Carbon source string.
-   For example, add code to print newly introduced proto fields.
+3. Add logic to populate the proto to
+   [ast_to_proto.cpp](/explorer/fuzzing/ast_to_proto.cpp).
 
-1. Make sure `proto_to_carbon_test` passes after the changes.
+4. Make sure `bazel test //explorer/fuzzing:ast_to_proto_test` passes with the
+   new changes.
 
 ## Running the fuzzer
 
@@ -85,36 +70,35 @@ a crash is triggered, or forever in a bug-free program ;).
 To run in 'unit test' mode:
 
 ```bash
-bazel test --config=proto-fuzzer --test_output=all //explorer/fuzzing:explorer_fuzzer
+bazel test //explorer/fuzzing:explorer_fuzzer
 ```
 
 To run in 'fuzzing' mode:
 
 ```bash
-bazel build --config=proto-fuzzer //explorer/fuzzing:explorer_fuzzer
-
-bazel-bin/explorer/fuzzing/explorer_fuzzer
-```
-
-It's also possible to run the fuzzer on a single input:
+bazel build --config=fuzzer //explorer/fuzzing:explorer_fuzzer.full_corpus
 
-```bash
-bazel-bin/explorer/fuzzing/explorer_fuzzer /tmp/crash.textproto
+bazel-bin/explorer/fuzzing/explorer_fuzzer.full_corpus
 ```
 
 ## Investigating a crash
 
-To reproduce a crash, run the fuzzer on the crashing input as described above.
+Typically it's going to be easiest to run explorer on directly. You can do this
+with:
+
+````bash
+# Convert the crash to a source file.
+bazel run //common/fuzzing:proto_to_carbon -- /tmp/crash.textproto > crash.carbon
 
-A separate tool called `fuzzverter` can be used for things like converting a
-crashing input to Carbon source code for running `explorer` on the code
-directly.
+# Run explorer on the crash.
+bazel run //explorer -- crash.carbon
+```
 
-To convert a `Fuzzing::Carbon` text proto to Carbon source:
+It's also possible to run the fuzzer on a single input:
 
 ```bash
-bazel-bin/explorer/fuzzing/fuzzverter --mode proto_to_carbon --input /tmp/crash.textproto
-```
+bazel-bin/explorer/fuzzing/explorer_fuzzer.full_corpus /tmp/crash.textproto
+````
 
 ## Generating new fuzzer corpus entries
 
@@ -125,5 +109,5 @@ to be a `Fuzzing::Carbon` text proto.
 To generate a text proto from Carbon source:
 
 ```bash
-bazel-bin/explorer/fuzzing/fuzzverter --mode carbon_to_proto --input /tmp/crash.carbon --output /tmp/crash.textproto
+bazel run //explorer/fuzzing:ast_to_proto -- /tmp/crash.carbon > crash.textproto
 ```

+ 7 - 7
explorer/fuzzing/ast_to_proto.cpp

@@ -843,15 +843,15 @@ static auto DeclarationToProto(const Declaration& declaration)
   return declaration_proto;
 }
 
-auto AstToProto(const AST& ast) -> Fuzzing::CompilationUnit {
-  Fuzzing::CompilationUnit compilation_unit;
-  *compilation_unit.mutable_package_statement() =
-      LibraryNameToProto(ast.package);
-  compilation_unit.set_is_api(ast.is_api);
+auto AstToProto(const AST& ast) -> Fuzzing::Carbon {
+  Fuzzing::Carbon carbon;
+  auto* unit = carbon.mutable_compilation_unit();
+  *unit->mutable_package_statement() = LibraryNameToProto(ast.package);
+  unit->set_is_api(ast.is_api);
   for (const Declaration* declaration : ast.declarations) {
-    *compilation_unit.add_declarations() = DeclarationToProto(*declaration);
+    *unit->add_declarations() = DeclarationToProto(*declaration);
   }
-  return compilation_unit;
+  return carbon;
 }
 
 }  // namespace Carbon

+ 1 - 1
explorer/fuzzing/ast_to_proto.h

@@ -11,7 +11,7 @@
 namespace Carbon {
 
 // Builds a protobuf representation of `ast`.
-auto AstToProto(const AST& ast) -> Fuzzing::CompilationUnit;
+auto AstToProto(const AST& ast) -> Fuzzing::Carbon;
 
 }  // namespace Carbon
 

+ 66 - 0
explorer/fuzzing/ast_to_proto_main.cpp

@@ -0,0 +1,66 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// To convert a Carbon file to a text proto:
+// `ast_to_proto <file.carbon>`
+
+#include <google/protobuf/text_format.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include "common/bazel_working_dir.h"
+#include "common/error.h"
+#include "common/fuzzing/carbon.pb.h"
+#include "explorer/ast/ast.h"
+#include "explorer/common/arena.h"
+#include "explorer/fuzzing/ast_to_proto.h"
+#include "explorer/syntax/parse.h"
+
+namespace Carbon {
+
+auto Main(int argc, char** argv) -> ErrorOr<Success> {
+  Carbon::SetWorkingDirForBazel();
+
+  if (argc != 2) {
+    return Error("Syntax: ast_to_proto <file.carbon>");
+  }
+  if (!std::filesystem::is_regular_file(argv[1])) {
+    return Error("Argument must be a file.");
+  }
+
+  // Read the input file.
+  std::ifstream proto_file(argv[1]);
+  std::stringstream buffer;
+  buffer << proto_file.rdbuf();
+  proto_file.close();
+
+  Arena arena;
+  const ErrorOr<AST> ast = Parse(&arena, argv[1],
+                                 /*parser_debug=*/false);
+  if (!ast.ok()) {
+    return ErrorBuilder() << "Parsing failed: " << ast.error().message();
+  }
+  Fuzzing::Carbon carbon_proto = AstToProto(*ast);
+
+  std::string proto_string;
+  google::protobuf::TextFormat::Printer p;
+  if (!p.PrintToString(carbon_proto, &proto_string)) {
+    return Error("Failed to convert to text proto");
+  }
+  std::cout << proto_string;
+  return Success();
+}
+
+}  // namespace Carbon
+
+auto main(int argc, char** argv) -> int {
+  auto err = Carbon::Main(argc, argv);
+  if (!err.ok()) {
+    std::cerr << err.error().message() << "\n";
+    return EXIT_FAILURE;
+  }
+  return EXIT_SUCCESS;
+}

+ 96 - 21
explorer/fuzzing/ast_to_proto_test.cpp

@@ -6,6 +6,7 @@
 
 #include <gmock/gmock.h>
 #include <google/protobuf/descriptor.h>
+#include <google/protobuf/util/message_differencer.h>
 #include <gtest/gtest.h>
 
 #include <filesystem>
@@ -13,8 +14,8 @@
 #include <set>
 #include <variant>
 
+#include "common/fuzzing/proto_to_carbon.h"
 #include "explorer/syntax/parse.h"
-#include "llvm/Support/Error.h"
 
 namespace Carbon::Testing {
 namespace {
@@ -26,6 +27,18 @@ using ::google::protobuf::Reflection;
 
 static std::vector<llvm::StringRef>* carbon_files = nullptr;
 
+// Returns a string representation of `ast`.
+auto AstToString(const AST& ast) -> std::string {
+  std::string s;
+  llvm::raw_string_ostream out(s);
+  out << "package " << ast.package.package << (ast.is_api ? "api" : "impl")
+      << ";\n";
+  for (auto* declaration : ast.declarations) {
+    out << *declaration << "\n";
+  }
+  return s;
+}
+
 // Concatenates message and field names.
 auto FieldName(const Descriptor& descriptor, const FieldDescriptor& field)
     -> std::string {
@@ -80,35 +93,29 @@ auto CollectUsedFields(const Message& message,
   }
 }
 
-// Determines which fields in the proto have not been used at all.
-auto GetUnusedFields(const Message& message) -> std::set<std::string> {
-  std::set<std::string> all_messages;
-  std::set<std::string> all_fields;
-  CollectAllFields(*message.GetDescriptor(), all_messages, all_fields);
-
-  std::set<std::string> used_fields;
-  CollectUsedFields(message, used_fields);
-
-  std::set<std::string> unused_fields;
-  std::set_difference(all_fields.begin(), all_fields.end(), used_fields.begin(),
-                      used_fields.end(),
-                      std::inserter(unused_fields, unused_fields.begin()));
-  return unused_fields;
-}
-
 // A 'smoke' test to check that each field present in `carbon.proto` is set at
 // least once after converting all Carbon test sources to proto representation.
 TEST(AstToProtoTest, SetsAllProtoFields) {
-  Carbon::Fuzzing::CompilationUnit merged_proto;
+  Fuzzing::Carbon merged_proto;
   for (const llvm::StringRef f : *carbon_files) {
-    Carbon::Arena arena;
-    const ErrorOr<AST> ast = Carbon::Parse(&arena, f, /*parser_debug=*/false);
+    Arena arena;
+    const ErrorOr<AST> ast = Parse(&arena, f, /*parser_debug=*/false);
     if (ast.ok()) {
       merged_proto.MergeFrom(AstToProto(*ast));
     }
   }
 
-  std::set<std::string> unused_fields = GetUnusedFields(merged_proto);
+  std::set<std::string> all_messages;
+  std::set<std::string> all_fields;
+  CollectAllFields(*Fuzzing::Carbon::GetDescriptor(), all_messages, all_fields);
+
+  std::set<std::string> used_fields;
+  CollectUsedFields(merged_proto, used_fields);
+
+  std::set<std::string> unused_fields;
+  std::set_difference(all_fields.begin(), all_fields.end(), used_fields.begin(),
+                      used_fields.end(),
+                      std::inserter(unused_fields, unused_fields.begin()));
   EXPECT_EQ(unused_fields.size(), 0)
       << "Unused fields"
       << std::accumulate(unused_fields.begin(), unused_fields.end(),
@@ -118,6 +125,74 @@ TEST(AstToProtoTest, SetsAllProtoFields) {
                          });
 }
 
+// Ensures that `carbon.proto` is able to represent ASTs correctly without
+// information loss by doing round-trip testing of files:
+//
+// 1) Converts each parseable Carbon file to a proto representation.
+// 2) Converts back to Carbon source.
+// 3) Parses the source into a second instance of an AST.
+// 4) Compares the second AST with the original.
+TEST(AstToProtoTest, Roundtrip) {
+  int parsed_ok_count = 0;
+  for (const llvm::StringRef f : *carbon_files) {
+    Arena arena;
+    const ErrorOr<AST> ast = Parse(&arena, f, /*parser_debug=*/false);
+    if (ast.ok()) {
+      ++parsed_ok_count;
+      const std::string source_from_proto =
+          ProtoToCarbon(AstToProto(*ast), /*maybe_add_main=*/false);
+      SCOPED_TRACE(testing::Message()
+                   << "Carbon file: " << f << ", source from proto:\n"
+                   << source_from_proto);
+      const ErrorOr<AST> ast_from_proto =
+          ParseFromString(&arena, f, source_from_proto, /*parser_debug=*/false);
+
+      if (ast_from_proto.ok()) {
+        EXPECT_EQ(AstToString(*ast), AstToString(*ast_from_proto));
+      } else {
+        ADD_FAILURE() << "Parse error " << ast_from_proto.error().message();
+      }
+    }
+  }
+  // Makes sure files were actually processed.
+  EXPECT_GT(parsed_ok_count, 0);
+}
+
+auto CloneAST(Arena& arena, const AST& ast) -> AST {
+  CloneContext context(&arena);
+  return {
+      .package = ast.package,
+      .is_api = ast.is_api,
+      .imports = ast.imports,
+      .declarations = context.Clone(ast.declarations),
+      .main_call = context.Clone(ast.main_call),
+      .num_prelude_declarations = ast.num_prelude_declarations,
+  };
+}
+
+// Verifies that an AST and its clone produce identical protos.
+TEST(AstToProtoTest, SameProtoAfterClone) {
+  int parsed_ok_count = 0;
+  for (const llvm::StringRef f : *carbon_files) {
+    Arena arena;
+    const ErrorOr<AST> ast = Parse(&arena, f, /*parser_debug=*/false);
+    if (ast.ok()) {
+      ++parsed_ok_count;
+      const AST clone = CloneAST(arena, *ast);
+      const Fuzzing::Carbon orig_proto = AstToProto(*ast);
+      const Fuzzing::Carbon clone_proto = AstToProto(clone);
+      // TODO: Use EqualsProto once it's available.
+      EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals(
+          orig_proto, clone_proto))
+          << "clone produced a different AST. original:\n"
+          << AstToString(*ast) << "clone:\n"
+          << AstToString(clone);
+    }
+  }
+  // Makes sure files were actually processed.
+  EXPECT_GT(parsed_ok_count, 0);
+}
+
 }  // namespace
 }  // namespace Carbon::Testing
 

+ 0 - 73
explorer/fuzzing/clone_test.cpp

@@ -1,73 +0,0 @@
-// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#include <gmock/gmock.h>
-#include <google/protobuf/util/message_differencer.h>
-#include <gtest/gtest.h>
-
-#include "explorer/ast/clone_context.h"
-#include "explorer/fuzzing/ast_to_proto.h"
-#include "explorer/syntax/parse.h"
-
-namespace Carbon::Testing {
-namespace {
-
-static std::vector<llvm::StringRef>* carbon_files = nullptr;
-
-auto CloneAST(Arena& arena, const AST& ast) -> AST {
-  CloneContext context(&arena);
-  return {
-      .package = ast.package,
-      .is_api = ast.is_api,
-      .imports = ast.imports,
-      .declarations = context.Clone(ast.declarations),
-      .main_call = context.Clone(ast.main_call),
-      .num_prelude_declarations = ast.num_prelude_declarations,
-  };
-}
-
-// Returns a string representation of `ast`.
-auto AstToString(const AST& ast) -> std::string {
-  std::string s;
-  llvm::raw_string_ostream out(s);
-  out << "package " << ast.package.package << (ast.is_api ? "api" : "impl")
-      << ";\n";
-  for (auto* declaration : ast.declarations) {
-    out << *declaration << "\n";
-  }
-  return s;
-}
-
-TEST(CloneTest, SameProtoAfterClone) {
-  int parsed_ok_count = 0;
-  for (const llvm::StringRef f : *carbon_files) {
-    Carbon::Arena arena;
-    const ErrorOr<AST> ast = Carbon::Parse(&arena, f, /*parser_debug=*/false);
-    if (ast.ok()) {
-      ++parsed_ok_count;
-      const AST clone = CloneAST(arena, *ast);
-      const Fuzzing::CompilationUnit orig_proto = AstToProto(*ast);
-      const Fuzzing::CompilationUnit clone_proto = AstToProto(clone);
-      // TODO: Use EqualsProto once it's available.
-      EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals(
-          orig_proto, clone_proto))
-          << "clone produced a different AST. original:\n"
-          << AstToString(*ast) << "clone:\n"
-          << AstToString(clone);
-    }
-  }
-  // Makes sure files were actually processed.
-  EXPECT_GT(parsed_ok_count, 0);
-}
-
-}  // namespace
-}  // namespace Carbon::Testing
-
-auto main(int argc, char** argv) -> int {
-  ::testing::InitGoogleTest(&argc, argv);
-  // gtest should remove flags, leaving just input files.
-  std::vector<llvm::StringRef> carbon_files(&argv[1], &argv[argc]);
-  Carbon::Testing::carbon_files = &carbon_files;
-  return RUN_ALL_TESTS();
-}

+ 1 - 1
explorer/fuzzing/explorer_fuzzer.cpp

@@ -10,5 +10,5 @@
 
 DEFINE_TEXT_PROTO_FUZZER(const Carbon::Fuzzing::Carbon& input) {
   // Only verifying it doesn't crash.
-  (void)Carbon::ParseAndExecute(input.compilation_unit());
+  (void)Carbon::ParseAndExecute(input);
 }

+ 4 - 40
explorer/fuzzing/fuzzer_util.cpp

@@ -19,16 +19,7 @@
 
 namespace Carbon {
 
-// Appended to fuzzer-generated Carbon source when the source is missing
-// `Main()` definition, to prevent early error return in semantic analysis.
-static constexpr char EmptyMain[] = R"(
-fn Main() -> i32 {
-  return 0;
-}
-)";
-
-auto Internal::GetRunfilesFile(const std::string& file)
-    -> ErrorOr<std::string> {
+auto GetRunfilesFile(const std::string& file) -> ErrorOr<std::string> {
   using bazel::tools::cpp::runfiles::Runfiles;
   std::string error;
   // `Runfiles::Create()` fails if passed an empty `argv0`.
@@ -44,42 +35,15 @@ auto Internal::GetRunfilesFile(const std::string& file)
   return full_path;
 }
 
-auto ParseCarbonTextProto(const std::string& contents, bool allow_unknown)
-    -> ErrorOr<Fuzzing::Carbon> {
-  google::protobuf::TextFormat::Parser parser;
-  if (allow_unknown) {
-    parser.AllowUnknownField(true);
-    parser.AllowUnknownExtension(true);
-  }
-  Fuzzing::Carbon carbon_proto;
-  if (!parser.ParseFromString(contents, &carbon_proto)) {
-    return ErrorBuilder() << "Couldn't parse Carbon text proto";
-  }
-  return carbon_proto;
-}
-
-auto ProtoToCarbonWithMain(const Fuzzing::CompilationUnit& compilation_unit)
-    -> std::string {
-  const bool has_main = std::any_of(
-      compilation_unit.declarations().begin(),
-      compilation_unit.declarations().end(),
-      [](const Fuzzing::Declaration& decl) {
-        return decl.kind_case() == Fuzzing::Declaration::kFunction &&
-               decl.function().name().name() == "Main";
-      });
-  return Carbon::ProtoToCarbon(compilation_unit) + (has_main ? "" : EmptyMain);
-}
-
-auto ParseAndExecute(const Fuzzing::CompilationUnit& compilation_unit)
-    -> ErrorOr<int> {
-  const std::string source = ProtoToCarbonWithMain(compilation_unit);
+auto ParseAndExecute(const Fuzzing::Carbon& carbon) -> ErrorOr<int> {
+  const std::string source = ProtoToCarbon(carbon, /*maybe_add_main=*/true);
 
   Arena arena;
   CARBON_ASSIGN_OR_RETURN(AST ast,
                           ParseFromString(&arena, "Fuzzer.carbon", source,
                                           /*parser_debug=*/false));
   const ErrorOr<std::string> prelude_path =
-      Internal::GetRunfilesFile("carbon/explorer/data/prelude.carbon");
+      GetRunfilesFile("carbon/explorer/data/prelude.carbon");
   // Can't do anything without a prelude, so it's a fatal error.
   CARBON_CHECK(prelude_path.ok()) << prelude_path.error();
 

+ 2 - 15
explorer/fuzzing/fuzzer_util.h

@@ -7,31 +7,18 @@
 
 #include "common/error.h"
 #include "common/fuzzing/carbon.pb.h"
+#include "explorer/ast/ast.h"
 
 namespace Carbon {
 
-// Parses text proto with a Carbon message, optionally ignoring unknown fields.
-auto ParseCarbonTextProto(const std::string& contents,
-                          bool allow_unknown = true)
-    -> ErrorOr<Fuzzing::Carbon>;
-
-// Converts `compilation_unit` to Carbon. Adds an default `Main()`
-// definition if one is not present in the proto.
-auto ProtoToCarbonWithMain(const Fuzzing::CompilationUnit& compilation_unit)
-    -> std::string;
-
 // Parses and executes a fuzzer-generated program.
 // Returns program result if execution was successful.
-auto ParseAndExecute(const Fuzzing::CompilationUnit& compilation_unit)
-    -> ErrorOr<int>;
-
-namespace Internal {
+auto ParseAndExecute(const Fuzzing::Carbon& carbon) -> ErrorOr<int>;
 
 // Returns a full path for a file under bazel runfiles.
 // Exposed for testing.
 auto GetRunfilesFile(const std::string& file) -> ErrorOr<std::string>;
 
-}  // namespace Internal
 }  // namespace Carbon
 
 #endif  // CARBON_EXPLORER_FUZZING_FUZZER_UTIL_H_

+ 4 - 24
explorer/fuzzing/fuzzer_util_test.cpp

@@ -9,6 +9,7 @@
 
 #include <fstream>
 
+#include "common/fuzzing/proto_to_carbon.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/raw_ostream.h"
 
@@ -41,38 +42,17 @@ TEST(FuzzerUtilTest, ParseAndExecute) {
       }
     })");
   ASSERT_TRUE(carbon_proto.ok());
-  const ErrorOr<int> result = ParseAndExecute(carbon_proto->compilation_unit());
+  const ErrorOr<int> result = ParseAndExecute(*carbon_proto);
   ASSERT_TRUE(result.ok()) << "Execution failed: " << result.error();
   EXPECT_EQ(*result, 0);
 }
 
 TEST(FuzzerUtilTest, GetRunfilesFile) {
-  EXPECT_THAT(*Internal::GetRunfilesFile("carbon/explorer/data/prelude.carbon"),
+  EXPECT_THAT(*GetRunfilesFile("carbon/explorer/data/prelude.carbon"),
               testing::EndsWith("/prelude.carbon"));
-  EXPECT_THAT(Internal::GetRunfilesFile("nonexistent-file").error().message(),
+  EXPECT_THAT(GetRunfilesFile("nonexistent-file").error().message(),
               testing::EndsWith("doesn't exist"));
 }
 
-TEST(FuzzerUtilTest, ParseCarbonTextProtoWithUnknownField) {
-  const ErrorOr<Fuzzing::Carbon> carbon_proto =
-      ParseCarbonTextProto(R"(
-    compilation_unit {
-      garbage: "value"
-      declarations {
-        choice {
-          name {
-            name: "Ch"
-          }
-        }
-      }
-    })",
-                           /*allow_unknown=*/true);
-  ASSERT_TRUE(carbon_proto.ok());
-  // No EqualsProto in gmock - https://github.com/google/googletest/issues/1761.
-  EXPECT_EQ(
-      carbon_proto->compilation_unit().declarations(0).choice().name().name(),
-      "Ch");
-}
-
 }  // namespace
 }  // namespace Carbon::Testing

+ 0 - 125
explorer/fuzzing/fuzzverter.cpp

@@ -1,125 +0,0 @@
-// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-// An utility for converting between fuzzer protos and Carbon sources.
-//
-// For example, to convert a crashing input in text proto to carbon source:
-// `fuzzverter --mode=proto_to_carbon --input file.textproto`
-//
-// To generate a new text proto from carbon source for seeding the corpus:
-// `fuzzverter --mode=carbon_to_proto --input file.carbon`
-
-#include <google/protobuf/text_format.h>
-
-#include <cstdlib>
-#include <fstream>
-#include <ios>
-#include <sstream>
-
-#include "common/error.h"
-#include "common/fuzzing/carbon.pb.h"
-#include "explorer/common/error_builders.h"
-#include "explorer/fuzzing/ast_to_proto.h"
-#include "explorer/fuzzing/fuzzer_util.h"
-#include "explorer/syntax/parse.h"
-#include "llvm/Support/CommandLine.h"
-#include "llvm/Support/InitLLVM.h"
-
-namespace Carbon {
-
-namespace cl = llvm::cl;
-
-// Reads a file and returns its contents as a string.
-static auto ReadFile(std::string_view file_name) -> ErrorOr<std::string> {
-  std::ifstream file(file_name, std::ios::in);
-  if (!file.is_open()) {
-    return ErrorBuilder() << "Could not open " << file_name << " for reading";
-  }
-  std::stringstream ss;
-  ss << file.rdbuf();
-  return ss.str();
-}
-
-// Writes string `s` to `file_name`.
-static auto WriteFile(std::string_view s, std::string_view file_name)
-    -> ErrorOr<Success> {
-  std::ofstream file(file_name, std::ios::out);
-  if (!file.is_open()) {
-    return ErrorBuilder() << "Could not open " << file_name << " for writing";
-  }
-  file << s;
-  return Success();
-}
-
-// Converts text proto to Carbon source.
-static auto TextProtoToCarbon(std::string_view input_file_name,
-                              std::string_view output_file_name)
-    -> ErrorOr<Success> {
-  CARBON_ASSIGN_OR_RETURN(const std::string input_contents,
-                          ReadFile(input_file_name));
-  CARBON_ASSIGN_OR_RETURN(const Fuzzing::Carbon carbon_proto,
-                          ParseCarbonTextProto(input_contents));
-  const std::string carbon_source =
-      ProtoToCarbonWithMain(carbon_proto.compilation_unit());
-  return WriteFile(carbon_source, output_file_name);
-}
-
-// Converts Carbon source to text proto.
-static auto CarbonToTextProto(std::string_view input_file_name,
-                              std::string_view output_file_name)
-    -> ErrorOr<Success> {
-  Carbon::Arena arena;
-  const ErrorOr<AST> ast = Carbon::Parse(&arena, input_file_name,
-                                         /*parser_debug=*/false);
-  if (!ast.ok()) {
-    return ErrorBuilder() << "Parsing failed: " << ast.error().message();
-  }
-  Fuzzing::Carbon carbon_proto;
-  *carbon_proto.mutable_compilation_unit() = AstToProto(*ast);
-
-  std::string proto_string;
-  google::protobuf::TextFormat::Printer p;
-  if (!p.PrintToString(carbon_proto, &proto_string)) {
-    return Error("Failed to convert to text proto");
-  }
-  return WriteFile(proto_string, output_file_name);
-}
-
-// Command line options for defining input/output format.
-enum class ConversionMode { TextProtoToCarbon, CarbonToTextProto };
-
-auto Main(int argc, char* argv[]) -> ErrorOr<Success> {
-  llvm::InitLLVM init_llvm(argc, argv);
-
-  cl::opt<ConversionMode> mode(
-      "mode", cl::desc("Conversion mode"),
-      cl::values(
-          clEnumValN(ConversionMode::TextProtoToCarbon, "proto_to_carbon",
-                     "Convert text proto to Carbon source"),
-          clEnumValN(ConversionMode::CarbonToTextProto, "carbon_to_proto",
-                     "Convert Carbon source to text proto")),
-      cl::Required);
-  cl::opt<std::string> input_file_name("input", cl::desc("<input file>"),
-                                       cl::init("/dev/stdin"));
-  cl::opt<std::string> output_file_name("output", cl::desc("<output file>"),
-                                        cl::init("/dev/stdout"));
-  cl::ParseCommandLineOptions(argc, argv);
-
-  switch (mode) {
-    case ConversionMode::TextProtoToCarbon:
-      return TextProtoToCarbon(input_file_name, output_file_name);
-    case ConversionMode::CarbonToTextProto:
-      return CarbonToTextProto(input_file_name, output_file_name);
-  }
-}
-
-}  // namespace Carbon
-
-auto main(int argc, char* argv[]) -> int {
-  if (const auto result = Carbon::Main(argc, argv); !result.ok()) {
-    llvm::errs() << result.error().message() << "\n";
-    return EXIT_FAILURE;
-  }
-  return EXIT_SUCCESS;
-}

+ 0 - 64
explorer/fuzzing/proto_to_carbon_test.cpp

@@ -1,64 +0,0 @@
-// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#include "common/fuzzing/proto_to_carbon.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include "explorer/fuzzing/ast_to_proto.h"
-#include "explorer/syntax/parse.h"
-
-namespace Carbon::Testing {
-namespace {
-
-static std::vector<llvm::StringRef>* carbon_files = nullptr;
-
-// Returns a string representation of `ast`.
-auto AstToString(const AST& ast) -> std::string {
-  std::string s;
-  llvm::raw_string_ostream out(s);
-  out << "package " << ast.package.package << (ast.is_api ? "api" : "impl")
-      << ";\n";
-  for (auto* declaration : ast.declarations) {
-    out << *declaration << "\n";
-  }
-  return s;
-}
-
-TEST(ProtoToCarbonTest, Roundtrip) {
-  int parsed_ok_count = 0;
-  for (const llvm::StringRef f : *carbon_files) {
-    Carbon::Arena arena;
-    const ErrorOr<AST> ast = Carbon::Parse(&arena, f, /*parser_debug=*/false);
-    if (ast.ok()) {
-      ++parsed_ok_count;
-      const std::string source_from_proto = ProtoToCarbon(AstToProto(*ast));
-      SCOPED_TRACE(testing::Message()
-                   << "Carbon file: " << f << ", source from proto:\n"
-                   << source_from_proto);
-      const ErrorOr<AST> ast_from_proto = Carbon::ParseFromString(
-          &arena, f, source_from_proto, /*parser_debug=*/false);
-
-      if (ast_from_proto.ok()) {
-        EXPECT_EQ(AstToString(*ast), AstToString(*ast_from_proto));
-      } else {
-        ADD_FAILURE() << "Parse error " << ast_from_proto.error().message();
-      }
-    }
-  }
-  // Makes sure files were actually processed.
-  EXPECT_GT(parsed_ok_count, 0);
-}
-
-}  // namespace
-}  // namespace Carbon::Testing
-
-auto main(int argc, char** argv) -> int {
-  ::testing::InitGoogleTest(&argc, argv);
-  // gtest should remove flags, leaving just input files.
-  std::vector<llvm::StringRef> carbon_files(&argv[1], &argv[argc]);
-  Carbon::Testing::carbon_files = &carbon_files;
-  return RUN_ALL_TESTS();
-}

+ 6 - 11
explorer/fuzzing/regen_corpus.py

@@ -35,8 +35,7 @@ def _carbon_to_proto(carbon_file: str) -> str:
     """Converts carbon file to text proto string."""
     try:
         p = subprocess.run(
-            "bazel-bin/explorer/fuzzing/fuzzverter --mode carbon_to_proto "
-            f"--input {carbon_file} --output /dev/stdout",
+            f"bazel-bin/explorer/fuzzing/ast_to_proto {carbon_file}",
             shell=True,
             check=True,
             stdout=subprocess.PIPE,
@@ -65,12 +64,12 @@ def _write_corpus_files(text_protos: Iterable[str], corpus_dir: str) -> None:
 def main() -> None:
     os.chdir(os.path.join(os.path.dirname(__file__), "../.."))
 
-    print("Building fuzzverter...", flush=True)
+    print("Building ast_to_proto...", flush=True)
     subprocess.check_call(
         [
             "bazel",
             "build",
-            "//explorer/fuzzing:fuzzverter",
+            "//explorer/fuzzing:ast_to_proto",
         ]
     )
     carbon_sources = _get_files(_TESTDATA, ".carbon")
@@ -95,12 +94,8 @@ def main() -> None:
             [
                 "bazel",
                 "build",
-                "--features=fuzzer",
-                # Workaround for #1208.
-                "--copt=-U_LIBCPP_DEBUG",
-                # Workaround for #1173.
-                "--per_file_copt=llvm/.*@-fno-sanitize=fuzzer",
-                "//explorer/fuzzing:explorer_fuzzer",
+                "--config=fuzzer",
+                "//explorer/fuzzing:explorer_fuzzer.full_corpus",
             ]
         )
 
@@ -110,7 +105,7 @@ def main() -> None:
         )
         subprocess.check_call(
             [
-                "bazel-bin/explorer/fuzzing/explorer_fuzzer",
+                "bazel-bin/explorer/fuzzing/explorer_fuzzer.full_corpus",
                 "-merge=1",
                 _FUZZER_CORPUS,
                 new_corpus_dir,