mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
367 lines
12 KiB
C++
367 lines
12 KiB
C++
/// \file SVG.cpp
|
|
/// \brief
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include <unordered_map>
|
|
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
|
|
#include "revng/Model/Binary.h"
|
|
#include "revng/Yield/ControlFlow/Configuration.h"
|
|
#include "revng/Yield/ControlFlow/Extraction.h"
|
|
#include "revng/Yield/ControlFlow/NodeSizeCalculation.h"
|
|
#include "revng/Yield/CrossRelations.h"
|
|
#include "revng/Yield/Graph.h"
|
|
#include "revng/Yield/PTML.h"
|
|
#include "revng/Yield/SVG.h"
|
|
#include "revng/Yield/Support/SugiyamaStyleGraphLayout.h"
|
|
|
|
namespace tags {
|
|
|
|
static constexpr auto UnconditionalEdge = "unconditional";
|
|
static constexpr auto CallEdge = "call";
|
|
static constexpr auto TakenEdge = "taken";
|
|
static constexpr auto RefusedEdge = "refused";
|
|
|
|
static constexpr auto NodeBody = "node-body";
|
|
static constexpr auto NodeContents = "node-contents";
|
|
|
|
static constexpr auto UnconditionalArrowHead = "unconditional-arrow-head";
|
|
static constexpr auto CallArrowHead = "call-arrow-head";
|
|
static constexpr auto TakenArrowHead = "taken-arrow-head";
|
|
static constexpr auto RefusedArrowHead = "refused-arrow-head";
|
|
|
|
} // namespace tags
|
|
|
|
namespace templates {
|
|
|
|
constexpr auto *Point = "M {0} {1} ";
|
|
constexpr auto *Line = "L {0} {1} ";
|
|
constexpr auto *BezierCubic = "C {0} {1} {2} {3} {4} {5} ";
|
|
|
|
constexpr auto *Edge = R"Edge(<path
|
|
class='{0}-edge'
|
|
{1}
|
|
marker-end="url(#{0}-arrow-head)"
|
|
fill="none" />
|
|
)Edge";
|
|
|
|
constexpr auto *Rect = R"(<rect
|
|
class="{0}"
|
|
x="{1}"
|
|
y="{2}"
|
|
rx="{3}"
|
|
ry="{3}"
|
|
width="{4}"
|
|
height="{5}" />)";
|
|
|
|
constexpr auto *ForeignObject = R"(<foreignObject
|
|
class="{0}"
|
|
x="{1}"
|
|
y="{2}"
|
|
width="{3}"
|
|
height="{4}"><body
|
|
xmlns="http://www.w3.org/1999/xhtml">{5}</body></foreignObject>
|
|
)";
|
|
|
|
static constexpr auto ArrowHead = R"(<marker
|
|
id="{0}"
|
|
markerWidth="{1}"
|
|
markerHeight="{1}"
|
|
refX="{5}"
|
|
refY="{2}"
|
|
orient="{6}"><polygon
|
|
points="{4}, {1} {3}, {2} {4}, {4} {1}, {2}" /></marker>
|
|
)";
|
|
|
|
static constexpr auto Graph = R"(<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewbox="{0} {1} {2} {3}"
|
|
width="{2}"
|
|
height="{3}"><defs>{4}</defs>{5}</svg>
|
|
)";
|
|
|
|
} // namespace templates
|
|
|
|
static std::string_view edgeTypeAsString(yield::Graph::EdgeType Type) {
|
|
switch (Type) {
|
|
case yield::Graph::EdgeType::Unconditional:
|
|
return tags::UnconditionalEdge;
|
|
case yield::Graph::EdgeType::Call:
|
|
return tags::CallEdge;
|
|
case yield::Graph::EdgeType::Taken:
|
|
return tags::TakenEdge;
|
|
case yield::Graph::EdgeType::Refused:
|
|
return tags::RefusedEdge;
|
|
default:
|
|
revng_abort("Unknown edge type");
|
|
}
|
|
}
|
|
|
|
static std::string
|
|
cubicBend(const yield::Graph::Point &From, const yield::Graph::Point &To) {
|
|
static constexpr yield::Graph::Coordinate BendFactor = 0.8;
|
|
|
|
yield::Graph::Coordinate XDistance = To.X - From.X;
|
|
return llvm::formatv(templates::BezierCubic,
|
|
From.X + XDistance * BendFactor,
|
|
-From.Y,
|
|
To.X - XDistance * BendFactor,
|
|
-To.Y,
|
|
To.X,
|
|
-To.Y);
|
|
}
|
|
|
|
static std::string edge(const std::vector<yield::Graph::Point> &Path,
|
|
const yield::Graph::EdgeType &Type,
|
|
bool UseOrthogonalBends) {
|
|
std::string Points = "d=\"";
|
|
|
|
revng_assert(!Path.empty());
|
|
const auto &First = Path.front();
|
|
Points += llvm::formatv(templates::Point, First.X, -First.Y);
|
|
|
|
if (UseOrthogonalBends) {
|
|
for (size_t Index = 1; Index < Path.size(); ++Index)
|
|
Points += llvm::formatv(templates::Line, Path[Index].X, -Path[Index].Y);
|
|
} else {
|
|
revng_assert(Path.size() >= 2);
|
|
for (auto Iter = Path.begin(); Iter != std::prev(Path.end()); ++Iter)
|
|
Points += cubicBend(*Iter, *std::next(Iter));
|
|
}
|
|
|
|
revng_assert(!Points.empty());
|
|
revng_assert(Points.back() == ' ');
|
|
Points.pop_back(); // Remove an extra space at the end.
|
|
Points += '"';
|
|
|
|
return llvm::formatv(templates::Edge, edgeTypeAsString(Type), Points);
|
|
}
|
|
|
|
static std::string node(const yield::Node *Node,
|
|
std::string &&Content,
|
|
const yield::cfg::Configuration &Configuration) {
|
|
yield::Graph::Size HalfSize{ Node->Size.W / 2, Node->Size.H / 2 };
|
|
yield::Graph::Point TopLeft{ Node->Center.X - HalfSize.W,
|
|
-Node->Center.Y - HalfSize.H };
|
|
|
|
auto Text = llvm::formatv(templates::ForeignObject,
|
|
tags::NodeContents,
|
|
TopLeft.X,
|
|
TopLeft.Y,
|
|
Node->Size.W,
|
|
Node->Size.H,
|
|
std::move(Content));
|
|
|
|
auto Body = llvm::formatv(templates::Rect,
|
|
tags::NodeBody,
|
|
TopLeft.X,
|
|
TopLeft.Y,
|
|
Configuration.NodeCornerRoundingFactor,
|
|
Node->Size.W,
|
|
Node->Size.H);
|
|
|
|
return llvm::formatv("{0}", std::move(Text) + std::move(Body));
|
|
}
|
|
|
|
struct Viewbox {
|
|
yield::Graph::Point TopLeft = { -1, -1 };
|
|
yield::Graph::Point BottomRight = { +1, +1 };
|
|
};
|
|
|
|
static Viewbox makeViewbox(const yield::Node *Node) {
|
|
yield::Graph::Size HalfSize{ Node->Size.W / 2, Node->Size.H / 2 };
|
|
yield::Graph::Point TopLeft{ Node->Center.X - HalfSize.W,
|
|
-Node->Center.Y - HalfSize.H };
|
|
yield::Graph::Point BottomRight{ Node->Center.X + HalfSize.W,
|
|
-Node->Center.Y + HalfSize.H };
|
|
return Viewbox{ .TopLeft = std::move(TopLeft),
|
|
.BottomRight = std::move(BottomRight) };
|
|
}
|
|
|
|
static void expandViewbox(Viewbox &LHS, const Viewbox &RHS) {
|
|
if (RHS.TopLeft.X < LHS.TopLeft.X)
|
|
LHS.TopLeft.X = RHS.TopLeft.X;
|
|
if (RHS.TopLeft.Y < LHS.TopLeft.Y)
|
|
LHS.TopLeft.Y = RHS.TopLeft.Y;
|
|
if (RHS.BottomRight.X > LHS.BottomRight.X)
|
|
LHS.BottomRight.X = RHS.BottomRight.X;
|
|
if (RHS.BottomRight.Y > LHS.BottomRight.Y)
|
|
LHS.BottomRight.Y = RHS.BottomRight.Y;
|
|
}
|
|
|
|
static void expandViewbox(Viewbox &Box, const yield::Graph::Point &Point) {
|
|
if (Box.TopLeft.X > Point.X)
|
|
Box.TopLeft.X = Point.X;
|
|
if (Box.TopLeft.Y > -Point.Y)
|
|
Box.TopLeft.Y = -Point.Y;
|
|
if (Box.BottomRight.X < Point.X)
|
|
Box.BottomRight.X = Point.X;
|
|
if (Box.BottomRight.Y < -Point.Y)
|
|
Box.BottomRight.Y = -Point.Y;
|
|
}
|
|
|
|
static Viewbox calculateViewbox(const yield::Graph &Graph) {
|
|
revng_assert(Graph.size() != 0);
|
|
revng_assert(Graph.getEntryNode() != nullptr);
|
|
|
|
// Ensure every node fits.
|
|
Viewbox Result = makeViewbox(Graph.getEntryNode());
|
|
for (const auto *Node : Graph.nodes())
|
|
expandViewbox(Result, makeViewbox(Node));
|
|
|
|
// Ensure every edge point fits.
|
|
for (const auto *From : Graph.nodes())
|
|
for (const auto [To, Label] : From->successor_edges())
|
|
for (const auto &Point : Label->Path)
|
|
expandViewbox(Result, Point);
|
|
|
|
// Add some extra padding for a good measure.
|
|
Result.TopLeft.X -= 50;
|
|
Result.TopLeft.Y -= 50;
|
|
Result.BottomRight.X += 50;
|
|
Result.BottomRight.Y += 50;
|
|
|
|
return Result;
|
|
}
|
|
|
|
static std::string
|
|
arrowHead(llvm::StringRef Name, float Size, float Dip, float Shift = 0) {
|
|
return llvm::formatv(templates::ArrowHead,
|
|
Name,
|
|
std::to_string(Size),
|
|
std::to_string(Size / 2),
|
|
std::to_string(Dip),
|
|
"0",
|
|
std::to_string(Size - Shift),
|
|
"auto");
|
|
}
|
|
|
|
static std::string
|
|
duplicateArrowHeadsImpl(float Size, float Dip, float Shift = 0) {
|
|
return arrowHead(tags::UnconditionalArrowHead, Size, Dip, Shift)
|
|
+ arrowHead(tags::CallArrowHead, Size, Dip, Shift)
|
|
+ arrowHead(tags::TakenArrowHead, Size, Dip, Shift)
|
|
+ arrowHead(tags::RefusedArrowHead, Size, Dip, Shift);
|
|
}
|
|
|
|
static std::string
|
|
defaultArrowHeads(const yield::cfg::Configuration &Configuration) {
|
|
if (Configuration.UseOrthogonalBends == true)
|
|
return duplicateArrowHeadsImpl(8, 3, 0);
|
|
else
|
|
return duplicateArrowHeadsImpl(8, 3, 2);
|
|
}
|
|
|
|
template<bool ShouldEmitEmptyNodes, typename CallableType>
|
|
static std::string exportGraph(const yield::Graph &Graph,
|
|
const yield::cfg::Configuration &Configuration,
|
|
CallableType &&Callable) {
|
|
std::string Result;
|
|
|
|
// Export all the edges.
|
|
for (const auto *From : Graph.nodes()) {
|
|
if (ShouldEmitEmptyNodes || From->Address.isValid()) {
|
|
for (const auto [To, Edge] : From->successor_edges()) {
|
|
if (ShouldEmitEmptyNodes || To->Address.isValid()) {
|
|
revng_assert(Edge != nullptr);
|
|
revng_assert(Edge->Status != yield::Graph::EdgeStatus::Unrouted);
|
|
Result += edge(Edge->Path,
|
|
Edge->Type,
|
|
Configuration.UseOrthogonalBends);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export all the nodes.
|
|
for (const auto *Node : Graph.nodes())
|
|
if (ShouldEmitEmptyNodes || Node->Address.isValid())
|
|
Result += node(Node, Callable(*Node), Configuration);
|
|
|
|
Viewbox Box = calculateViewbox(Graph);
|
|
return llvm::formatv(templates::Graph,
|
|
Box.TopLeft.X,
|
|
Box.TopLeft.Y,
|
|
Box.BottomRight.X - Box.TopLeft.X,
|
|
Box.BottomRight.Y - Box.TopLeft.Y,
|
|
defaultArrowHeads(Configuration),
|
|
std::move(Result));
|
|
}
|
|
|
|
std::string yield::svg::controlFlow(const yield::Function &InternalFunction,
|
|
const model::Binary &Binary) {
|
|
constexpr auto Configuration = cfg::Configuration::getDefault();
|
|
|
|
yield::Graph ControlFlowGraph = cfg::extractFromInternal(InternalFunction,
|
|
Binary,
|
|
Configuration);
|
|
|
|
cfg::calculateNodeSizes(ControlFlowGraph,
|
|
InternalFunction,
|
|
Binary,
|
|
Configuration);
|
|
|
|
sugiyama::layout(ControlFlowGraph, Configuration);
|
|
|
|
auto ContentHelper = [&](const yield::Graph::Node &Node) {
|
|
if (Node.Address.isValid())
|
|
return yield::ptml::controlFlowNode(Node.Address,
|
|
InternalFunction,
|
|
Binary);
|
|
else
|
|
return std::string{};
|
|
};
|
|
return exportGraph<true>(ControlFlowGraph, Configuration, ContentHelper);
|
|
}
|
|
|
|
std::string yield::svg::calls(const yield::CrossRelations &Relations,
|
|
const model::Binary &Binary) {
|
|
auto Configuration = cfg::Configuration::getDefault();
|
|
Configuration.UseOrthogonalBends = false;
|
|
|
|
auto InternalGraph = Relations.toYieldGraph();
|
|
|
|
// Calculate node sizes
|
|
for (auto *Node : InternalGraph.nodes()) {
|
|
if (Node->Address.isValid()) {
|
|
// A normal node
|
|
auto FunctionIterator = Binary.Functions.find(Node->Address);
|
|
revng_assert(FunctionIterator != Binary.Functions.end());
|
|
|
|
size_t NameLength = FunctionIterator->name().size();
|
|
revng_assert(NameLength != 0);
|
|
|
|
Node->Size = yield::Graph::Size{ NameLength * Configuration.LabelFontSize
|
|
* Configuration.HorizontalFontFactor,
|
|
1 * Configuration.LabelFontSize
|
|
* Configuration.VerticalFontFactor };
|
|
} else {
|
|
// An entry node.
|
|
Node->Size = yield::Graph::Size{ 30, 30 };
|
|
}
|
|
|
|
Node->Size.W += Configuration.InternalNodeMarginSize * 2;
|
|
Node->Size.H += Configuration.InternalNodeMarginSize * 2;
|
|
}
|
|
|
|
sugiyama::layout(InternalGraph,
|
|
Configuration,
|
|
sugiyama::RankingStrategy::BreadthFirstSearch,
|
|
sugiyama::LayoutOrientation::LeftToRight);
|
|
|
|
auto ContentHelper = [&Binary](const yield::Graph::Node &Node) {
|
|
revng_assert(Node.Address.isValid());
|
|
|
|
if (Node.NextAddress.isValid())
|
|
return yield::ptml::shallowFunctionLink(Node.NextAddress, Binary);
|
|
else
|
|
return yield::ptml::functionNameDefinition(Node.Address, Binary);
|
|
};
|
|
return exportGraph<false>(InternalGraph, Configuration, ContentHelper);
|
|
}
|