// Copyright 2022 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 THIRD_PARTY_CENTIPEDE_COVERAGE_H_ #define THIRD_PARTY_CENTIPEDE_COVERAGE_H_ #include #include #include #include #include #include #include #include "absl/base/thread_annotations.h" #include "absl/container/flat_hash_set.h" #include "absl/log/check.h" #include "absl/synchronization/mutex.h" #include "./centipede/control_flow.h" #include "./centipede/feature.h" #include "./centipede/pc_info.h" namespace fuzztest::internal { class SymbolTable; // To avoid mutual inclusion with symbol_table.h. // Reads and visualizes the code coverage produced by SanitizerCoverage. // https://clang.llvm.org/docs/SanitizerCoverage.html // // Thread-compatible. class Coverage { public: // PCTable is a property of the binary. // PCIndexVec is the coverage obtained from specific execution(s). Coverage(const PCTable &pc_table, const PCIndexVec &pci_vec); // Saves the by-function coverage in human-readable form to the `filepath` // file, using `symbols` to translate PCs to function names. void DumpReportToFile(const SymbolTable &symbols, std::string_view filepath, std::string_view description = ""); // Returns true if the function is fully covered. pc_index is for a function // entry. bool FunctionIsFullyCovered(PCIndex pc_index) const { CHECK(func_entries_[pc_index]); return fully_covered_funcs_vec_[pc_index]; } // Returns true if the given basic block is covered. pc_index is for any BB. bool BlockIsCovered(PCIndex pc_index) const { return covered_pcs_vec_[pc_index]; } private: // A vector of size PCTable. func_entries[idx] is true iff means the PC at idx // is a function entry. std::vector func_entries_; // Vector of fully covered functions i.e. functions with all edges covered. // A Function is represented by its entry block's PCIndex. // TODO(kcc): fix private variables' name to match the code style. PCIndexVec fully_covered_funcs; // A vector of size PCTable. fully_covered_funcs_vec[idx] is true iff the PC // at idx is an entry block of a fully covered function. std::vector fully_covered_funcs_vec_; // A vector of size PCTable. covered_pcs_vec[idx] is true iff the PC at idx is // covered. std::vector covered_pcs_vec_; // Same as `fully_covered_funcs`, but for functions with no edges covered. PCIndexVec uncovered_funcs; // Partially covered function: function with some, but not all, edges covered. // Thus we can represent it as two vectors of PCIndex: covered and uncovered. struct PartiallyCoveredFunction { PCIndexVec covered; // Non-empty, covered[0] is function entry. PCIndexVec uncovered; // Non-empty. }; std::vector partially_covered_funcs; }; // Iterates `pc_table`, calls `callback` on every pair {beg, end}, such that // pc_table[beg] is PCInfo::kFuncEntry, and pc_table[beg + 1 : end] are not. template void IteratePcTableFunctions(const PCTable &pc_table, Callback callback) { for (size_t beg = 0, n = pc_table.size(); beg < n;) { if (pc_table[beg].has_flag(PCInfo::kFuncEntry)) { size_t end = beg + 1; while (end < n && !pc_table[end].has_flag(PCInfo::kFuncEntry)) { ++end; } callback(beg, end); beg = end; } } } // CoverageLogger helps to log coverage locations once for each location. // CoverageLogger is thread-safe. class CoverageLogger { public: // CTOR. // Lifetimes of `pc_table` and `symbols` should be longer than for `this`. CoverageLogger(const PCTable &pc_table, const SymbolTable &symbols) : pc_table_(pc_table), symbols_(symbols) {} // Checks if `pc_index` or its symbolized description was observed before. // If yes, returns empty string. // If this is the first observation, returns a symbolized description. // If symbolization is not available, returns a non-symbolized description. std::string ObserveAndDescribeIfNew(PCIndex pc_index); private: const PCTable &pc_table_; const SymbolTable &symbols_; absl::Mutex mu_; absl::flat_hash_set observed_indices_ ABSL_GUARDED_BY(mu_); absl::flat_hash_set observed_descriptions_ ABSL_GUARDED_BY(mu_); }; // FunctionFilter maps a set of function names to a set of features. class FunctionFilter { public: // Initialize the filter. // `functions_to_filter` is a comma-separated list of function names. // If a function name is found in `symbols`, the PCs from that function // will be filtered. FunctionFilter(std::string_view functions_to_filter, const SymbolTable &symbols); // Returns true if // * some of the `features` are from feature_domains::kPC // and belong to a filtered function. // * either `functions_to_filter` or `symbols` passed to CTOR was empty. bool filter(const FeatureVec &features) const; // Counts PCs that belong to filtered functions. Test-only. size_t count() const { return std::count(pcs_.begin(), pcs_.end(), 1); } private: // pcs_[idx]==1 means that the PC at idx belongs to the filtered function. // We don't use vector for performance. // We don't use a hash set, because CPU is more important here than RAM. std::vector pcs_; }; // Computes the frontier weight. The weight is calculated based on the functions // called in the non-covered side of the frontier. For each such callee, the // cyclomatic complexity (CC) of the callee is multiplied by a factor (MF) // where MF is determined based on the coverage type of callee: // // frontier_weight = 0 // for f in callees_of_non_covered_successor_bb: // frontier_weight += CC(f) * MF(f) // // The breakdown for MF based on the coverage type of callee is as follows // (subject to change): // - Non-covered: %60 // - Partially-covered: %30 // - Fully-covered: %10 // Non-covered callee gets the highest MF as it is very interesting to // get it covered. That said, going to partially or even fully covered callee // still have some value as it may trigger new state there. uint32_t ComputeFrontierWeight(const Coverage &coverage, const ControlFlowGraph &cfg, const std::vector &callees); } // namespace fuzztest::internal #endif // THIRD_PARTY_CENTIPEDE_COVERAGE_H_