| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| |
| #include <fuzzer/FuzzedDataProvider.h> |
| |
| #include "base/files/file_util.h" |
| #include "base/path_service.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "components/browsing_topics/annotator_impl.h" |
| #include "components/optimization_guide/core/optimization_guide_features.h" |
| #include "components/optimization_guide/core/optimization_guide_model_provider.h" |
| #include "components/optimization_guide/core/test_model_info_builder.h" |
| #include "components/optimization_guide/core/test_optimization_guide_model_provider.h" |
| #include "components/optimization_guide/proto/models.pb.h" |
| #include "components/optimization_guide/proto/page_topics_model_metadata.pb.h" |
| |
| // Sends a fully working model and configuration to the calling observer. |
| class ModelProvider |
| : public optimization_guide::TestOptimizationGuideModelProvider { |
| public: |
| ModelProvider() = default; |
| ~ModelProvider() override = default; |
| |
| // optimization_guide::TestOptimizationGuideModelProvider: |
| void AddObserverForOptimizationTargetModel( |
| optimization_guide::proto::OptimizationTarget optimization_target, |
| const std::optional<optimization_guide::proto::Any>& model_metadata, |
| optimization_guide::OptimizationTargetModelObserver* observer) override { |
| optimization_guide::proto::Any any_metadata; |
| any_metadata.set_type_url( |
| "type.googleapis.com/com.foo.PageTopicsModelMetadata"); |
| optimization_guide::proto::PageTopicsModelMetadata |
| page_topics_model_metadata; |
| page_topics_model_metadata.set_version(123); |
| page_topics_model_metadata.add_supported_output( |
| optimization_guide::proto::PAGE_TOPICS_SUPPORTED_OUTPUT_CATEGORIES); |
| auto* output_params = |
| page_topics_model_metadata.mutable_output_postprocessing_params(); |
| auto* category_params = output_params->mutable_category_params(); |
| category_params->set_max_categories(5); |
| category_params->set_min_none_weight(0.8); |
| category_params->set_min_category_weight(0.1); |
| category_params->set_min_normalized_weight_within_top_n(0.1); |
| page_topics_model_metadata.SerializeToString(any_metadata.mutable_value()); |
| |
| base::FilePath source_root_dir; |
| base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &source_root_dir); |
| base::FilePath model_file_path = |
| source_root_dir.AppendASCII("components") |
| .AppendASCII("test") |
| .AppendASCII("data") |
| .AppendASCII("browsing_topics") |
| .AppendASCII("golden_data_model.tflite"); |
| std::unique_ptr<optimization_guide::ModelInfo> model_info = |
| optimization_guide::TestModelInfoBuilder() |
| .SetModelFilePath(model_file_path) |
| .SetModelMetadata(any_metadata) |
| .Build(); |
| |
| observer->OnModelUpdated( |
| optimization_guide::proto::OPTIMIZATION_TARGET_PAGE_TOPICS_V2, |
| *model_info); |
| } |
| }; |
| |
| // An AnnotatorImpl that never unloads the model, thus keeping the executions |
| // per second high. |
| class TestAnnotator : public browsing_topics::AnnotatorImpl { |
| public: |
| TestAnnotator( |
| optimization_guide::OptimizationGuideModelProvider* model_provider, |
| scoped_refptr<base::SequencedTaskRunner> background_task_runner, |
| const std::optional<optimization_guide::proto::Any>& model_metadata) |
| : browsing_topics::AnnotatorImpl(model_provider, |
| std::move(background_task_runner), |
| model_metadata) {} |
| |
| protected: |
| // AnnotatorImpl: |
| void UnloadModel() override { |
| // Do nothing so that the model stays loaded. |
| } |
| }; |
| |
| // Static test fixture to maintain the state needed to run repeated fuzz tests. |
| class AnnotatorFuzzerTest { |
| public: |
| explicit AnnotatorFuzzerTest() |
| : annotator_(std::make_unique<TestAnnotator>( |
| &model_provider_, |
| task_environment_.GetMainThreadTaskRunner(), |
| std::nullopt)) { |
| scoped_feature_list_.InitAndDisableFeature( |
| optimization_guide::features::kPreventLongRunningPredictionModels); |
| } |
| ~AnnotatorFuzzerTest() { |
| annotator_.reset(); |
| // To prevent ASAN issues, ensure the ModelExecutor gets destroyed since |
| // that is done using a DeleteSoon PostTask. |
| task_environment_.RunUntilIdle(); |
| } |
| |
| browsing_topics::Annotator* annotator() { return annotator_.get(); } |
| |
| void RunUntilIdle() { task_environment_.RunUntilIdle(); } |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| ModelProvider model_provider_; |
| std::unique_ptr<browsing_topics::AnnotatorImpl> annotator_; |
| }; |
| |
| extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
| static AnnotatorFuzzerTest test; |
| static bool had_successful_run = false; |
| |
| std::string input(reinterpret_cast<const char*>(data), size); |
| |
| base::RunLoop run_loop; |
| test.annotator()->BatchAnnotate( |
| base::BindOnce( |
| [](base::RunLoop* run_loop, |
| const std::vector<browsing_topics::Annotation>& annotations) { |
| if (!annotations[0].topics.empty() && !had_successful_run) { |
| had_successful_run = true; |
| // Print a single debug message so that its obvious things are |
| // working (or able to at least once) when running locally. |
| LOG(INFO) << "Congrats! Got a successful model execution. This " |
| "message will not be printed again."; |
| } |
| |
| run_loop->Quit(); |
| }, |
| &run_loop), |
| {input}); |
| run_loop.Run(); |
| |
| // The model executor does some PostTask'ing to manage its state. While these |
| // tasks are not important for fuzzing, we don't want to queue up a ton of |
| // them. |
| test.RunUntilIdle(); |
| |
| return 0; |
| } |