Add coverage symbolizer library #Centipede PiperOrigin-RevId: 561764679
diff --git a/centipede/BUILD b/centipede/BUILD index 52e507c..a955033 100644 --- a/centipede/BUILD +++ b/centipede/BUILD
@@ -82,6 +82,20 @@ # C++ libraries ################################################################################ +cc_library( + name = "coverage_symbolizer", + srcs = ["coverage_symbolizer.cc"], + hdrs = ["coverage_symbolizer.h"], + deps = [ + ":coverage", + ":feature", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + ], +) + # This lib must have zero non-trivial dependencies (other than libc). cc_library( name = "int_utils", @@ -1287,6 +1301,18 @@ ) cc_test( + name = "coverage_symbolizer_test", + srcs = ["coverage_symbolizer_test.cc"], + deps = [ + ":coverage_symbolizer", + ":feature", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( name = "runner_cmp_trace_test", srcs = ["runner_cmp_trace_test.cc"], deps = [
diff --git a/centipede/coverage_symbolizer.cc b/centipede/coverage_symbolizer.cc new file mode 100644 index 0000000..9bfc1d8 --- /dev/null +++ b/centipede/coverage_symbolizer.cc
@@ -0,0 +1,90 @@ +// Copyright 2023 The Centipede Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "./centipede/coverage_symbolizer.h" + +#include <stddef.h> + +#include <functional> +#include <string> + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "./centipede/feature.h" +#include "./centipede/symbol_table.h" + +namespace centipede { + +DomainSymbolizer::DomainSymbolizer(size_t domain_id) + : domain_id_(domain_id), initialized_(false) { + func_ = [domain_id](size_t idx) -> std::string { + return absl::StrFormat("unknown symbol: domain_id=%d, idx=%d", domain_id, + idx); + }; +} + +absl::StatusOr<SymbolTable *> +DomainSymbolizer::InitializeByPopulatingSymbolTable() { + if (initialized_) { + return absl::FailedPreconditionError(absl::StrCat( + "Already initialized this domain symbolizer for domain_id=", + domain_id_)); + } + initialized_ = true; + func_ = [this](size_t idx) -> std::string { + return symbols_.full_description(idx); + }; + return &symbols_; +} + +absl::Status DomainSymbolizer::InitializeWithSymbolizationFunction( + const std::function<std::string(size_t idx)> &func) { + if (initialized_) { + return absl::FailedPreconditionError(absl::StrCat( + "Already initialized this domain symbolizer for domain_id=", + domain_id_)); + } + initialized_ = true; + func_ = func; + return absl::OkStatus(); +} + +std::string DomainSymbolizer::GetSymbolForIndex(size_t idx) const { + return func_(idx); +} + +CoverageSymbolizer::CoverageSymbolizer() { + for (size_t i = 0; i < feature_domains::kLastDomain.domain_id(); ++i) { + symbolizers_.emplace_back(/*domain_id=*/i); + } +} + +absl::StatusOr<DomainSymbolizer *> CoverageSymbolizer::GetSymbolizerForDomain( + feature_domains::Domain domain) { + if (domain.domain_id() >= feature_domains::kLastDomain.domain_id()) { + return absl::InvalidArgumentError( + absl::StrCat("Provided invalid domain_id: ", domain.domain_id())); + } + return &symbolizers_[domain.domain_id()]; +} + +std::string CoverageSymbolizer::GetSymbolForFeature(feature_t feature) const { + size_t domain_id = feature_domains::Domain::FeatureToDomainId(feature); + size_t domain_index = feature_domains::Domain::FeatureToDomainIndex(feature); + return symbolizers_[domain_id].GetSymbolForIndex(domain_index); +} + +} // namespace centipede
diff --git a/centipede/coverage_symbolizer.h b/centipede/coverage_symbolizer.h new file mode 100644 index 0000000..b109322 --- /dev/null +++ b/centipede/coverage_symbolizer.h
@@ -0,0 +1,86 @@ +// Copyright 2023 The Centipede Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FUZZTEST_CENTIPEDE_COVERAGE_SYMBOLIZER_H_ +#define FUZZTEST_CENTIPEDE_COVERAGE_SYMBOLIZER_H_ + +#include <stddef.h> + +#include <functional> +#include <string> +#include <vector> + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "./centipede/feature.h" +#include "./centipede/symbol_table.h" + +namespace centipede { + +// Provides symbols for one type of coverage features in a domain. +// Note: Not thread-safe. +class DomainSymbolizer { + public: + // Instantiates a DomainSymbolizer for Domain with `domain_id`. + explicit DomainSymbolizer(size_t domain_id); + + // Returns a pointer to a symbol table that can be populated with entries for + // coverage features. + absl::StatusOr<SymbolTable *> InitializeByPopulatingSymbolTable(); + // Registers a function to be used for symbolizing coverage features. + // Given an index into the domain, the function should return the description + // of the feature at that index. + absl::Status InitializeWithSymbolizationFunction( + const std::function<std::string(size_t idx)> &func); + + // Returns a description of the feature at the provided index in the domain. + // If the symbolizer is uninitialized, returns an "unknown feature" message. + std::string GetSymbolForIndex(size_t idx) const; + + private: + // Holds symbols for coverage features. Unpopulated if initialized with + // symbolization function. + SymbolTable symbols_; + // Function that symbolizes the feature at the provided index `idx`. If + // initialized by populating `symbols_`, looks up the relevant symbol in + // `symbols_`. + std::function<std::string(size_t idx)> func_; + // Domain ID of the domain this object symbolizes. + size_t domain_id_; + // Ensures that we cannot be initialized more than once. + bool initialized_; +}; + +// Provides symbols for features in any domain. +// Note: Not thread-safe. +class CoverageSymbolizer { + public: + CoverageSymbolizer(); + + // Returns pointer to corresponding symbolizer for `domain`. + absl::StatusOr<DomainSymbolizer *> GetSymbolizerForDomain( + feature_domains::Domain domain); + + // Returns the symbol for `feature`. The symbol will be "unknown feature" for + // uninitialized domain symbolizers. + std::string GetSymbolForFeature(feature_t feature) const; + + private: + // Symbolizers for the valid domains. + std::vector<DomainSymbolizer> symbolizers_; +}; + +} // namespace centipede + +#endif // FUZZTEST_CENTIPEDE_COVERAGE_SYMBOLIZER_H_
diff --git a/centipede/coverage_symbolizer_test.cc b/centipede/coverage_symbolizer_test.cc new file mode 100644 index 0000000..ddcc307 --- /dev/null +++ b/centipede/coverage_symbolizer_test.cc
@@ -0,0 +1,139 @@ +// Copyright 2023 The Centipede Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "./centipede/coverage_symbolizer.h" + +#include <stddef.h> + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "absl/strings/str_cat.h" +#include "./centipede/feature.h" + +namespace centipede { +namespace { + +constexpr size_t kUnusedDomainId = 13; + +TEST(DomainSymbolizerTest, InitializeByPopulatingSymbolTable) { + DomainSymbolizer symbolizer(kUnusedDomainId); + ASSERT_OK_AND_ASSIGN(auto symbols, + symbolizer.InitializeByPopulatingSymbolTable()); + symbols->AddEntry("func_a", "file_line_col:1"); + symbols->AddEntry("func_b", "file_line_col:2"); + symbols->AddEntry("func_c", "file_line_col:3"); + + EXPECT_EQ(symbolizer.GetSymbolForIndex(0), "func_a file_line_col:1"); + EXPECT_EQ(symbolizer.GetSymbolForIndex(2), "func_c file_line_col:3"); + EXPECT_EQ(symbolizer.GetSymbolForIndex(1), "func_b file_line_col:2"); +} + +TEST(DomainSymbolizerTest, InitializeWithSymbolizationFunction) { + DomainSymbolizer symbolizer(kUnusedDomainId); + ASSERT_OK(symbolizer.InitializeWithSymbolizationFunction( + [](size_t idx) { return absl::StrCat(idx, "_llama"); })); + + EXPECT_EQ(symbolizer.GetSymbolForIndex(0), "0_llama"); + EXPECT_EQ(symbolizer.GetSymbolForIndex(2), "2_llama"); + EXPECT_EQ(symbolizer.GetSymbolForIndex(1), "1_llama"); +} + +TEST(DomainSymbolizerTest, CannotDoubleInitialize) { + DomainSymbolizer symbolizer1(kUnusedDomainId); + ASSERT_OK(symbolizer1.InitializeByPopulatingSymbolTable()); + EXPECT_THAT( + symbolizer1.InitializeWithSymbolizationFunction( + [](size_t idx) { return "never_used"; }), + ::testing::status::StatusIs(absl::StatusCode::kFailedPrecondition)); + + DomainSymbolizer symbolizer2(kUnusedDomainId); + ASSERT_OK(symbolizer2.InitializeWithSymbolizationFunction( + [](size_t idx) { return "never_used"; })); + EXPECT_THAT( + symbolizer2.InitializeByPopulatingSymbolTable(), + ::testing::status::StatusIs(absl::StatusCode::kFailedPrecondition)); + + DomainSymbolizer symbolizer3(kUnusedDomainId); + ASSERT_OK(symbolizer3.InitializeByPopulatingSymbolTable()); + EXPECT_THAT( + symbolizer3.InitializeByPopulatingSymbolTable(), + ::testing::status::StatusIs(absl::StatusCode::kFailedPrecondition)); +} + +TEST(DomainSymbolizerTest, UnknownSymbolIfUninitialized) { + DomainSymbolizer symbolizer(/*domain_id=*/12); + std::string expected_symbol = "unknown symbol: domain_id=12, idx=10006"; + EXPECT_EQ(symbolizer.GetSymbolForIndex(10006), expected_symbol); +} + +TEST(CoverageSymbolizerTest, GetSymbolizerForDomain) { + CoverageSymbolizer symbolizers; + ASSERT_OK_AND_ASSIGN(auto pc_symbolizer, symbolizers.GetSymbolizerForDomain( + feature_domains::kPCs)); + ASSERT_OK_AND_ASSIGN( + auto bounded_path_symbolizer, + symbolizers.GetSymbolizerForDomain(feature_domains::kBoundedPath)); + EXPECT_NE(pc_symbolizer, bounded_path_symbolizer); + + ASSERT_OK_AND_ASSIGN( + auto same_pc_symbolizer, + symbolizers.GetSymbolizerForDomain(feature_domains::kPCs)); + EXPECT_EQ(pc_symbolizer, same_pc_symbolizer); +} + +TEST(CoverageSymbolizerTest, CannotGetSymbolizerForInvalidDomain) { + CoverageSymbolizer symbolizers; + EXPECT_THAT(symbolizers.GetSymbolizerForDomain(feature_domains::kLastDomain), + ::testing::status::StatusIs(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(symbolizers.GetSymbolizerForDomain(feature_domains::Domain(777)), + ::testing::status::StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(CoverageSymbolizerTest, UnknownSymbolForUninitializedDomains) { + CoverageSymbolizer symbolizers; + feature_t feature_pc = feature_domains::kPCs.ConvertToMe(7); + std::string expected_pc_symbol = "unknown symbol: domain_id=1, idx=7"; + EXPECT_EQ(symbolizers.GetSymbolForFeature(feature_pc), expected_pc_symbol); + + feature_t feature_8bit = feature_domains::k8bitCounters.ConvertToMe(2); + std::string expected_8bit_symbol = "unknown symbol: domain_id=2, idx=2"; + EXPECT_EQ(symbolizers.GetSymbolForFeature(feature_8bit), + expected_8bit_symbol); +} + +TEST(CoverageSymbolizerTest, GetSymbolForFeature) { + CoverageSymbolizer symbolizers; + ASSERT_OK_AND_ASSIGN(auto pc_symbolizer, symbolizers.GetSymbolizerForDomain( + feature_domains::kPCs)); + ASSERT_OK(pc_symbolizer->InitializeWithSymbolizationFunction( + [](size_t idx) { return absl::StrCat("pc_", idx); })); + ASSERT_OK_AND_ASSIGN( + auto bounded_path_symbolizer, + symbolizers.GetSymbolizerForDomain(feature_domains::kBoundedPath)); + ASSERT_OK(bounded_path_symbolizer->InitializeWithSymbolizationFunction( + [](size_t idx) { return absl::StrCat("bounded_path_", idx); })); + + feature_t feature_pc = feature_domains::kPCs.ConvertToMe(7); + EXPECT_EQ(symbolizers.GetSymbolForFeature(feature_pc), "pc_7"); + + feature_t feature_bounded_path = feature_domains::kBoundedPath.ConvertToMe(7); + EXPECT_EQ(symbolizers.GetSymbolForFeature(feature_bounded_path), + "bounded_path_7"); +} + +} // namespace +} // namespace centipede
diff --git a/centipede/feature.h b/centipede/feature.h index 7c61dec..10072df 100644 --- a/centipede/feature.h +++ b/centipede/feature.h
@@ -94,6 +94,11 @@ return feature / kDomainSize; } + // Returns the index into the domain of a feature. + static size_t FeatureToDomainIndex(feature_t feature) { + return feature % kDomainSize; + } + private: const size_t domain_id_; };