-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce control-flow analysis algorithms and data structures.
This introduces three new data structures. The first is ControlFlowGraph, which represents a collection of basic blocks for a method. A basic block is a unit of code that terminates in a control flow instruction, and for which the only entry point is the start of the block. Basic blocks are represented by the new Block class. Finally, there is a new GraphBuilder class which can extract a ControlFlowGraph for a method. It also performs basic verification on control-flow, namely: - Jump targets must be a valid decoded instruction in the method. - The method's local control flow does not escape the method bounds.
- Loading branch information
Showing
12 changed files
with
1,149 additions
and
213 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// vim: set sts=2 ts=8 sw=2 tw=99 et: | ||
// | ||
// Copyright (C) 2006-2015 AlliedModders LLC | ||
// | ||
// This file is part of SourcePawn. SourcePawn is free software: you can | ||
// redistribute it and/or modify it under the terms of the GNU General Public | ||
// License as published by the Free Software Foundation, either version 3 of | ||
// the License, or (at your option) any later version. | ||
// | ||
// You should have received a copy of the GNU General Public License along with | ||
// SourcePawn. If not, see http://www.gnu.org/licenses/. | ||
// | ||
|
||
#include <amtl/am-bits.h> | ||
#include <amtl/am-maybe.h> | ||
#include <amtl/am-function.h> | ||
#include <amtl/am-vector.h> | ||
#include <stddef.h> | ||
|
||
namespace sp { | ||
|
||
class BitSet | ||
{ | ||
public: | ||
BitSet() | ||
{} | ||
explicit BitSet(size_t max_bits) | ||
: max_bits_(ke::Some(max_bits)) | ||
{} | ||
|
||
bool test(uintptr_t bit) { | ||
size_t word = word_for_bit(bit); | ||
if (word >= words_.length()) | ||
return false; | ||
return !!(words_[word] & (uintptr_t(1) << pos_in_word(bit))); | ||
} | ||
|
||
void set(uintptr_t bit) { | ||
assert(!max_bits_ || bit <= *max_bits_); | ||
size_t word = word_for_bit(bit); | ||
if (word >= words_.length()) | ||
words_.resize(word + 1); | ||
words_[word] |= (uintptr_t(1) << pos_in_word(bit)); | ||
} | ||
|
||
void for_each(const ke::Function<void(uintptr_t)>& callback) { | ||
for (size_t i = 0; i < words_.length(); i++) { | ||
uintptr_t word = words_[i]; | ||
|
||
while (word) { | ||
size_t bit = ke::FindRightmostBit(word); | ||
word &= ~(uintptr_t(1) << bit); | ||
callback(i * kBitsPerWord + bit); | ||
} | ||
} | ||
} | ||
|
||
private: | ||
static const size_t kBitsPerWord = sizeof(uintptr_t) * 8; | ||
|
||
static inline size_t word_for_bit(uintptr_t bit) { | ||
return bit / kBitsPerWord; | ||
} | ||
static inline size_t pos_in_word(uintptr_t bit) { | ||
return bit % kBitsPerWord; | ||
} | ||
|
||
private: | ||
ke::Vector<uintptr_t> words_; | ||
ke::Maybe<size_t> max_bits_; | ||
}; | ||
|
||
} // namespace sp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// vim: set sts=2 ts=8 sw=2 tw=99 et: | ||
// | ||
// Copyright (C) 2006-2015 AlliedModders LLC | ||
// | ||
// This file is part of SourcePawn. SourcePawn is free software: you can | ||
// redistribute it and/or modify it under the terms of the GNU General Public | ||
// License as published by the Free Software Foundation, either version 3 of | ||
// the License, or (at your option) any later version. | ||
// | ||
// You should have received a copy of the GNU General Public License along with | ||
// SourcePawn. If not, see http://www.gnu.org/licenses/. | ||
// | ||
|
||
#include "control-flow.h" | ||
#include "opcodes.h" | ||
|
||
namespace sp { | ||
|
||
using namespace ke; | ||
|
||
ControlFlowGraph::ControlFlowGraph(PluginRuntime* rt, const uint8_t* start_offset) | ||
: rt_(rt), | ||
epoch_(1) | ||
{ | ||
entry_ = newBlock(start_offset); | ||
} | ||
|
||
ControlFlowGraph::~ControlFlowGraph() | ||
{ | ||
// This is necessary because blocks contain cycles between each other. | ||
for (const auto& block : blocks_) | ||
block->unlink(); | ||
} | ||
|
||
ke::RefPtr<Block> | ||
ControlFlowGraph::newBlock(const uint8_t* start) | ||
{ | ||
ke::RefPtr<Block> block = new Block(*this, start); | ||
blocks_.append(block); | ||
return block; | ||
} | ||
|
||
void | ||
ControlFlowGraph::dump(FILE* fp) | ||
{ | ||
for (const auto& block : blocks_) { | ||
fprintf(fp, "Block %p:\n", block.get()); | ||
for (const auto& pred : block->predecessors()) | ||
fprintf(fp, " predecessor: %p\n", pred.get()); | ||
for (const auto& child : block->successors()) | ||
fprintf(fp, " successor: %p\n", child.get()); | ||
fprintf(fp, " ---\n"); | ||
const uint8_t* cip = block->start(); | ||
while (true) { | ||
if (block->endType() == BlockEnd::Jump && cip >= block->end()) | ||
break; | ||
SpewOpcode(fp, | ||
rt_, | ||
reinterpret_cast<const cell_t*>(block->start()), | ||
reinterpret_cast<const cell_t*>(cip)); | ||
cip = NextInstruction(cip); | ||
if (block->endType() == BlockEnd::Insn && cip > block->end()) | ||
break; | ||
} | ||
fprintf(fp, "\n"); | ||
} | ||
} | ||
|
||
Block::Block(ControlFlowGraph& graph, const uint8_t* start) | ||
: graph_(graph), | ||
start_(start), | ||
end_(nullptr), | ||
end_type_(BlockEnd::Unknown), | ||
epoch_(0) | ||
{ | ||
} | ||
|
||
void | ||
Block::addTarget(Block* target) | ||
{ | ||
target->predecessors_.append(this); | ||
successors_.append(target); | ||
} | ||
|
||
void | ||
Block::endWithJump(const uint8_t* end_at, Block* target) | ||
{ | ||
end(end_at, BlockEnd::Jump); | ||
addTarget(target); | ||
} | ||
|
||
void | ||
Block::end(const uint8_t* end_at, BlockEnd end_type) | ||
{ | ||
assert(!end_); | ||
end_ = end_at; | ||
end_type_ = end_type; | ||
} | ||
|
||
bool | ||
Block::visited() const | ||
{ | ||
return epoch_ == graph_.epoch(); | ||
} | ||
|
||
void | ||
Block::setVisited() | ||
{ | ||
epoch_ = graph_.epoch(); | ||
} | ||
|
||
void | ||
Block::unlink() | ||
{ | ||
predecessors_.clear(); | ||
successors_.clear(); | ||
} | ||
|
||
} // namespace sp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// vim: set sts=2 ts=8 sw=2 tw=99 et: | ||
// | ||
// Copyright (C) 2006-2015 AlliedModders LLC | ||
// | ||
// This file is part of SourcePawn. SourcePawn is free software: you can | ||
// redistribute it and/or modify it under the terms of the GNU General Public | ||
// License as published by the Free Software Foundation, either version 3 of | ||
// the License, or (at your option) any later version. | ||
// | ||
// You should have received a copy of the GNU General Public License along with | ||
// SourcePawn. If not, see http://www.gnu.org/licenses/. | ||
// | ||
#ifndef _include_sourcepawn_vm_control_flow_h_ | ||
#define _include_sourcepawn_vm_control_flow_h_ | ||
|
||
#include <amtl/am-refcounting.h> | ||
#include <amtl/am-vector.h> | ||
#include <stdio.h> | ||
#include "plugin-runtime.h" | ||
|
||
namespace sp { | ||
|
||
class ControlFlowGraph; | ||
|
||
enum class BlockEnd { | ||
Unknown, | ||
Insn, | ||
Jump | ||
}; | ||
|
||
class Block : public ke::Refcounted<Block> | ||
{ | ||
friend class ControlFlowGraph; | ||
|
||
private: | ||
Block(ControlFlowGraph& graph, const uint8_t* start); | ||
|
||
public: | ||
const uint8_t* start() const { | ||
return start_; | ||
} | ||
const uint8_t* end() const { | ||
return end_; | ||
} | ||
bool ended() const { | ||
return !!end_; | ||
} | ||
BlockEnd endType() const { | ||
return end_type_; | ||
} | ||
const ke::Vector<ke::RefPtr<Block>>& predecessors() const { | ||
return predecessors_; | ||
} | ||
const ke::Vector<ke::RefPtr<Block>>& successors() const { | ||
return successors_; | ||
} | ||
|
||
~Block() { | ||
assert(!predecessors_.length()); | ||
assert(!successors_.length()); | ||
} | ||
|
||
void addTarget(Block* target); | ||
void endWithJump(const uint8_t* cip, Block* target); | ||
void end(const uint8_t* end_at, BlockEnd end_type); | ||
|
||
bool visited() const; | ||
void setVisited(); | ||
|
||
// Zap all references so the block has no cycles. | ||
void unlink(); | ||
|
||
private: | ||
ControlFlowGraph& graph_; | ||
ke::Vector<ke::RefPtr<Block>> predecessors_; | ||
ke::Vector<ke::RefPtr<Block>> successors_; | ||
|
||
// Note that |end| is dependent on end_type. If it's Insn, then end_ should | ||
// be the |start| of the last instruction, since that is the terminating | ||
// instruction. | ||
const uint8_t* start_; | ||
const uint8_t* end_; | ||
BlockEnd end_type_; | ||
|
||
// Counter for fast already-visited testing. | ||
uint32_t epoch_; | ||
}; | ||
|
||
class ControlFlowGraph : public ke::Refcounted<ControlFlowGraph> | ||
{ | ||
public: | ||
explicit ControlFlowGraph(PluginRuntime* rt, const uint8_t* start_offset); | ||
~ControlFlowGraph(); | ||
|
||
ke::RefPtr<Block> entry() const { | ||
return entry_; | ||
} | ||
|
||
ke::RefPtr<Block> newBlock(const uint8_t* start); | ||
|
||
// Increase the epoch number, effectively resetting which blcoks have been | ||
// visited. | ||
void newEpoch() { | ||
epoch_++; | ||
} | ||
uint32_t epoch() const { | ||
return epoch_; | ||
} | ||
|
||
void dump(FILE* fp); | ||
|
||
private: | ||
PluginRuntime* rt_; | ||
ke::RefPtr<Block> entry_; | ||
ke::Vector<ke::RefPtr<Block>> blocks_; | ||
uint32_t epoch_; | ||
}; | ||
|
||
} // namespace sp | ||
|
||
#endif // _include_sourcepawn_vm_control_flow_h_ |
Oops, something went wrong.