// // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Casting.h" #include "revng/Model/Binary.h" #include "revng/Model/CABIFunctionDefinition.h" #include "revng/Model/RawFunctionDefinition.h" #include "revng/Model/StructDefinition.h" #include "revng/Model/TypeDefinitionKind.h" #include "revng/Model/TypeSystemPrinter.h" #include "revng/Model/TypedefDefinition.h" #include "revng/Model/UnionDefinition.h" #include "revng/Support/Assert.h" using llvm::cast; using llvm::dyn_cast; using llvm::isa; using model::CABIFunctionDefinition; using model::RawFunctionDefinition; using model::StructDefinition; using model::TypedefDefinition; using model::UnionDefinition; using std::to_string; using FieldList = llvm::SmallVector; static constexpr const char *TableOpts = "border='0' cellborder='1' " "cellspacing='0' cellpadding='0'"; static constexpr const char *PaddingOpts = "cellpadding='10'"; static constexpr const char *Green = "\"#8DB596\""; static constexpr const char *Red = "\"#DC7878\""; static constexpr const char *Blue = "\"#93ABD3\""; static constexpr const char *Orange = "\"#EEEE00\""; static constexpr const char *Purple = "\"#C689C6\""; static constexpr const char *Pink = "\"#FF99CC\""; static constexpr const char *Grey = "\"#CCCCCC\""; static constexpr const char *White = "\"white\""; /// Background and border color for records of a given TypeDefinitionKind static llvm::StringRef getColor(model::TypeDefinitionKind::Values K) { if (K == model::TypeDefinitionKind::UnionDefinition) return Red; else if (K == model::TypeDefinitionKind::CABIFunctionDefinition or K == model::TypeDefinitionKind::RawFunctionDefinition) return Green; else if (K == model::TypeDefinitionKind::StructDefinition) return Blue; return Grey; } /// Cell with inner padding, a colored background and a white border static void headerCell(llvm::raw_ostream &Out, llvm::StringRef Color, llvm::StringRef Content) { Out << "" << Content << ""; } /// Cell with inner padding and possibly a port identifier static void paddedCell(llvm::raw_ostream &Out, llvm::StringRef Content, std::optional Port = {}) { Out << "" << Content << ""; } /// Collect an ordered list of the subtypes in a type (e.g. field, return /// values, arguments ...) static FieldList collectFields(const model::TypeDefinition *T) { FieldList Fields; if (auto *Struct = llvm::dyn_cast(T)) { for (auto &Field : Struct->Fields()) Fields.push_back(Field.Type().get()); } else if (auto *Union = llvm::dyn_cast(T)) { for (auto &Field : Union->Fields()) Fields.push_back(Field.Type().get()); } else if (auto *CABI = llvm::dyn_cast(T)) { if (not CABI->ReturnType().isEmpty()) Fields.push_back(CABI->ReturnType().get()); for (auto &Field : CABI->Arguments()) Fields.push_back(Field.Type().get()); } else if (auto *RawFunc = llvm::dyn_cast(T)) { for (auto &Field : RawFunc->ReturnValues()) Fields.push_back(Field.Type().get()); for (auto &Field : RawFunc->Arguments()) Fields.push_back(Field.Type().get()); if (not RawFunc->StackArgumentsType().isEmpty()) Fields.push_back(RawFunc->StackArgumentsType().get()); } else if (auto *Typedef = llvm::dyn_cast(T)) { Fields.push_back(Typedef->UnderlyingType().get()); } return Fields; } TypeSystemPrinter::TypeSystemPrinter(llvm::raw_ostream &Out, const model::Binary &Binary, bool OrthoEdges) : Out(Out), Binary(Binary), NameBuilder(Binary) { Out << "digraph TypeGraph {\n"; if (OrthoEdges) Out << "splines=ortho;\n"; // The outedges of a node, that is, edges with the node as its tail node, must // appear left-to-right in the same order in which they are defined in the // input. Out << "ordering=out;\n"; // the inedges of a node must appear left-to-right in the same order in // which they are defined in the input. Out << "ordering=in;\n"; Out << "node [shape=none, margin=0];\n"; Out << "graph [fontname=Courier];\n"; Out << "node [fontname=Courier];\n"; Out << "edge [fontname=Courier];\n"; } TypeSystemPrinter::~TypeSystemPrinter() { Out << "}\n"; Out.flush(); } /// Build a C-like string for a given Type // TODO: replace this with a call to `getNamedCInstance` once the two repos // are merged together. std::string TypeSystemPrinter::buildFieldName(const model::Type &Type, std::string &&Prefix, std::string &&Suffix) { if (const auto *Array = llvm::dyn_cast(&Type)) { revng_assert(!Array->IsConst(), "Const arrays are not supported by this serializer."); Suffix = "[" + std::to_string(Array->ElementCount()) + "]" + std::move(Suffix); return buildFieldName(*Array->ElementType(), std::move(Prefix), std::move(Suffix)); } else if (const auto *D = llvm::dyn_cast(&Type)) { std::string Result = std::move(Prefix); if (!Result.empty() && Result.back() != '*') Result += ' '; return Result += NameBuilder.name(D->unwrap()) + Suffix; } else if (const auto *P = llvm::dyn_cast(&Type)) { return buildFieldName(*P->PointeeType(), std::move(Prefix += P->IsConst() ? "* const" : "*"), std::move(Suffix)); } else if (const auto *P = llvm::dyn_cast(&Type)) { std::string Result = std::move(Prefix); if (!Result.empty() && Result.at(Result.size() - 1) != '*') Result += ' '; return Result += P->getCName() + Suffix; } else { revng_abort("Unsupported type."); } } /// Add a row in a struct table static void addStructField(llvm::raw_ostream &Out, size_t Offset, size_t Size, llvm::StringRef Content, std::optional Port = {}) { Out << ""; paddedCell(Out, to_string(Offset)); paddedCell(Out, to_string(Size)); paddedCell(Out, Content, Port); Out << ""; } /// Generate the inner table of a struct type void TypeSystemPrinter::dumpStructFields(llvm::raw_ostream &Out, const model::StructDefinition *T) { if (T->Fields().size() == 0) { Out << ""; return; } // Header llvm::StringRef Color = getColor(model::TypeDefinitionKind::StructDefinition); Out << ""; headerCell(Out, Color, "Offset"); headerCell(Out, Color, "Size"); headerCell(Out, Color, "Name"); Out << ""; // Struct fields are stacked vertically uint64_t LastOffset = 0; for (auto &&[Index, Field] : llvm::enumerate(T->Fields())) { // Check if there's padding to be added before this field if (Field.Offset() > LastOffset) addStructField(Out, LastOffset, Field.Offset() - LastOffset, "padding"); auto Name = buildFieldName(*Field.Type()); uint64_t Size = Field.Type()->size().value_or(0); addStructField(Out, Field.Offset(), Size, Name, Index); LastOffset += Field.Offset() + Size; } // Check if there's trailing padding auto StructSize = T->size().value_or(0); if (StructSize > LastOffset) addStructField(Out, LastOffset, StructSize - LastOffset, "padding"); } /// Generate the inner table of a union type void TypeSystemPrinter::dumpUnionFields(llvm::raw_ostream &Out, const model::UnionDefinition *T) { if (T->Fields().size() == 0) { Out << ""; return; } Out << ""; // Union fields are disposed horizontally for (auto &&[Index, Field] : llvm::enumerate(T->Fields())) { auto Name = buildFieldName(*Field.Type()); const auto Size = Field.Type()->size().value_or(0); paddedCell(Out, Name + " (size: " + to_string(Size) + ")", Index); } Out << ""; } /// Generate the inner table of a function type void TypeSystemPrinter::dumpFunctionType(llvm::raw_ostream &Out, const model::TypeDefinition *T) { llvm::SmallVector ReturnTypes; llvm::SmallVector Arguments; // Collect arguments and return types if (auto *RawFunc = dyn_cast(T)) { for (auto &RetTy : RawFunc->ReturnValues()) ReturnTypes.push_back(RetTy.Type().get()); for (auto &ArgTy : RawFunc->Arguments()) Arguments.push_back(ArgTy.Type().get()); if (not RawFunc->StackArgumentsType().isEmpty()) Arguments.push_back(RawFunc->StackArgumentsType().get()); } else if (auto *CABIFunc = dyn_cast(T)) { if (not CABIFunc->ReturnType().isEmpty()) ReturnTypes.push_back(CABIFunc->ReturnType().get()); for (auto &ArgTy : CABIFunc->Arguments()) Arguments.push_back(ArgTy.Type().get()); } // Inner table that divides return types and arguments Out << ""; // Header llvm::StringRef Color = getColor(T->Kind()); Out << ""; headerCell(Out, Color, "Return Types"); headerCell(Out, Color, "Arguments"); Out << ""; // Second row of the inner table (actual types) Out << ""; size_t CurPort = 0; // Return types are disposed horizontally in a dedicated table Out << ""; // Arguments types are disposed horizontally in a dedicated table Out << ""; // End of second row Out << ""; // End of inner table Out << "
"; if (ReturnTypes.empty()) { paddedCell(Out, "void"); } else { for (auto Field : ReturnTypes) paddedCell(Out, buildFieldName(*Field), CurPort++); } Out << "
"; if (Arguments.empty()) { Out << ""; } else { for (auto Field : Arguments) paddedCell(Out, buildFieldName(*Field), CurPort++); } Out << "
"; } static std::string nodeName(uint64_t NodeID) { return "node_" + to_string(NodeID); } /// Generate the inner content of a Typedef node void TypeSystemPrinter::dumpTypedefUnderlying(llvm::raw_ostream &Out, const model::TypedefDefinition *T) { Out << ""; paddedCell(Out, buildFieldName(*T->UnderlyingType()), 0); Out << ""; } void TypeSystemPrinter::dumpTypeNode(const model::TypeDefinition *T, int NodeID) { // Print the name of the node Out << nodeName(NodeID) << "["; // Choose the node's border color llvm::StringRef Color = getColor(T->Kind()); Out << "color=" << Color << ", "; // Start of HTML-style label Out << "label= < "; // Print the name of the type on top Out << ""; // Print fields in a table Out << ""; // End of label Out << "
" << NameBuilder.name(*T) << " (size: " << to_string(T->trySize().value_or(0)) << ")
"; if (auto *StructT = dyn_cast(T)) dumpStructFields(Out, StructT); else if (auto *UnionT = dyn_cast(T)) dumpUnionFields(Out, UnionT); else if (isa(T) or isa(T)) dumpFunctionType(Out, T); else if (auto *Typedef = dyn_cast(T)) dumpTypedefUnderlying(Out, Typedef); else Out << ""; Out << "
Unhandled Type
>];\n"; } void TypeSystemPrinter::addEdge(int SrcID, int SrcPort, int DstID) { Out << nodeName(SrcID) << ":"; Out << " -> "; Out << nodeName(DstID); Out << ":;\n"; } struct FieldEdge { std::string Label; const model::TypeDefinition *Destination; bool IsPointer; }; static RecursiveCoroutine buildFieldEdgeLabel(const model::Type &Type, std::string &&Current = {}, bool IsPointer = false) { if (const auto *Array = llvm::dyn_cast(&Type)) { if (!Current.empty()) Current += ",\\n"; Current += "Array[" + std::to_string(Array->ElementCount()) + "]"; rc_return buildFieldEdgeLabel(*Array->ElementType(), std::move(Current), false); } else if (const auto *D = llvm::dyn_cast(&Type)) { rc_return{ .Label = std::move(Current), .Destination = &D->unwrap(), .IsPointer = IsPointer }; } else if (const auto *P = llvm::dyn_cast(&Type)) { if (!Current.empty()) Current += ",\\n"; Current += "Pointer (" + std::to_string(P->PointerSize()) + " bytes)"; rc_return buildFieldEdgeLabel(*P->PointeeType(), std::move(Current), true); } else if (const auto *P = llvm::dyn_cast(&Type)) { rc_return{ .Label = std::move(Current), .Destination = nullptr, .IsPointer = IsPointer }; } else { revng_abort("Unsupported type."); } } void TypeSystemPrinter::addFieldEdge(std::string &&Label, bool IsPointer, int SrcID, int SrcPort, int DstID) { // Edge Out << nodeName(SrcID) << ":"; Out << " -> "; Out << nodeName(DstID) << ":"; // Label Out << "[label=\"" << std::move(Label) << "\""; // Style if (IsPointer) Out << ", style=dotted"; Out << "];\n"; } void TypeSystemPrinter::print(const model::TypeDefinition &T) { // Don't repeat nodes if (Visited.contains(&T)) return; llvm::SmallVector ToVisit = { &T }; auto EmitNode = [this](const model::TypeDefinition *TypeToEmit) { dumpTypeNode(TypeToEmit, NextID); NodesMap.insert({ TypeToEmit, NextID }); NextID++; }; // Emit the root EmitNode(&T); while (not ToVisit.empty()) { const model::TypeDefinition *CurType = ToVisit.pop_back_val(); if (Visited.contains(CurType)) continue; uint64_t CurID = NodesMap.at(CurType); // Collect all the successors FieldList Fields = collectFields(CurType); for (auto &&[Index, Field] : llvm::enumerate(Fields)) { FieldEdge Edge = buildFieldEdgeLabel(*Field); auto &&[Label, DefinitionPointer, IsPointer] = Edge; // Don't add edges for primitive types, as they would pollute the graph // and add no information regarding the type system structure if (DefinitionPointer == nullptr) continue; uint64_t SuccID; auto It = NodesMap.find(DefinitionPointer); if (It != NodesMap.end()) { // If a node already exists for the target type, use that SuccID = It->second; } else { // If the node does not already exist, create a new one SuccID = NextID; EmitNode(DefinitionPointer); } // Add an edge to the type referenced by the current field. // Since we have created the target type if it does not exist, and // the source node was either the root node or a successor of a // previously visited node, we are sure that both the source and the // destination of this edge have already been created. addFieldEdge(std::move(Label), IsPointer, CurID, Index, SuccID); // Push the field's type to the visit stack ToVisit.push_back(DefinitionPointer); } // Mark this Type as visited: the node has been emitted, as well as // all of its outgoing edges and their respective target nodes. Visited.insert(CurType); } } void TypeSystemPrinter::dumpFunctionNode(const model::Function &F, int NodeID) { // Print the name of the node Out << nodeName(NodeID) << "["; // Choose the node's border color llvm::StringRef Color = Purple; Out << "color=" << Color << ", "; // Start of HTML-style label Out << "label= < "; // Print the name of the function on top Out << ""; // Print connected types in a table Out << ""; // End of label Out << "
" << NameBuilder.name(F) << "()
"; // Header Out << ""; headerCell(Out, Color, "Prototype"); headerCell(Out, Color, "StackType"); Out << ""; // Second row of the inner table (actual types) Out << ""; if (const model::TypeDefinition *Prototype = F.prototype()) paddedCell(Out, NameBuilder.name(*Prototype), /*port=*/0); else Out << ""; if (const model::StructDefinition *StackFrame = F.stackFrameType()) paddedCell(Out, NameBuilder.name(*StackFrame), /*port=*/1); else Out << ""; Out << ""; // End of inner table Out << "
>];\n"; } uint64_t TypeSystemPrinter::print(const model::Function &F) { // Node corresponding to the function uint64_t FunctionNodeID = NextID; dumpFunctionNode(F, FunctionNodeID); NextID++; // Node of prototype type, if present if (const model::TypeDefinition *Prototype = F.prototype()) { print(*Prototype); // Edges uint64_t PrototypeNodeID = NodesMap.at(Prototype); addEdge(FunctionNodeID, 0, PrototypeNodeID); } // Node of the stack type, if present if (const model::StructDefinition *StackFrame = F.stackFrameType()) { print(*StackFrame); // Edges uint64_t StackNodeID = NodesMap.at(StackFrame); addEdge(FunctionNodeID, 1, StackNodeID); } return FunctionNodeID; } void TypeSystemPrinter::dumpFunctionNode(const model::DynamicFunction &F, int NodeID) { // Print the name of the node Out << nodeName(NodeID) << "["; // Choose the node's border color llvm::StringRef Color = Pink; Out << "color=" << Color << ", "; // Start of HTML-style label Out << "label= < "; // Print the name of the function on top Out << ""; // Print connected types in a table Out << ""; // End of label Out << "
" << NameBuilder.name(F) << "()
"; // Header Out << ""; headerCell(Out, Color, "Prototype"); Out << ""; // Second row of the inner table (actual types) Out << ""; if (const model::TypeDefinition *Prototype = F.prototype()) paddedCell(Out, NameBuilder.name(*Prototype), /*port=*/0); else Out << ""; Out << ""; // End of inner table Out << "
>];\n"; } uint64_t TypeSystemPrinter::print(const model::DynamicFunction &F) { // Node corresponding to the function uint64_t FunctionNodeID = NextID; dumpFunctionNode(F, FunctionNodeID); NextID++; // Node of the prototype type, if present if (const model::TypeDefinition *Prototype = F.prototype()) { print(*Prototype); // Edges uint64_t PrototypeNodeID = NodesMap.at(Prototype); addEdge(FunctionNodeID, 0, PrototypeNodeID); } return FunctionNodeID; } void TypeSystemPrinter::dumpSegmentNode(const model::Segment &S, int NodeID) { // Print the name of the node Out << nodeName(NodeID) << "["; // Choose the node's border color llvm::StringRef Color = Orange; Out << "color=" << Color << ", "; // Start of HTML-style label Out << "label= < "; // Print the name of the function on top Out << ""; // Print connected types in a table Out << ""; // End of label Out << "
" << NameBuilder.name(Binary, S) << "()
"; // Header Out << ""; headerCell(Out, Color, "Type"); Out << ""; // Second row of the inner table (actual types) Out << ""; if (const model::StructDefinition *Type = S.type()) paddedCell(Out, NameBuilder.name(*Type), /*port=*/0); else Out << ""; Out << ""; // End of inner table Out << "
>];\n"; } uint64_t TypeSystemPrinter::print(const model::Segment &S) { // Node corresponding to the function uint64_t SegmentNodeID = NextID; dumpSegmentNode(S, SegmentNodeID); NextID++; // Node of the prototype type, if present if (const model::StructDefinition *Type = S.type()) { print(*Type); // Edges uint64_t PrototypeNodeID = NodesMap.at(Type); addEdge(SegmentNodeID, 0, PrototypeNodeID); } return SegmentNodeID; } void TypeSystemPrinter::print() { std::vector TopLevelIDs; // Print all functions and related types for (auto &F : Binary.Functions()) TopLevelIDs.push_back(print(F)); // Print all dynamic functions and related types for (auto &F : Binary.ImportedDynamicFunctions()) TopLevelIDs.push_back(print(F)); // Print all the segments and related types for (auto &S : Binary.Segments()) TopLevelIDs.push_back(print(S)); if (not TopLevelIDs.empty()) { Out << "{ rank=source;"; for (uint64_t I : TopLevelIDs) Out << " " << nodeName(I); Out << "}\n"; } // Print remaining types, if any for (auto &T : Binary.TypeDefinitions()) if (!NodesMap.contains(T.get())) print(*T); }