Initialize FuzzTest for all Chromium tests.
FUZZ_TEST is a new type of test which is half way between a unit test
and a fuzzer (the same code can run in two modes).
Eventually, we hope to allow their free inclusion in existing Chromium
unit test targets (though this is still not encouraged by Chromium's
fuzzing documentation as there are a number of things we need to resolve
first).
This extra call in base::TestSuite sets up FuzzTest ready for this.
base/test:test_support is included in all sorts of test targets, not
just test suites. And, of the test suites where it ends up being
included, some of them will have fuzztests and some of them won't. This
CL is heavily structured such that the new code impacts only test suites
which have fuzztests.
Specifically,
* base/test:test_support ends up being included in various pre-existing
fuzzers. This is a problem because those fuzzers might
have implementations of core fuzzing functions such as
LLVMFuzzerCustomMutator, which would conflict with the fuzztest
implementation. We therefore take pains not to include
src/fuzztest/internal/compatibility_mode.cc except if the test suite
really does have fuzztests.
* base/test:test_support is sometimes linked from targets that include
protobuf_full (notably some GPU test targets). fuzztest depends on
protobuf_lite. On component builds this causes a conflict - a build
failure on Windows and ODR violations on other platforms. We therefore
need to avoid including fuzztests' dependencies into
base/test:test_support unless there really are fuzztests in the test
suite.
Fortunately, testing/test.gni already knows whether there are fuzztests
in a given test suite, as it needs to generate wrapper executables for
each fuzztest.
We therefore use dependency injection, where base/test:test_support calls
a MaybeInitFuzztest function. In test suites containing real fuzztests,
a function pointer will have been populated with a real function that
can initialize fuzztests.
A couple of other details:
* Fuzztest takes the liberty of looking at its command line arguments
"later". Chromium turns out to modify its command line, and thus the
fuzztest command line would become invalid. We store a copy to avoid
this problem.
* When test suites are running in fuzzing mode, we avoid going into
multiple process test mode.
Cq-Include-Trybots: luci.chromium.try:linux-libfuzzer-asan-rel,win-libfuzzer-asan-rel,linux-centipede-asan-rel
Bug: 40286211
Change-Id: I9456ffd6b273c0e32fd88abd8547bf7f9cad96a3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4981917
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Adrian Taylor <adetaylor@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1311096}
NOKEYCHECK=True
GitOrigin-RevId: 6337f56cb77cf03a0884b184ac9885c3658a8758
diff --git a/BUILD.gn b/BUILD.gn
index 5494373..6fd8e0c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -211,6 +211,8 @@
}
}
+# Parts of fuzztest internals which are safe to include in all sorts
+# of test-related code, including other fuzzers.
source_set("fuzztest_internal") {
sources = [
"src/fuzztest/domain.h",
@@ -221,7 +223,6 @@
"src/fuzztest/googletest_fixture_adapter.h",
"src/fuzztest/internal/any.h",
"src/fuzztest/internal/centipede_adaptor.h",
- "src/fuzztest/internal/compatibility_mode.cc",
"src/fuzztest/internal/compatibility_mode.h",
"src/fuzztest/internal/configuration.cc",
"src/fuzztest/internal/configuration.h",
@@ -331,10 +332,6 @@
# will depend upon :is_a_fuzz_target). Such executables will have a data_dep
# on the underlying fuzztest executable, so it will get built.
source_set("fuzztest") {
- sources = [
- "src/fuzztest/init_fuzztest.cc",
- "src/fuzztest/init_fuzztest.h",
- ]
deps = [
"//testing/gtest",
"//third_party/abseil-cpp:absl_full",
@@ -342,13 +339,14 @@
if (use_centipede) {
# If we are building for centipede, we want to make fuzztest executables
# which can be used as centipede fuzzers.
- sources += [ "src/fuzztest/internal/centipede_adaptor.cc" ]
+ sources = [ "src/fuzztest/internal/centipede_adaptor.cc" ]
deps += [
":centipede_executable_engine",
":centipede_runner_no_main",
]
} else if (use_fuzzing_engine && fuzzing_engine_supports_custom_main) {
# Typically, libfuzzer.
+ sources = [ "src/fuzztest/internal/compatibility_mode.cc" ]
deps += [ "//testing/libfuzzer:fuzzing_engine_no_main_core" ]
}
public_deps = [ ":fuzztest_internal" ]
@@ -357,6 +355,27 @@
testonly = true
}
+# Depend on this if you're a test runner executable (or similar)
+# with its own main() but you need to call InitFuzzTest.
+source_set("init_fuzztest") {
+ sources = [
+ "confirm_init.cc",
+ "src/fuzztest/init_fuzztest.cc",
+ "src/fuzztest/init_fuzztest.h",
+ ]
+ deps = [
+ "//testing/gtest",
+ "//third_party/abseil-cpp:absl_full",
+ ]
+ public_deps = [
+ ":fuzztest_internal",
+ ":init_helper",
+ ]
+ configs -= fuzztest_remove_configs
+ configs += fuzztest_add_configs + [ ":fuzztest_internal_config" ]
+ testonly = true
+}
+
# Depend upon this if you need a main() function
source_set("fuzztest_gtest_main") {
deps = [
@@ -365,7 +384,7 @@
]
sources = [ "src/fuzztest/fuzztest_gtest_main.cc" ]
public_deps = [
- ":fuzztest",
+ ":init_fuzztest",
"//testing/gtest",
]
if (use_centipede) {
@@ -373,3 +392,14 @@
}
testonly = true
}
+
+# A way to potentially init FuzzTest without pulling in all of its deps.
+# For use from generic code such as base/test:test_support.
+# If it turns out the target actually does contain fuzztests, also
+# depend upon :init_fuzztest and this code will end up initializing FuzzTest.
+source_set("init_helper") {
+ sources = [
+ "init_helper.cc",
+ "init_helper.h",
+ ]
+}
diff --git a/confirm_init.cc b/confirm_init.cc
new file mode 100644
index 0000000..c5f7e4b
--- /dev/null
+++ b/confirm_init.cc
@@ -0,0 +1,25 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/fuzztest/init_helper.h"
+#include "third_party/fuzztest/src/fuzztest/init_fuzztest.h"
+
+static void RealInitFunction(int* argc, char*** argv) {
+ fuzztest::ParseAbslFlags(*argc, *argv);
+ fuzztest::InitFuzzTest(argc, argv);
+}
+
+// base/test:test_support is used in test suites containing fuzztests
+// and those without. In those without, we want to avoid depending
+// on fuzztest's complex dependencies, but on those with fuzztests
+// we need to call InitFuzzTest. So, use a static initializer to fill
+// in a function pointer in those cases.
+class FuzztestInitializer {
+ public:
+ FuzztestInitializer() {
+ fuzztest_init_helper::initialization_function = RealInitFunction;
+ }
+};
+
+FuzztestInitializer static_initializer;
diff --git a/init_helper.cc b/init_helper.cc
new file mode 100644
index 0000000..da8a92d
--- /dev/null
+++ b/init_helper.cc
@@ -0,0 +1,7 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/fuzztest/init_helper.h"
+
+void (*fuzztest_init_helper::initialization_function)(int* argc, char*** argv);
diff --git a/init_helper.h b/init_helper.h
new file mode 100644
index 0000000..2fe93d8
--- /dev/null
+++ b/init_helper.h
@@ -0,0 +1,25 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_FUZZTEST_INIT_HELPER_H_
+#define THIRD_PARTY_FUZZTEST_INIT_HELPER_H_
+
+namespace fuzztest_init_helper {
+
+extern void (*initialization_function)(int* argc, char*** argv);
+
+}
+
+// If we're in a test suite which really has fuzztests,
+// the above function pointer will have been populated with
+// a function that knows how to initialize FuzzTests. Otherwise,
+// it won't, to avoid bringing all of FuzzTests's dependencies
+// into all the other Chromium test suites.
+inline void MaybeInitFuzztest(int* argc, char*** argv) {
+ if (fuzztest_init_helper::initialization_function) {
+ fuzztest_init_helper::initialization_function(argc, argv);
+ }
+}
+
+#endif // THIRD_PARTY_FUZZTEST_INIT_HELPER_H_