mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:20:13 +01:00
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).
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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";
|
||||
|
||||
if (!compilation_unit.declarations().empty()) {
|
||||
out << "\n";
|
||||
for (const auto& declaration : compilation_unit.declarations()) {
|
||||
DeclarationToCarbon(declaration, out);
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto ProtoToCarbon(const Fuzzing::CompilationUnit& compilation_unit)
|
||||
auto ProtoToCarbon(const Fuzzing::Carbon& proto, bool maybe_add_main)
|
||||
-> std::string {
|
||||
std::string source;
|
||||
llvm::raw_string_ostream out(source);
|
||||
ProtoToCarbon(compilation_unit, out);
|
||||
|
||||
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";
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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",
|
||||
|
||||
+33
-49
@@ -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.
|
||||
[carbon.proto](/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).
|
||||
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. Make sure `ast_to_proto_test` passes with the new changes.
|
||||
3. Add logic to populate the proto to
|
||||
[ast_to_proto.cpp](/explorer/fuzzing/ast_to_proto.cpp).
|
||||
|
||||
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.
|
||||
|
||||
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 build --config=fuzzer //explorer/fuzzing:explorer_fuzzer.full_corpus
|
||||
|
||||
bazel-bin/explorer/fuzzing/explorer_fuzzer
|
||||
bazel-bin/explorer/fuzzing/explorer_fuzzer.full_corpus
|
||||
```
|
||||
|
||||
## Investigating a crash
|
||||
|
||||
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
|
||||
|
||||
# Run explorer on the crash.
|
||||
bazel run //explorer -- crash.carbon
|
||||
```
|
||||
|
||||
It's also possible to run the fuzzer on a single input:
|
||||
|
||||
```bash
|
||||
bazel-bin/explorer/fuzzing/explorer_fuzzer /tmp/crash.textproto
|
||||
```
|
||||
|
||||
## Investigating a crash
|
||||
|
||||
To reproduce a crash, run the fuzzer on the crashing input as described above.
|
||||
|
||||
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.
|
||||
|
||||
To convert a `Fuzzing::Carbon` text proto to Carbon source:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user