Skip to content

Commit 1b92fc0

Browse files
a new exploring strategy: exit when all branches are covered
1 parent 8971eb5 commit 1b92fc0

File tree

4 files changed

+63
-19
lines changed

4 files changed

+63
-19
lines changed

headers/wasm/concolic_driver.hpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,25 @@
1111
#include <string>
1212
#include <vector>
1313

14+
enum class ExploreMode { EarlyExit, ExitByCoverage };
15+
16+
#ifdef EARLY_EXIT
17+
static const ExploreMode EXPLORE_MODE = ExploreMode::EarlyExit;
18+
#elif defined(BY_COVERAGE)
19+
static const ExploreMode EXPLORE_MODE = ExploreMode::ExitByCoverage;
20+
#else
21+
static const ExploreMode EXPLORE_MODE = ExploreMode::EarlyExit;
22+
#endif
23+
1424
class ConcolicDriver {
1525
friend class ManagedConcolicCleanup;
1626

1727
public:
1828
ConcolicDriver(std::function<void()> entrypoint,
1929
std::optional<std::string> tree_file, int branchCount)
2030
: entrypoint(entrypoint), tree_file(tree_file) {
21-
ExploreTree.branch_cov_map.assign(branchCount, false);
31+
ExploreTree.true_branch_cov_map.assign(branchCount, false);
32+
ExploreTree.false_branch_cov_map.assign(branchCount, false);
2233
}
2334
void run();
2435

@@ -76,7 +87,15 @@ inline void ConcolicDriver::run() {
7687
ExploreTree.fillFailedNode();
7788
GENSYM_INFO("Caught runtime error with symbolic environment:");
7889
GENSYM_INFO(SymEnv.to_string());
79-
return;
90+
switch (EXPLORE_MODE) {
91+
case ExploreMode::EarlyExit:
92+
return;
93+
case ExploreMode::ExitByCoverage:
94+
if (ExploreTree.all_branch_covered()) {
95+
GENSYM_INFO("All branches covered, exiting...");
96+
return;
97+
}
98+
}
8099
}
81100
#if defined(RUN_ONCE)
82101
return;

headers/wasm/symbolic_rt.hpp

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ struct NodeBox {
291291
std::unique_ptr<Node> node;
292292
NodeBox *parent;
293293

294-
std::monostate fillIfElseNode(SymVal cond);
294+
std::monostate fillIfElseNode(SymVal cond, int id);
295295
std::monostate fillFinishedNode();
296296
std::monostate fillFailedNode();
297297
std::monostate fillUnreachableNode();
@@ -344,10 +344,11 @@ struct IfElseNode : Node {
344344
SymVal cond;
345345
std::unique_ptr<NodeBox> true_branch;
346346
std::unique_ptr<NodeBox> false_branch;
347+
int id;
347348

348-
IfElseNode(SymVal cond, NodeBox *parent)
349+
IfElseNode(SymVal cond, NodeBox *parent, int id)
349350
: cond(cond), true_branch(std::make_unique<NodeBox>(parent)),
350-
false_branch(std::make_unique<NodeBox>(parent)) {}
351+
false_branch(std::make_unique<NodeBox>(parent)), id(id) {}
351352

352353
std::string to_string() override {
353354
std::string result = "IfElseNode {\n";
@@ -480,10 +481,10 @@ inline NodeBox::NodeBox(NodeBox *parent)
480481
/* TODO: avoid allocation of unexplored node */
481482
parent(parent) {}
482483

483-
inline std::monostate NodeBox::fillIfElseNode(SymVal cond) {
484+
inline std::monostate NodeBox::fillIfElseNode(SymVal cond, int id) {
484485
// fill the current NodeBox with an ifelse branch node when it's unexplored
485486
if (this->isUnexplored()) {
486-
node = std::make_unique<IfElseNode>(cond, this);
487+
node = std::make_unique<IfElseNode>(cond, this, id);
487488
}
488489
assert(
489490
dynamic_cast<IfElseNode *>(node.get()) != nullptr &&
@@ -582,8 +583,7 @@ class ExploreTree_t {
582583
std::monostate fillFailedNode() { return cursor->fillFailedNode(); }
583584

584585
std::monostate fillIfElseNode(SymVal cond, int id) {
585-
branch_cov_map[id] = true;
586-
return cursor->fillIfElseNode(cond);
586+
return cursor->fillIfElseNode(cond, id);
587587
}
588588

589589
std::monostate moveCursor(bool branch, Snapshot_t snapshot) {
@@ -593,9 +593,11 @@ class ExploreTree_t {
593593
if_else_node != nullptr &&
594594
"Can't move cursor when the branch node is not initialized correctly!");
595595
if (branch) {
596+
true_branch_cov_map[if_else_node->id] = true;
596597
if_else_node->false_branch->fillSnapshotNode(snapshot);
597598
cursor = if_else_node->true_branch.get();
598599
} else {
600+
false_branch_cov_map[if_else_node->id] = true;
599601
if_else_node->true_branch->fillSnapshotNode(snapshot);
600602
cursor = if_else_node->false_branch.get();
601603
}
@@ -637,9 +639,14 @@ class ExploreTree_t {
637639
// For now, we just iterate through the tree and return the first unexplored
638640
return pick_unexplored_of(root.get());
639641
}
640-
std::vector<bool> branch_cov_map;
642+
std::vector<bool> true_branch_cov_map;
643+
std::vector<bool> false_branch_cov_map;
641644
bool all_branch_covered() const {
642-
for (bool covered : branch_cov_map) {
645+
for (bool covered : true_branch_cov_map) {
646+
if (!covered)
647+
return false;
648+
}
649+
for (bool covered : false_branch_cov_map) {
643650
if (!covered)
644651
return false;
645652
}

src/main/scala/wasm/StagedConcolicMiniWasm.scala

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,30 @@ import gensym.structure.freer.Explore
2121

2222
object Counter {
2323
var currentId: Int = 0
24-
private val dict = new HashMap[WIR, Int]()
24+
25+
// WIR is the branch's corresponding ast, while the Int stands for the nth
26+
// branch of the AST(a WIR may contain multiple branches, e.g., br_table)
27+
private val dict = new HashMap[(WIR, Int), Int]()
2528

2629
def reset(): Unit = {
2730
currentId = 0
2831
dict.clear()
2932
}
3033

31-
def getId(wir: WIR): Int = {
32-
if (dict.contains(wir)) {
33-
dict(wir)
34+
def getId(wir: WIR, nth: Int = 0): Int = {
35+
if (dict.contains((wir, nth))) {
36+
dict((wir, nth))
3437
} else {
3538
val id = currentId
3639
currentId += 1
37-
dict(wir) = id
40+
dict((wir, nth)) = id
3841
id
3942
}
43+
44+
}
45+
46+
def getId(wir: WIR): Int = {
47+
getId(wir, 0)
4048
}
4149
}
4250
@virtualize
@@ -445,7 +453,7 @@ trait StagedWasmEvaluator extends SAIOps {
445453
val labelSym = Stack.peekS(ty)
446454
val cond = (label - toStagedNum(I32V(idx))).isZero()
447455
val condSym = (labelSym - toStagedSymbolicNum(I32V(idx))).isZero()
448-
val id = Counter.getId(inst)
456+
val id = Counter.getId(inst, idx)
449457
ExploreTree.fillWithIfElse(condSym.s, id)
450458
// When moving the cursor to a branch, we mark another branch as
451459
// snapshotNode (this is done by moveCursor's runtime implementation)

src/test/scala/genwasym/TestStagedConcolicEval.scala

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import gensym.wasm.parser._
99
import gensym.wasm.stagedconcolicminiwasm._
1010

1111
class TestStagedConcolicEval extends FunSuite {
12-
def testFileConcolicCpp(filename: String, main: Option[String] = None) = {
12+
def testFileConcolicCpp(filename: String,
13+
main: Option[String] = None,
14+
exitByCoverage: Boolean = false) = {
1315
val moduleInst = ModuleInstance(Parser.parseFile(filename))
1416
val cppFile = s"$filename.cpp"
1517
val exe = s"$cppFile.exe"
1618
val exploreTreeFile = s"$filename.tree.dot"
17-
WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true)
19+
WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, if (exitByCoverage) "BY_COVERAGE" else "EARLY_EXIT")
1820

1921
import sys.process._
2022
val result = Process(s"./$exe", None, "TREE_FILE" -> exploreTreeFile).!!
@@ -52,6 +54,14 @@ class TestStagedConcolicEval extends FunSuite {
5254
testFileConcolicCpp("./benchmarks/wasm/staged/brtable_concolic.wat")
5355
}
5456

57+
test("bug-finding-cov-concolic") {
58+
testFileConcolicCpp("./benchmarks/wasm/branch-strip-buggy.wat", Some("real_main"), exitByCoverage=true)
59+
}
60+
61+
test("brtable-bug-finding-cov-concolic") {
62+
testFileConcolicCpp("./benchmarks/wasm/staged/brtable_concolic.wat", exitByCoverage=true)
63+
}
64+
5565
test("return-poly - concrete") {
5666
testFileConcreteCpp("./benchmarks/wasm/staged/return_poly.wat", Some("$real_main"), expect=Some(List(42)))
5767
}

0 commit comments

Comments
 (0)