Add an afl-based fuzzer for notifications
This commit is contained in:
parent
f4aaa7c9de
commit
8c94cd1b2c
|
@ -16,4 +16,5 @@ use_realm_core(${REALM_CORE_VERSION})
|
|||
include_directories(${REALM_CORE_INCLUDE_DIR} src external/pegtl)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(fuzzer)
|
||||
add_subdirectory(tests)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
add_executable(fuzzer fuzzer.cpp)
|
||||
target_link_libraries(fuzzer realm-object-store)
|
|
@ -0,0 +1,364 @@
|
|||
#include "object_schema.hpp"
|
||||
#include "property.hpp"
|
||||
#include "results.hpp"
|
||||
#include "schema.hpp"
|
||||
#include "impl/realm_coordinator.hpp"
|
||||
|
||||
#include <realm/commit_log.hpp>
|
||||
#include <realm/disable_sync_to_disk.hpp>
|
||||
#include <realm/group_shared.hpp>
|
||||
#include <realm/link_view.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <strstream>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace realm;
|
||||
|
||||
#define FUZZ_SORTED 1
|
||||
|
||||
#if 0
|
||||
#define log(...) fprintf(stderr, __VA_ARGS__)
|
||||
#else
|
||||
#define log(...)
|
||||
#endif
|
||||
|
||||
// Read from a fd until eof into a string
|
||||
// Needs to use unbuffered i/o to work properly with afl
|
||||
static void read_all(std::string& buffer, int fd)
|
||||
{
|
||||
buffer.clear();
|
||||
size_t offset = 0;
|
||||
while (true) {
|
||||
buffer.resize(offset + 4096);
|
||||
ssize_t bytes_read = read(fd, &buffer[offset], 4096);
|
||||
if (bytes_read < 4096) {
|
||||
buffer.resize(offset + bytes_read);
|
||||
break;
|
||||
}
|
||||
offset += 4096;
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<int64_t> read_initial_values(std::istream& input_stream)
|
||||
{
|
||||
std::vector<int64_t> initial_values;
|
||||
std::string line;
|
||||
input_stream.seekg(0);
|
||||
while (std::getline(input_stream, line) && !line.empty()) {
|
||||
try {
|
||||
initial_values.push_back(std::stoll(line));
|
||||
}
|
||||
catch (std::invalid_argument) {
|
||||
// not an error
|
||||
}
|
||||
catch (std::out_of_range) {
|
||||
// not an error
|
||||
}
|
||||
}
|
||||
return initial_values;
|
||||
}
|
||||
|
||||
struct Change {
|
||||
enum class Action {
|
||||
Commit,
|
||||
Add,
|
||||
Modify,
|
||||
Delete
|
||||
} action;
|
||||
|
||||
size_t index = npos;
|
||||
int64_t value = 0;
|
||||
};
|
||||
|
||||
static std::vector<Change> read_changes(std::istream& input_stream)
|
||||
{
|
||||
std::vector<Change> ret;
|
||||
|
||||
while (!input_stream.eof()) {
|
||||
char op = '\0';
|
||||
input_stream >> op;
|
||||
if (!input_stream.good())
|
||||
break;
|
||||
switch (op) {
|
||||
case 'a': {
|
||||
int64_t value = 0;
|
||||
input_stream >> value;
|
||||
if (input_stream.good())
|
||||
ret.push_back({Change::Action::Add, npos, value});
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
int64_t value;
|
||||
size_t ndx;
|
||||
input_stream >> ndx >> value;
|
||||
if (input_stream.good())
|
||||
ret.push_back({Change::Action::Modify, ndx, value});
|
||||
break;
|
||||
}
|
||||
case 'd': {
|
||||
size_t ndx;
|
||||
input_stream >> ndx;
|
||||
if (input_stream.good())
|
||||
ret.push_back({Change::Action::Delete, ndx, 0});
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
ret.push_back({Change::Action::Commit, npos, 0});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return ret;
|
||||
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static Query query(Table& table)
|
||||
{
|
||||
return table.where().greater(1, 100).less(1, 50000);
|
||||
}
|
||||
|
||||
static TableView tableview(Table& table)
|
||||
{
|
||||
auto tv = table.where().greater(1, 100).less(1, 50000).find_all();
|
||||
#if FUZZ_SORTED
|
||||
tv.sort({1, 0}, {true, true});
|
||||
#endif
|
||||
return tv;
|
||||
}
|
||||
|
||||
static int64_t id = 0;
|
||||
|
||||
static void import_initial_values(SharedRealm& r, std::vector<int64_t>& initial_values)
|
||||
{
|
||||
auto& table = *r->read_group()->get_table("class_object");
|
||||
|
||||
r->begin_transaction();
|
||||
table.clear();
|
||||
size_t ndx = table.add_empty_row(initial_values.size());
|
||||
for (auto value : initial_values) {
|
||||
table.set_int(0, ndx, id++);
|
||||
table.set_int(1, ndx++, value);
|
||||
log("%lld\n", value);
|
||||
}
|
||||
r->commit_transaction();
|
||||
}
|
||||
|
||||
// Apply the changes from the command file and then return whether a change
|
||||
// notification should occur
|
||||
static bool apply_changes(Realm& r, std::istream& input_stream)
|
||||
{
|
||||
auto& table = *r.read_group()->get_table("class_object");
|
||||
auto tv = tableview(table);
|
||||
|
||||
std::vector<int64_t> modified;
|
||||
|
||||
log("\n");
|
||||
r.begin_transaction();
|
||||
for (auto const& change : read_changes(input_stream)) {
|
||||
switch (change.action) {
|
||||
case Change::Action::Commit:
|
||||
log("c\n");
|
||||
r.commit_transaction();
|
||||
_impl::RealmCoordinator::get_existing_coordinator(r.config().path)->on_change();
|
||||
r.begin_transaction();
|
||||
break;
|
||||
|
||||
case Change::Action::Add: {
|
||||
log("a %lld\n", change.value);
|
||||
size_t ndx = table.add_empty_row();
|
||||
table.set_int(0, ndx, id++);
|
||||
table.set_int(1, ndx, change.value);
|
||||
break;
|
||||
}
|
||||
|
||||
case Change::Action::Modify:
|
||||
if (change.index < table.size()) {
|
||||
log("m %zu %lld\n", change.index, change.value);
|
||||
modified.push_back(table.get_int(0, change.index));
|
||||
table.set_int(1, change.index, change.value);
|
||||
}
|
||||
break;
|
||||
|
||||
case Change::Action::Delete:
|
||||
if (change.index < table.size()) {
|
||||
log("d %zu\n", change.index);
|
||||
table.move_last_over(change.index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
r.commit_transaction();
|
||||
log("\n");
|
||||
|
||||
auto tv2 = tableview(table);
|
||||
if (tv.size() != tv2.size())
|
||||
return true;
|
||||
|
||||
for (size_t i = 0; i < tv.size(); ++i) {
|
||||
if (!tv.is_row_attached(i))
|
||||
return true;
|
||||
if (tv.get_int(0, i) != tv2.get_int(0, i))
|
||||
return true;
|
||||
if (find(begin(modified), end(modified), tv.get_int(0, i)) != end(modified))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void verify(CollectionChangeIndices const& changes, std::vector<int64_t> values, Table& table)
|
||||
{
|
||||
auto tv = tableview(table);
|
||||
|
||||
// Apply the changes from the transaction log to our copy of the
|
||||
// initial, using UITableView's batching rules (i.e. delete, then
|
||||
// insert, then update)
|
||||
auto it = std::make_reverse_iterator(changes.deletions.end()), end = std::make_reverse_iterator(changes.deletions.begin());
|
||||
for (; it != end; ++it) {
|
||||
values.erase(values.begin() + it->first, values.begin() + it->second);
|
||||
}
|
||||
|
||||
for (auto i : changes.insertions.as_indexes()) {
|
||||
values.insert(values.begin() + i, tv.get_int(1, i));
|
||||
}
|
||||
|
||||
if (values.size() != tv.size()) {
|
||||
abort();
|
||||
}
|
||||
|
||||
for (auto i : changes.modifications.as_indexes()) {
|
||||
if (changes.insertions.contains(i))
|
||||
abort();
|
||||
values[i] = tv.get_int(1, i);
|
||||
}
|
||||
|
||||
#if FUZZ_SORTED
|
||||
if (!std::is_sorted(values.begin(), values.end()))
|
||||
abort();
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
if (values[i] != tv.get_int(1, i)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test(Realm::Config const& config, SharedRealm& r, SharedRealm& r2, std::istream& input_stream)
|
||||
{
|
||||
std::vector<int64_t> initial_values = read_initial_values(input_stream);
|
||||
if (initial_values.empty()) {
|
||||
return;
|
||||
}
|
||||
import_initial_values(r, initial_values);
|
||||
|
||||
auto& table = *r->read_group()->get_table("class_object");
|
||||
auto results = Results(r, ObjectSchema(), query(table))
|
||||
#if FUZZ_SORTED
|
||||
.sort({{1, 0}, {true, true}})
|
||||
#endif
|
||||
;
|
||||
|
||||
initial_values.clear();
|
||||
for (size_t i = 0; i < results.size(); ++i)
|
||||
initial_values.push_back(results.get(i).get_int(1));
|
||||
|
||||
CollectionChangeIndices changes;
|
||||
int notification_calls = 0;
|
||||
auto token = results.add_notification_callback([&](CollectionChangeIndices c, std::exception_ptr err) {
|
||||
if (notification_calls > 0 && c.empty())
|
||||
abort();
|
||||
changes = c;
|
||||
++notification_calls;
|
||||
});
|
||||
|
||||
auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path);
|
||||
coordinator.on_change(); r->notify();
|
||||
if (notification_calls != 1) {
|
||||
abort();
|
||||
}
|
||||
|
||||
bool expect_notification = apply_changes(*r2, input_stream);
|
||||
coordinator.on_change(); r->notify();
|
||||
|
||||
if (notification_calls != 1 + expect_notification) {
|
||||
abort();
|
||||
}
|
||||
|
||||
verify(changes, initial_values, table);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::ios_base::sync_with_stdio(false);
|
||||
realm::disable_sync_to_disk();
|
||||
|
||||
Realm::Config config;
|
||||
config.path = getenv("TMPDIR");
|
||||
config.path += "/realm.XXXXXX";
|
||||
mktemp(&config.path[0]);
|
||||
config.cache = false;
|
||||
config.in_memory = true;
|
||||
config.automatic_change_notifications = false;
|
||||
|
||||
Schema schema = {
|
||||
{"object", "", {
|
||||
{"id", PropertyTypeInt},
|
||||
{"value", PropertyTypeInt}
|
||||
}}
|
||||
};
|
||||
|
||||
config.schema = std::make_unique<Schema>(schema);
|
||||
unlink(config.path.c_str());
|
||||
|
||||
auto r = Realm::get_shared_realm(config);
|
||||
auto r2 = Realm::get_shared_realm(config);
|
||||
auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path);
|
||||
|
||||
auto test_on = [&](auto& buffer) {
|
||||
id = 0;
|
||||
|
||||
std::istringstream ss(buffer);
|
||||
test(config, r, r2, ss);
|
||||
if (r->is_in_transaction())
|
||||
r->cancel_transaction();
|
||||
r2->invalidate();
|
||||
coordinator.on_change();
|
||||
};
|
||||
|
||||
if (argc > 1) {
|
||||
std::string buffer;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
int fd = open(argv[i], O_RDONLY);
|
||||
if (fd < 0)
|
||||
abort();
|
||||
read_all(buffer, fd);
|
||||
close(fd);
|
||||
|
||||
test_on(buffer);
|
||||
}
|
||||
unlink(config.path.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __AFL_HAVE_MANUAL_CONTROL
|
||||
std::string buffer;
|
||||
while (__AFL_LOOP(1000)) {
|
||||
read_all(buffer, 0);
|
||||
test_on(buffer);
|
||||
}
|
||||
#else
|
||||
std::string buffer;
|
||||
read_all(buffer, 0);
|
||||
test_on(buffer);
|
||||
#endif
|
||||
|
||||
unlink(config.path.c_str());
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
3
|
||||
100
|
||||
200
|
||||
400
|
||||
1000
|
||||
2000
|
||||
50
|
||||
80
|
||||
150
|
||||
180
|
||||
6000
|
||||
5000
|
||||
60000
|
||||
|
||||
a 500
|
||||
d 12
|
||||
c
|
||||
m 11 10000
|
||||
a 800
|
|
@ -0,0 +1,33 @@
|
|||
101
|
||||
102
|
||||
103
|
||||
104
|
||||
105
|
||||
106
|
||||
107
|
||||
108
|
||||
109
|
||||
110
|
||||
111
|
||||
112
|
||||
113
|
||||
|
||||
a 114
|
||||
a 115
|
||||
a 116
|
||||
a 117
|
||||
a 118
|
||||
a 119
|
||||
a 120
|
||||
a 121
|
||||
a 122
|
||||
c
|
||||
m 4 200
|
||||
m 3 201
|
||||
m 2 202
|
||||
m 1 203
|
||||
m 5 203
|
||||
m 6 204
|
||||
m 7 205
|
||||
c
|
||||
d 11
|
Loading…
Reference in New Issue