/// \file HTML.cpp
/// \brief
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/Support/FormatVariadic.h"
#include "revng/EarlyFunctionAnalysis/ControlFlowGraph.h"
#include "revng/Model/Binary.h"
#include "revng/Yield/ControlFlow/FallthroughDetection.h"
#include "revng/Yield/Function.h"
#include "revng/Yield/HTML.h"
namespace tags {
static constexpr auto Function = "function";
static constexpr auto LabeledBlock = "labeled-block";
static constexpr auto BasicBlock = "basic-block";
static constexpr auto FunctionLabel = "function-label";
static constexpr auto BasicBlockLabel = "basic-block-label";
static constexpr auto Instruction = "instruction";
static constexpr auto InstructionAddress = "instruction-address";
static constexpr auto InstructionBytes = "instruction-bytes";
static constexpr auto InstructionMnemonic = "mnemonic";
static constexpr auto InstructionMnemonicPrefix = "mnemonic-prefix";
static constexpr auto InstructionMnemonicSuffix = "mnemonic-suffix";
static constexpr auto InstructionOpcode = "instruction-opcode";
static constexpr auto Comment = "comment";
static constexpr auto Error = "error";
static constexpr auto ImmediateValue = "immediate-value";
static constexpr auto MemoryOperand = "memory-operand";
static constexpr auto Register = "register";
static constexpr auto CommentIndicator = "comment-indicator";
static constexpr auto LabelIndicator = "label-indicator";
static constexpr auto FunctionLink = "function-link";
static constexpr auto BasicBlockLink = "basic-block-link";
static constexpr auto InstructionLink = "instruction-link";
static constexpr auto BasicBlockOwner = "basic-block-owner";
static constexpr auto InstructionTarget = "instruction-target";
static constexpr auto InstructionTargets = "instruction-targets";
static constexpr auto Whitespace = "whitespace";
static constexpr auto Untagged = "untagged";
} // namespace tags
namespace templates {
static constexpr auto BlockDiv = R"(
{2}
)";
static constexpr auto SimpleDiv = R"({1}
)";
static constexpr auto Link = R"({2})";
static constexpr auto Span = R"({1})";
} // namespace templates
static std::string address(const MetaAddress &Address) {
std::string Result = Address.toString();
constexpr std::array ForbiddenCharacters = { ' ', ':', '!', '#', '?',
'<', '>', '/', '\\', '{',
'}', '[', ']' };
for (char &Character : Result)
if (llvm::find(ForbiddenCharacters, Character) != ForbiddenCharacters.end())
Character = '_';
return Result;
}
static std::string basicBlockID(const MetaAddress &Address) {
return "basic_block_at_" + address(Address);
}
static std::string instructionID(const MetaAddress &Address) {
return "instruction_at_" + address(Address);
}
static std::string link(const MetaAddress &Target,
const yield::Function &Function,
const model::Binary &Binary,
llvm::StringRef CustomName = "") {
if (auto Iterator = Binary.Functions.find(Target);
Iterator != Binary.Functions.end()) {
// The target is a function
std::string FinalName = CustomName.str();
if (FinalName.empty())
FinalName = Iterator->name().str().str();
return llvm::formatv(templates::Link,
tags::FunctionLink,
address(Target) + ".html#" + basicBlockID(Target),
std::move(FinalName));
} else if (auto Iterator = Function.ControlFlowGraph.find(Target);
Iterator != Function.ControlFlowGraph.end()) {
// The target is a basic block
std::string FinalName = CustomName.str();
if (FinalName.empty()) {
auto FunctionIterator = Binary.Functions.find(Function.Entry);
revng_assert(FunctionIterator != Binary.Functions.end());
std::string FunctionPrefix = FunctionIterator->name().str().str() + "_";
std::string BlockOwnerName = llvm::formatv(templates::Span,
tags::BasicBlockOwner,
std::move(FunctionPrefix));
std::string BlockName = "basic_block_at_" + address(Target);
FinalName = std::move(BlockOwnerName) + std::move(BlockName);
}
return llvm::formatv(templates::Link,
tags::BasicBlockLink,
address(Function.Entry) + ".html#"
+ basicBlockID(Target),
std::move(FinalName));
} else if (Target.isValid()) {
// The target is an instruction
std::string FinalName = CustomName.str();
if (FinalName.empty())
FinalName = "instruction_at_" + Target.toString();
return llvm::formatv(templates::Link,
tags::InstructionLink,
address(Function.Entry) + ".html#"
+ instructionID(Target),
std::move(FinalName));
} else {
// The target is impossible to deduce, it's an indirect call or the like.
return "unknown_target";
}
}
static std::string commentIndicator(model::Architecture::Values Architecture) {
namespace Arch = model::Architecture;
return llvm::formatv(templates::Span,
tags::CommentIndicator,
Arch::getAssemblyCommentIndicator(Architecture));
}
static std::string labelIndicator(model::Architecture::Values Architecture) {
namespace Arch = model::Architecture;
return llvm::formatv(templates::Span,
tags::LabelIndicator,
Arch::getAssemblyLabelIndicator(Architecture));
}
static std::string label(const yield::BasicBlock &BasicBlock,
const yield::Function &Function,
const model::Binary &Binary) {
std::string Link = link(BasicBlock.Start, Function, Binary);
return llvm::formatv(templates::SimpleDiv,
Function.Entry == BasicBlock.Start ?
tags::FunctionLabel :
tags::BasicBlockLabel,
std::move(Link += labelIndicator(Binary.Architecture)));
}
static std::string whitespace(size_t Count) {
if (Count == 0)
return "";
std::string Result;
for (size_t Counter = 0; Counter < Count; ++Counter)
Result += " ";
return llvm::formatv(templates::Span, tags::Whitespace, std::move(Result));
}
static std::string newLine() {
return llvm::formatv(templates::Span, tags::Whitespace, "
");
}
static std::string commentImpl(const char *Template,
llvm::StringRef Tag,
const model::Binary &Binary,
std::string &&Body,
size_t Offset,
bool NeedsNewLine) {
std::string Result = commentIndicator(Binary.Architecture) + whitespace(1)
+ std::move(Body);
Result = llvm::formatv(Template, Tag, std::move(Result));
return (NeedsNewLine ? newLine() : "") + whitespace(Offset)
+ std::move(Result);
}
static std::string comment(const model::Binary &Binary,
std::string &&Body,
size_t Offset = 0,
bool NeedsNewLine = false) {
return commentImpl(templates::Span,
tags::Comment,
Binary,
std::move(Body),
Offset,
NeedsNewLine);
}
static std::string error(const model::Binary &Binary,
std::string &&Body,
size_t Offset = 0,
bool NeedsNewLine = false) {
return commentImpl(templates::Span,
tags::Error,
Binary,
std::move(Body),
Offset,
NeedsNewLine);
}
static std::string blockComment(llvm::StringRef Tag,
const model::Binary &Binary,
std::string &&Body,
size_t Offset = 0,
bool NeedsNewLine = false) {
return commentImpl(templates::SimpleDiv,
Tag,
Binary,
std::move(Body),
Offset,
NeedsNewLine);
}
static std::string bytes(const model::Binary &Binary,
const yield::ByteContainer &Bytes,
size_t Limit = std::numeric_limits::max()) {
std::string Result;
llvm::raw_string_ostream FormattingStream(Result);
bool NeedsSpace = false;
for (const auto &Byte : llvm::ArrayRef{ Bytes }.take_front(Limit)) {
if (NeedsSpace)
FormattingStream << " ";
else
NeedsSpace = true;
llvm::write_hex(FormattingStream, Byte, llvm::HexPrintStyle::Upper, 2);
}
if (Bytes.size() > Limit)
FormattingStream << " [...]";
FormattingStream.flush();
return blockComment(tags::InstructionBytes, Binary, std::move(Result));
}
using ParsedSuccessorVector = llvm::SmallVector;
template
class TargetPrintingHelper {
private:
const yield::BasicBlock &BasicBlock;
const yield::Function &Function;
const model::Binary &Binary;
size_t TailOffset;
public:
TargetPrintingHelper(const yield::BasicBlock &BasicBlock,
const yield::Function &Function,
const model::Binary &Binary,
size_t TailOffset) :
BasicBlock(BasicBlock),
Function(Function),
Binary(Binary),
TailOffset(TailOffset) {}
std::string singleTarget(const efa::ParsedSuccessor &Target) {
const auto &[NextAddress, CallAddress] = Target;
if (NextAddress.isValid()) {
if (CallAddress.isValid()) {
// Both are valid, it's a normal call.
return call({ CallAddress })
+ comment(Binary,
"then goes to " + targetLink(NextAddress),
TailOffset,
true);
} else {
// Only jump address is valid, it's a normal jump.
if (NextAddress == BasicBlock.End) {
// The only target is the next instruction.
// Don't emit these in horizontal layout.
if constexpr (ShouldUseVerticalLayout == false)
return "";
}
return comment(Binary, "always goes to " + targetLink(NextAddress));
}
} else {
if (CallAddress.isValid()) {
// Only call address is valid, it's a no-return call.
return call({ CallAddress })
+ comment(Binary, "and does not return", TailOffset, true);
} else {
// Neither is valid, nothing is known about the target.
return "";
}
}
}
std::string twoTargets(const efa::ParsedSuccessor &First,
const efa::ParsedSuccessor &Second) {
if (First.OptionalCallAddress.isValid()
|| Second.OptionalCallAddress.isValid()) {
return multipleTargets({ First, Second });
}
MetaAddress FirstTarget = First.NextInstructionAddress;
MetaAddress SecondTarget = Second.NextInstructionAddress;
if (FirstTarget == SecondTarget)
return singleTarget(First);
if (FirstTarget == BasicBlock.End)
std::swap(FirstTarget, SecondTarget);
if (SecondTarget == BasicBlock.End) {
// One of the targets is the next instruction.
std::string Result = comment(Binary,
"if taken, goes to "
+ targetLink(FirstTarget) + ",");
Result += comment(Binary,
"otherwise, goes to " + targetLink(SecondTarget),
TailOffset,
true);
return Result;
} else {
return multipleTargets({ First, Second });
}
}
std::string multipleTargets(const ParsedSuccessorVector &Targets,
bool HasUnknownTargets = false) {
llvm::SmallVector CallAddresses;
for (const auto &[_, Target] : Targets)
if (Target.isValid())
CallAddresses.emplace_back(Target);
std::string Result = !CallAddresses.empty() ? call(CallAddresses) : "";
if (!Result.empty())
Result += comment(Binary, "then goes to one of: ", TailOffset, true);
else
Result += comment(Binary, "known targets include: ");
size_t ValidTargetCount = 0;
for (const auto &[Target, _] : Targets)
if (Target.isValid())
++ValidTargetCount;
revng_assert(ValidTargetCount != 0);
for (size_t Counter = 0; const auto &[Target, _] : Targets) {
if (Target.isValid()) {
std::string Link = targetLink(Target);
if (++Counter < ValidTargetCount)
Link += ",";
Result += comment(Binary, "- " + std::move(Link), TailOffset, true);
}
}
if (HasUnknownTargets == true)
Result += comment(Binary, "and more", TailOffset, true);
return Result;
}
protected:
std::string targetLink(const MetaAddress &Target) {
if (Target.isInvalid())
return "an unknown location";
else if (Target == BasicBlock.End)
return llvm::formatv(templates::Span,
tags::InstructionTarget,
link(Target,
Function,
Binary,
"the next instruction"));
else
return llvm::formatv(templates::Span,
tags::InstructionTarget,
link(Target, Function, Binary));
}
std::string call(const llvm::SmallVector &CallAddresses) {
revng_assert(!CallAddresses.empty());
std::string Result = "calls ";
for (size_t Counter = 0; const MetaAddress &Address : CallAddresses) {
Result += targetLink(Address);
if (++Counter != CallAddresses.size())
Result += ", ";
}
return comment(Binary, std::move(Result));
}
};
template
static std::string targets(const yield::BasicBlock &BasicBlock,
const yield::Function &Function,
const model::Binary &Binary,
size_t TailOffset = 0) {
static const efa::ParsedSuccessor UnknownTarget{
.NextInstructionAddress = MetaAddress::invalid(),
.OptionalCallAddress = MetaAddress::invalid()
};
bool HasUnknownTargets = false;
ParsedSuccessorVector SuccessorTargets;
for (const auto &Edge : BasicBlock.Successors) {
auto TargetPair = efa::parseSuccessor(*Edge, BasicBlock.End, Binary);
if (TargetPair.NextInstructionAddress.isValid()
|| TargetPair.OptionalCallAddress.isValid()) {
SuccessorTargets.emplace_back(std::move(TargetPair));
} else {
HasUnknownTargets = true;
}
}
std::string Result;
using LocalHelper = TargetPrintingHelper;
LocalHelper Helper(BasicBlock, Function, Binary, TailOffset);
if (SuccessorTargets.size() == 0) {
revng_assert(HasUnknownTargets == true,
"A basic block with no successors.");
Result = Helper.singleTarget(UnknownTarget);
} else if (SuccessorTargets.size() == 1) {
if (HasUnknownTargets == false)
Result = Helper.singleTarget(SuccessorTargets.front());
else
Result = Helper.twoTargets(SuccessorTargets.front(), UnknownTarget);
} else if (SuccessorTargets.size() == 2 && HasUnknownTargets == false) {
Result = Helper.twoTargets(SuccessorTargets.front(),
SuccessorTargets.back());
} else {
Result = Helper.multipleTargets(SuccessorTargets, HasUnknownTargets);
}
return llvm::formatv(templates::Span,
tags::InstructionTargets,
std::move(Result));
}
static std::string tagTypeAsString(yield::TagType::Values Type) {
switch (Type) {
case yield::TagType::Immediate:
return tags::ImmediateValue;
case yield::TagType::Memory:
return tags::MemoryOperand;
case yield::TagType::Mnemonic:
return tags::InstructionMnemonic;
case yield::TagType::MnemonicPrefix:
return tags::InstructionMnemonicPrefix;
case yield::TagType::MnemonicSuffix:
return tags::InstructionMnemonicSuffix;
case yield::TagType::Register:
return tags::Register;
case yield::TagType::Whitespace:
return tags::Whitespace;
case yield::TagType::Invalid:
default:
revng_abort("Unknown tag type");
}
}
using LeafContainer = llvm::SmallVector, 16>;
static std::string tag(size_t Index,
const LeafContainer &Leaves,
const yield::Instruction &Instruction) {
revng_assert(Index < Instruction.Tags.size());
const yield::Tag &Tag = *std::next(Instruction.Tags.begin(), Index);
llvm::StringRef TextView = Instruction.Disassembled;
revng_assert(Index < Leaves.size());
const auto &AdjacentLeaves = Leaves[Index];
std::string Result;
size_t CurrentIndex = Tag.From;
for (const auto &LeafIndex : llvm::reverse(AdjacentLeaves)) {
revng_assert(LeafIndex < Instruction.Tags.size());
const auto &LeafTag = *std::next(Instruction.Tags.begin(), LeafIndex);
revng_assert(CurrentIndex <= LeafTag.From);
if (CurrentIndex < LeafTag.From)
Result += TextView.slice(CurrentIndex, LeafTag.From);
Result += tag(LeafIndex, Leaves, Instruction);
CurrentIndex = LeafTag.To;
}
revng_assert(CurrentIndex <= Tag.To);
if (CurrentIndex < Tag.To)
Result += TextView.slice(CurrentIndex, Tag.To);
std::string TagStr = tagTypeAsString(Tag.Type);
if (Tag.Type != yield::TagType::Mnemonic)
return llvm::formatv(templates::Span, std::move(TagStr), std::move(Result));
else
return llvm::formatv(templates::Link,
std::move(TagStr),
"#" + instructionID(Instruction.Address),
std::move(Result));
}
static std::string taggedText(const yield::Instruction &Instruction) {
revng_assert(!Instruction.Tags.empty(),
"Tagless instructions are not supported");
// Convert the tag list into a tree to simplify working with nested tags.
llvm::SmallVector RootIndices;
LeafContainer Leaves(Instruction.Tags.size());
for (size_t Index = Instruction.Tags.size() - 1; Index > 0; --Index) {
const auto &CurrentTag = *std::next(Instruction.Tags.begin(), Index);
bool DependencyDetected = false;
for (size_t PrevIndex = Index - 1; PrevIndex != size_t(-1); --PrevIndex) {
const auto &PreviousTag = *std::next(Instruction.Tags.begin(), PrevIndex);
if (CurrentTag.From >= PreviousTag.From
&& CurrentTag.To <= PreviousTag.To) {
// Current tag is inside the previous one.
// Add an edge corresponding to this relation.
if (!DependencyDetected)
Leaves[PrevIndex].emplace_back(Index);
DependencyDetected = true;
} else if (CurrentTag.From >= PreviousTag.To
&& CurrentTag.To >= PreviousTag.To) {
// Current tag is after (and outside) the previous one.
// Do nothing.
} else if (CurrentTag.From <= PreviousTag.From
&& CurrentTag.To <= PreviousTag.From) {
// Current tag is before (and outside) the previous one.
revng_abort("Tag container must be sorted.");
} else {
revng_abort("Tags must not intersect");
}
}
// The node is not depended on - add it as a root.
if (!DependencyDetected)
RootIndices.emplace_back(Index);
}
// Make sure there's at least one root.
RootIndices.emplace_back(0);
// Insert html-flavoured tags based on the tree.
std::string Result;
size_t CurrentIndex = 0;
llvm::StringRef TextView = Instruction.Disassembled;
for (size_t RootIndex : llvm::reverse(RootIndices)) {
revng_assert(RootIndex < Instruction.Tags.size());
const auto &RootTag = *std::next(Instruction.Tags.begin(), RootIndex);
if (CurrentIndex < RootTag.From)
Result += llvm::formatv(templates::Span,
tags::Untagged,
TextView.slice(CurrentIndex, RootTag.From));
Result += tag(RootIndex, Leaves, Instruction);
CurrentIndex = RootTag.To;
}
revng_assert(CurrentIndex <= TextView.size());
if (CurrentIndex < TextView.size())
Result += llvm::formatv(templates::Span,
tags::Untagged,
TextView.substr(CurrentIndex));
return Result;
}
template
static std::string instruction(const yield::Instruction &Instruction,
const yield::BasicBlock &BasicBlock,
const yield::Function &Function,
const model::Binary &Binary,
bool ShouldPrintTargets = false,
bool IsInDelayedSlot = false) {
// MetaAddress of the instruction.
std::string Result = blockComment(tags::InstructionAddress,
Binary,
Instruction.Address.toString());
// Raw bytes of the instruction.
//
// \note the instructions disassembler failed on are limited to 16 bytes.
if (Instruction.Error == "MCDisassembler failed")
Result += bytes(Binary, Instruction.RawBytes, 16);
else
Result += bytes(Binary, Instruction.RawBytes);
// LLVM's Opcode of the instruction.
if (!Instruction.OpcodeIdentifier.empty())
Result += blockComment(tags::InstructionOpcode,
Binary,
"llvm Opcode: " + Instruction.OpcodeIdentifier);
// Error message (Vertical layout only).
if constexpr (ShouldUseVerticalLayout == true)
if (!Instruction.Error.empty())
Result += error(Binary, "Error: " + Instruction.Error + "\n");
// Tagged instruction body.
Result += taggedText(Instruction);
size_t Tail = Instruction.Disassembled.size() + 1;
// The original comment if present.
bool HasTailComments = false;
if (!Instruction.Comment.empty()) {
Result += comment(Binary, std::string(Instruction.Comment), 1);
HasTailComments = true;
}
// Delayed slot notice if applicable.
if (IsInDelayedSlot) {
if (HasTailComments == true)
Result += comment(Binary, "delayed", Tail, true);
else
Result += comment(Binary, "delayed", 1);
HasTailComments = true;
}
// Horizontal layout only
if constexpr (ShouldUseVerticalLayout == false) {
// An error message.
if (!Instruction.Error.empty()) {
if (HasTailComments == true)
Result += error(Binary, "Error: " + Instruction.Error, Tail, true);
else
Result += error(Binary, "Error: " + Instruction.Error, 1);
HasTailComments = true;
}
// The list of targets.
if (ShouldPrintTargets) {
auto Targets = targets(BasicBlock, Function, Binary, Tail);
if (!Targets.empty()) {
if (HasTailComments == false)
Result += whitespace(1) + std::move(Targets);
else
Result += newLine() + whitespace(Tail) + std::move(Targets);
}
}
}
return llvm::formatv(templates::BlockDiv,
tags::Instruction,
instructionID(Instruction.Address),
std::move(Result));
}
template
static std::string basicBlock(const yield::BasicBlock &BasicBlock,
const yield::Function &Function,
const model::Binary &Binary,
bool HasLabel = true) {
revng_assert(!BasicBlock.Instructions.empty());
auto FromIterator = BasicBlock.Instructions.begin();
auto ToIterator = std::prev(BasicBlock.Instructions.end());
if (BasicBlock.HasDelaySlot) {
revng_assert(BasicBlock.Instructions.size() > 1);
--ToIterator;
}
std::string Result;
for (auto Iterator = FromIterator; Iterator != ToIterator; ++Iterator) {
// Print most of the instructions.
Result += instruction(*Iterator,
BasicBlock,
Function,
Binary);
}
// Print the instruction with targets.
Result += instruction(*ToIterator++,
BasicBlock,
Function,
Binary,
true);
if (BasicBlock.HasDelaySlot) {
// Print the instruction in the delay slot, if applicable.
Result += instruction(*ToIterator++,
BasicBlock,
Function,
Binary,
false,
true);
}
revng_assert(ToIterator == BasicBlock.Instructions.end());
if (HasLabel)
return llvm::formatv(templates::SimpleDiv,
tags::BasicBlock,
std::move(Result));
else
return llvm::formatv(templates::BlockDiv,
tags::BasicBlock,
basicBlockID(BasicBlock.Start),
std::move(Result));
}
template
static std::string labeledBlock(const yield::BasicBlock &FirstBlock,
const yield::Function &Function,
const model::Binary &Binary) {
std::string Result;
Result += label(FirstBlock, Function, Binary);
const yield::BasicBlock *LastBlock = nullptr;
if constexpr (ShouldMergeFallthroughTargets == false) {
Result += basicBlock(FirstBlock, Function, Binary);
LastBlock = &FirstBlock;
} else {
auto BasicBlocks = yield::cfg::labeledBlock(FirstBlock, Function, Binary);
if (BasicBlocks.empty())
return "";
bool IsFirst = true;
for (const auto &BasicBlock : BasicBlocks) {
Result += basicBlock(*BasicBlock,
Function,
Binary,
IsFirst);
IsFirst = false;
}
LastBlock = BasicBlocks.back();
}
if constexpr (UseVerticalTargetLayout == true) {
auto Targets = targets(*LastBlock,
Function,
Binary);
if (!Targets.empty())
Result += newLine() + std::move(Targets);
}
return llvm::formatv(templates::BlockDiv,
tags::LabeledBlock,
basicBlockID(FirstBlock.Start),
std::move(Result));
}
std::string yield::html::functionAssembly(const yield::Function &Function,
const model::Binary &Binary) {
std::string Result;
for (const auto &BasicBlock : Function.ControlFlowGraph)
Result += labeledBlock(BasicBlock, Function, Binary);
return Result;
}
std::string yield::html::controlFlowNode(const MetaAddress &Address,
const yield::Function &Function,
const model::Binary &Binary) {
auto Iterator = Function.ControlFlowGraph.find(Address);
revng_assert(Iterator != Function.ControlFlowGraph.end());
auto Result = labeledBlock(*Iterator, Function, Binary);
revng_assert(!Result.empty());
return Result;
}