| // 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_CONTROL_FLOW_H_ | |
| #define THIRD_PARTY_CENTIPEDE_CONTROL_FLOW_H_ | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <istream> | |
| #include <mutex> //NOLINT | |
| #include <ostream> | |
| #include <string_view> | |
| #include <vector> | |
| #include "absl/container/flat_hash_map.h" | |
| #include "absl/container/flat_hash_set.h" | |
| #include "./centipede/pc_info.h" | |
| #include "./common/defs.h" | |
| #include "./common/logging.h" | |
| namespace fuzztest::internal { | |
| class SymbolTable; // To avoid mutual inclusion with symbol_table.h. | |
| // Reads a PCTable from `file_path`, returns it. Returns empty table on error. | |
| PCTable ReadPcTableFromFile(std::string_view file_path); | |
| // Helper for GetPcTableFromBinary, for binaries built with | |
| // -fsanitize-coverage=trace-pc. Returns the PCTable reconstructed from | |
| // `binary_path` with `<objdump_path> -d`. May create a file `tmp_path`, but | |
| // will delete it afterwards. | |
| PCTable GetPcTableFromBinaryWithTracePC(std::string_view binary_path, | |
| std::string_view objdump_path, | |
| std::string_view tmp_path); | |
| // PCIndex: an index into the PCTable. | |
| // We use 32-bit int for compactness since PCTable is never too large. | |
| using PCIndex = uint32_t; | |
| // A set of PCIndex-es, order is not important. | |
| using PCIndexVec = std::vector<PCIndex>; | |
| // Array of elements in __sancov_cfs section. | |
| // CFTable is created by the compiler/linker in the instrumented binary. | |
| // https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-control-flow. | |
| using CFTable = std::vector<intptr_t>; | |
| // Reads a CFTable from `file_path`, returns it. Returns empty table on error. | |
| CFTable ReadCfTable(std::string_view file_path); | |
| // Same as above but reads from a stream. | |
| CFTable ReadCfTable(std::istream &in); | |
| // Writes the `cf_table` to `out`. | |
| void WriteCfTable(const CFTable &cf_table, std::ostream &out); | |
| // Reads a DsoTable from `file_path`, returns it. Returns empty table on error. | |
| DsoTable ReadDsoTableFromFile(std::string_view file_path); | |
| class ControlFlowGraph { | |
| public: | |
| // Reads form __sancov_cfs section. On error it crashes, if the section is not | |
| // there, the graph_ will be empty. | |
| void InitializeControlFlowGraph(const CFTable &cf_table, | |
| const PCTable &pc_table); | |
| // Returns the vector of successor PCs for the given basic block PC. | |
| const std::vector<uintptr_t> &GetSuccessors(uintptr_t basic_block) const; | |
| // Returns the number of cfg entries. | |
| size_t size() const { return graph_.size(); } | |
| // Checks if basic_block is in cfg. | |
| bool exists(const uintptr_t basic_block) const { | |
| return graph_.contains(basic_block); | |
| } | |
| // Returns cyclomatic complexity of function PC. FUZZTEST_CHECK-fails if it is | |
| // not a valid function PC. | |
| uint32_t GetCyclomaticComplexity(uintptr_t pc) const { | |
| auto it = function_complexities_.find(pc); | |
| FUZZTEST_CHECK(it != function_complexities_.end()); | |
| return it->second; | |
| } | |
| // Returns true if the given basic block is function entry. | |
| bool BlockIsFunctionEntry(PCIndex pc_index) const { | |
| // TODO(ussuri): Change the following to use FUZZTEST_CHECK_LE(pc_index, | |
| // func_entries_.size()) and have a death test. | |
| return pc_index < func_entries_.size() ? func_entries_[pc_index] : false; | |
| } | |
| // Returns the idx in pc_table associated with the PC, FUZZTEST_CHECK-fails if | |
| // the PC is not in the pc_table. | |
| PCIndex GetPcIndex(uintptr_t pc) const { | |
| auto it = pc_index_map_.find(pc); | |
| FUZZTEST_CHECK(it != pc_index_map_.end()) | |
| << VV(pc) << " is not in pc_table."; | |
| return it->second; | |
| } | |
| // Returns true if the PC is in PCTable. | |
| bool IsInPcTable(uintptr_t pc) const { return pc_index_map_.contains(pc); } | |
| // Returns a vector& containing all basic blocks (represented by their PCs) | |
| // reachable from `pc`. The reachability is computed once, lazily. | |
| // The method is const, under the hood it uses a mutable data member. | |
| // Thread-safe: can be called concurrently from multiple threads | |
| const std::vector<uintptr_t> &LazyGetReachabilityForPc(uintptr_t pc) const { | |
| FUZZTEST_CHECK_EQ(reachability_.size(), pc_index_map_.size()); | |
| auto pc_index = GetPcIndex(pc); | |
| std::call_once(*(reachability_[pc_index].once), [this, &pc, &pc_index]() { | |
| reachability_[pc_index].reach = ComputeReachabilityForPc(pc); | |
| }); | |
| return reachability_[pc_index].reach; | |
| } | |
| private: | |
| // Map from PC to the idx in pc_table. | |
| absl::flat_hash_map<uintptr_t, PCIndex> pc_index_map_; | |
| // A vector of size PCTable. func_entries[idx] is true iff means the PC at idx | |
| // is a function entry. | |
| std::vector<bool> func_entries_; | |
| // A map with PC as the keys and vector of PCs as value. | |
| absl::flat_hash_map<uintptr_t, std::vector<uintptr_t>> graph_; | |
| // A map from function PC to its calculated cyclomatic complexity. It is | |
| // to avoid unnecessary calls to ComputeFunctionCyclomaticComplexity. | |
| absl::flat_hash_map<uintptr_t, uint32_t> function_complexities_; | |
| // Returns a vector of PCs reachable from `pc`, not in any particular order. | |
| // The result always includes `pc`, since any block is reachable from itself. | |
| std::vector<uintptr_t> ComputeReachabilityForPc(uintptr_t pc) const; | |
| FRIEND_TEST(ControlFlowGraph, ComputeReachabilityForPc); | |
| // ReachInfo is a struct to store reachability information for each PC in | |
| // pc_table. The once flag is used to make sure the reach vector is populated | |
| // only once lazily in a thread-friendly manner. | |
| struct ReachInfo { | |
| mutable std::once_flag *once; | |
| mutable std::vector<uintptr_t> reach; | |
| ReachInfo() : once(new std::once_flag) {} | |
| ~ReachInfo() { delete once; } | |
| }; | |
| // A vector of size PCTable. reachability_[idx] is reachability info for the | |
| // `idx`th pc. Conceptually it is constant, but we compute it lazily, hence | |
| // 'mutable' | |
| std::vector<ReachInfo> reachability_; | |
| }; | |
| // Computes the Cyclomatic Complexity for the given function, | |
| // https://en.wikipedia.org/wiki/Cyclomatic_complexity. | |
| uint32_t ComputeFunctionCyclomaticComplexity(uintptr_t pc, | |
| const ControlFlowGraph &cfg); | |
| } // namespace fuzztest::internal | |
| #endif // THIRD_PARTY_CENTIPEDE_CONTROL_FLOW_H_ |