blob: 2feb9faf179e64bfdd559f09567db6613a486c1d [file] [log] [blame]
// 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_