Files
revng-revng/lib/HeadersGeneration/DependencyGraph.cpp
T
Pietro Fezzardi c613c61add HeadersGeneration: fix stack arguments dependency
Before this commit, the DependencyGraph used by HeadersGeneration to
represent dependencies between type declarations and definition did not
take into consideration RawFunctionType.StackArgumentsType properly.

This commit uses model::Type::edges() to inspect the dependencies of
the types, fixing this problem.
2022-04-08 17:58:06 +02:00

252 lines
9.7 KiB
C++

#include "llvm/ADT/Twine.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/DOTGraphTraits.h"
#include "llvm/Support/GraphWriter.h"
#include "revng/ADT/FilteredGraphTraits.h"
#include "revng/ADT/GenericGraph.h"
#include "revng/Model/Binary.h"
#include "revng/Model/Type.h"
#include "revng/Support/Assert.h"
#include "revng/Support/Debug.h"
#include "DependencyGraph.h"
static Logger<> Log{ "model-to-header-dependencies" };
using namespace llvm;
static llvm::StringRef toString(TypeNode::Kind K) {
switch (K) {
case TypeNode::Kind::TypeName:
return "TypeName";
case TypeNode::Kind::FullType:
return "FullType";
}
return "Invalid";
}
void DependencyGraph::addNode(const model::Type *T) {
constexpr auto TypeName = TypeNode::Kind::TypeName;
auto *NameNode = GenericGraph::addNode(TypeNode{ T, TypeName });
TypeToNode[TypeKindPair{ T, TypeName }] = NameNode;
constexpr auto FullType = TypeNode::Kind::FullType;
auto *FullNode = GenericGraph::addNode(TypeNode{ T, FullType });
TypeToNode[TypeKindPair{ T, FullType }] = FullNode;
}
std::string getNodeLabel(const TypeDependencyNode *N) {
return (Twine(getNameFromYAMLScalar(N->T->key())) + Twine("-")
+ Twine(toString(N->K)))
.str();
}
using DepNode = TypeDependencyNode;
using DepGraph = DependencyGraph;
std::string llvm::DOTGraphTraits<DepGraph *>::getNodeLabel(const DepNode *N,
const DepGraph *G) {
return ::getNodeLabel(N);
}
static TypeDependencyNode *
getDependencyForTypeName(const model::QualifiedType &QT,
const TypeToDependencyNodeMap &TypeToNode) {
const auto *Unqualified = QT.UnqualifiedType.get();
// If we find at least a pointer qualifier, then we only need the name of
// the unqualified type, not its full definition.
bool ArrayFound = false;
for (const auto &Qualifier : QT.Qualifiers) {
if (model::Qualifier::isPointer(Qualifier))
return TypeToNode.at({ Unqualified, TypeNode::Kind::TypeName });
if (model::Qualifier::isArray(Qualifier))
ArrayFound = true;
}
// If we reach this point we haven't found not even a single pointer
// qualifier.
// If we did find an array qualifier, we need the full type of the
// unqualified type.
if (ArrayFound)
return TypeToNode.at({ Unqualified, TypeNode::Kind::FullType });
// Otherwise we can get away with just the name of the unqualified type.
return TypeToNode.at({ Unqualified, TypeNode::Kind::TypeName });
}
static TypeDependencyNode *
getDependencyForFullType(const model::QualifiedType &QT,
const TypeToDependencyNodeMap &TypeToNode) {
const auto *Unqualified = QT.UnqualifiedType.get();
// If we find at least a pointer qualifier, then we only need the name of
// the unqualified type, not its full definition.
bool ArrayFound = false;
for (const auto &Qualifier : QT.Qualifiers) {
if (model::Qualifier::isPointer(Qualifier))
return TypeToNode.at({ Unqualified, TypeNode::TypeName });
if (model::Qualifier::isArray(Qualifier))
ArrayFound = true;
}
// If we reach this pointe we haven't found not even a single pointer
// qualifier. Given that we need the full definition, we need the full
// type of of the unqualified type.
return TypeToNode.at({ Unqualified, TypeNode::FullType });
}
static void registerDependencies(const model::Type *T,
const TypeToDependencyNodeMap &TypeToNode) {
using Edge = std::pair<TypeDependencyNode *, TypeDependencyNode *>;
llvm::SmallVector<Edge, 2> Deps;
switch (T->Kind) {
case model::TypeKind::Invalid: {
revng_abort("Primitive or Invalid type should never depend on others");
} break;
case model::TypeKind::PrimitiveType: {
// Nothing to do here. Primitive types names and full definitions can
// always be defined without dependencies, because they are either not
// necessary (for primitive types that are already present in stdint.h)
// or they boil down to a simple typedef of a type in stdint.h. In both
// cases, the definition provide visibility on both the name and on the
// full definition.
} break;
case model::TypeKind::EnumType: {
// Enum names and full definitions could always be conjured out of thin
// air. However, given that we have enums with underlying primitive
// types, for consistency we enforce that enums names and full
// definitions always depend on full definition of the underlying
// primitive type. This adds a little unnessary edges, but makes the
// overall structure of the graph easier to reason about. Moreover, full
// definitions of primitive types can also always be conjured out of
// thin air, so we're always sure that this does not generates infinite
// loops.
const auto *E = cast<model::EnumType>(T);
const model::QualifiedType &UnderlyingQT = E->UnderlyingType;
revng_assert(T->edges().size() == 1 and UnderlyingQT == *T->edges().begin()
and UnderlyingQT.Qualifiers.empty());
auto *U = cast<model::PrimitiveType>(UnderlyingQT.UnqualifiedType.get());
auto *EnumName = TypeToNode.at({ E, TypeNode::Kind::TypeName });
auto *EnumFull = TypeToNode.at({ E, TypeNode::Kind::FullType });
auto *UnderFull = TypeToNode.at({ U, TypeNode::Kind::FullType });
Deps.push_back({ EnumName, UnderFull });
Deps.push_back({ EnumFull, UnderFull });
revng_log(Log,
getNodeLabel(EnumName)
<< " depends on " << getNodeLabel(UnderFull));
revng_log(Log,
getNodeLabel(EnumFull)
<< " depends on " << getNodeLabel(UnderFull));
} break;
case model::TypeKind::StructType:
case model::TypeKind::UnionType: {
// Struct and Union names can always be conjured out of thin air thanks to
// typedefs. So we only need to add dependencies between their full
// definition and the full definition of their fields.
auto *Full = TypeToNode.at({ T, TypeNode::Kind::FullType });
for (const model::QualifiedType &QT : T->edges()) {
TypeDependencyNode *Dep = getDependencyForFullType(QT, TypeToNode);
Deps.push_back({ Full, Dep });
revng_log(Log, getNodeLabel(Full) << " depends on " << getNodeLabel(Dep));
}
} break;
case model::TypeKind::TypedefType: {
// Typedefs are nasty.
auto *TD = cast<model::TypedefType>(T);
const model::QualifiedType &Underlying = TD->UnderlyingType;
auto *TDName = TypeToNode.at({ TD, TypeNode::Kind::TypeName });
TypeDependencyNode *NameDep = getDependencyForTypeName(Underlying,
TypeToNode);
Deps.push_back({ TDName, NameDep });
revng_log(Log,
getNodeLabel(TDName) << " depends on " << getNodeLabel(NameDep));
auto *TDFull = TypeToNode.at({ TD, TypeNode::Kind::FullType });
TypeDependencyNode *FullDep = getDependencyForFullType(Underlying,
TypeToNode);
Deps.push_back({ TDFull, FullDep });
revng_log(Log,
getNodeLabel(TDFull) << " depends on " << getNodeLabel(FullDep));
} break;
case model::TypeKind::CABIFunctionType:
case model::TypeKind::RawFunctionType: {
// For function types we can print a valid typedef definition as long as
// we have visibility on all the names of all the argument types and all
// return types.
auto *FullNode = TypeToNode.at({ T, TypeNode::Kind::FullType });
auto *NameNode = TypeToNode.at({ T, TypeNode::Kind::TypeName });
for (const model::QualifiedType &QT : T->edges()) {
// The two dependencies added here below are actually stricter than
// necessary for e.g. stack arguments.
// The reason is that, on the model, stack arguments are represented by
// value, but in some cases they are actually passed by pointer in C.
// Given that with the edges() accessor here we cannot discriminate, we
// decided to err on the strict side.
// This could potentially create graphs with loops of dependencies, or
// make some instances not solvable, that would have otherwise been valid.
// This should only happen in nasty cases involving loops of function
// pointers, but possibly other cases we haven't considered.
// Overall, these remote cases have never showed up until now.
// If this ever happen, we'll need to fix this properly, either relaxing
// this dependencies, or pre-processing the model so that what reaches
// this point is always guaranteed to be in a form that can be emitted.
TypeDependencyNode *FullDep = getDependencyForFullType(QT, TypeToNode);
Deps.push_back({ FullNode, FullDep });
TypeDependencyNode *NameDep = getDependencyForTypeName(QT, TypeToNode);
Deps.push_back({ NameNode, NameDep });
revng_log(Log,
getNodeLabel(FullNode)
<< " depends on " << getNodeLabel(FullDep));
revng_log(Log,
getNodeLabel(NameNode)
<< " depends on " << getNodeLabel(NameDep));
}
} break;
default:
revng_abort();
}
for (const auto &[From, To] : Deps) {
revng_log(Log,
"Adding edge " << getNodeLabel(From) << " --> "
<< getNodeLabel(To));
From->addSuccessor(To);
}
}
DependencyGraph buildDependencyGraph(const TypeVector &Types) {
DependencyGraph Dependencies;
// Create nodes
for (const UpcastablePointer<model::Type> &MT : Types)
Dependencies.addNode(MT.get());
// Compute dependencies and add them to the graph
for (const UpcastablePointer<model::Type> &MT : Types)
registerDependencies(MT.get(), Dependencies.TypeNodes());
if (Log.isEnabled())
llvm::ViewGraph(&Dependencies, "type-deps.dot");
return Dependencies;
}