diff --git a/fuzzer/CMakeLists.txt b/fuzzer/CMakeLists.txt index 65df5418..b646497a 100644 --- a/fuzzer/CMakeLists.txt +++ b/fuzzer/CMakeLists.txt @@ -1,2 +1,5 @@ -add_executable(fuzzer fuzzer.cpp) +add_executable(fuzzer + command_file.hpp + command_file.cpp + fuzzer.cpp) target_link_libraries(fuzzer realm-object-store) diff --git a/fuzzer/command_file.cpp b/fuzzer/command_file.cpp new file mode 100644 index 00000000..28123abe --- /dev/null +++ b/fuzzer/command_file.cpp @@ -0,0 +1,222 @@ +#include "command_file.hpp" + +#include "impl/realm_coordinator.hpp" +#include "shared_realm.hpp" + +#include +#include + +#include + +using namespace fuzzer; +using namespace realm; + +#if 0 +#define log(...) fprintf(stderr, __VA_ARGS__) +#else +#define log(...) +#endif + +template +static T read_value(std::istream& input) +{ + T ret; + input >> ret; + return ret; +} + +template +static auto make_reader(void (*fn)(RealmState&, Args...)) { + return [=](std::istream& input) { + return std::bind(fn, std::placeholders::_1, read_value(input)...); + }; +} + +static void run_add(RealmState& state, int64_t value) +{ + log("add %lld\n", value); + size_t ndx = state.table.add_empty_row(); + state.table.set_int(0, ndx, state.uid++); + state.table.set_int(1, ndx, value); +} + +static void run_modify(RealmState& state, size_t index, int64_t value) +{ + if (index < state.table.size()) { + log("modify %zu %lld\n", index, value); + state.table.set_int(1, index, value); + state.modified.push_back(state.table.get_int(0, index)); + } +} + +static void run_delete(RealmState& state, size_t index) +{ + if (index < state.table.size()) { + log("delete %zu\n", index); + state.table.move_last_over(index); + } +} + +static void run_commit(RealmState& state) +{ + log("commit\n"); + state.realm.commit_transaction(); + state.coordinator.on_change(); + state.realm.begin_transaction(); +} + +static void run_lv_insert(RealmState& state, size_t pos, size_t target) +{ + if (target < state.table.size() && pos <= state.lv->size()) { + log("lv insert %zu %zu\n", pos, target); + state.lv->insert(pos, target); + } +} + +static void run_lv_set(RealmState& state, size_t pos, size_t target) +{ + if (target < state.table.size() && pos < state.lv->size()) { + log("lv set %zu %zu\n", pos, target); + // We can't reliably detect self-assignment for verification, so don't do it + if (state.lv->get(pos).get_index() != target) + state.lv->set(pos, target); + } +} + +static void run_lv_move(RealmState& state, size_t from, size_t to) +{ + if (from < state.lv->size() && to < state.lv->size()) { + log("lv move %zu %zu\n", from, to); + // FIXME: only do the move if it has an effect to avoid getting a + // notification which we weren't expecting. This is really urgh. + for (size_t i = std::min(from, to); i < std::max(from, to); ++i) { + if (state.lv->get(i).get_index() != state.lv->get(i + 1).get_index()) { + state.lv->move(from, to); + break; + } + } + } +} + +static void run_lv_swap(RealmState& state, size_t ndx1, size_t ndx2) +{ + if (ndx1 < state.lv->size() && ndx2 < state.lv->size()) { + log("lv swap %zu %zu\n", ndx1, ndx2); + if (state.lv->get(ndx1).get_index() != state.lv->get(ndx2).get_index()) { + state.lv->swap(ndx1, ndx2); + // FIXME: swap() needs to produce moves so that a pair of swaps can + // be collapsed away. Currently it just marks the rows as modified. + state.modified.push_back(state.lv->get(ndx1).get_int(0)); + state.modified.push_back(state.lv->get(ndx2).get_int(0)); + } + } +} + +static void run_lv_remove(RealmState& state, size_t pos) +{ + if (pos < state.lv->size()) { + log("lv remove %zu\n", pos); + state.lv->remove(pos); + } +} + +static void run_lv_remove_target(RealmState& state, size_t pos) +{ + if (pos < state.lv->size()) { + log("lv target remove %zu\n", pos); + state.lv->remove_target_row(pos); + } +} + +static std::map(std::istream&)>> readers = { + // Row functions + {'a', make_reader(run_add)}, + {'c', make_reader(run_commit)}, + {'d', make_reader(run_delete)}, + {'m', make_reader(run_modify)}, + + // LinkView functions + {'i', make_reader(run_lv_insert)}, + {'s', make_reader(run_lv_set)}, + {'o', make_reader(run_lv_move)}, + {'w', make_reader(run_lv_swap)}, + {'r', make_reader(run_lv_remove)}, + {'t', make_reader(run_lv_remove_target)}, +}; + +template +static std::vector read_int_list(std::istream& input_stream) +{ + std::vector ret; + std::string line; + while (std::getline(input_stream, line) && !line.empty()) { + try { + ret.push_back(std::stoll(line)); + log("%lld\n", (long long)ret.back()); + } + catch (std::invalid_argument) { + // not an error + } + catch (std::out_of_range) { + // not an error + } + } + log("\n"); + return ret; +} + +CommandFile::CommandFile(std::istream& input) +: initial_values(read_int_list(input)) +, initial_list_indices(read_int_list(input)) +{ + if (!input.good()) + return; + + while (input.good()) { + char op = '\0'; + input >> op; + if (!input.good()) + break; + + auto it = readers.find(op); + if (it == readers.end()) + continue; + + auto fn = it->second(input); + if (!input.good()) + return; + commands.push_back(std::move(fn)); + } +} + +void CommandFile::import(RealmState& state) +{ + auto& table = state.table; + + state.realm.begin_transaction(); + + table.clear(); + size_t ndx = table.add_empty_row(initial_values.size()); + for (auto value : initial_values) { + table.set_int(0, ndx, state.uid++); + table.set_int(1, ndx++, value); + } + + state.lv->clear(); + for (auto value : initial_list_indices) { + if (value < table.size()) + state.lv->add(value); + } + + state.realm.commit_transaction(); + +} + +void CommandFile::run(RealmState& state) +{ + state.realm.begin_transaction(); + for (auto& command : commands) { + command(state); + } + state.realm.commit_transaction(); +} diff --git a/fuzzer/command_file.hpp b/fuzzer/command_file.hpp new file mode 100644 index 00000000..f06f5433 --- /dev/null +++ b/fuzzer/command_file.hpp @@ -0,0 +1,38 @@ +#include + +#include +#include +#include +#include + +namespace realm { + class Table; + class LinkView; + class Realm; + namespace _impl { + class RealmCoordinator; + } +} + +namespace fuzzer { +struct RealmState { + realm::Realm& realm; + realm::_impl::RealmCoordinator& coordinator; + + realm::Table& table; + realm::LinkViewRef lv; + int64_t uid = 0; + std::vector modified; +}; + +struct CommandFile { + std::vector initial_values; + std::vector initial_list_indices; + std::vector> commands; + + CommandFile(std::istream& input); + + void import(RealmState& state); + void run(RealmState& state); +}; +} \ No newline at end of file diff --git a/fuzzer/fuzzer.cpp b/fuzzer/fuzzer.cpp index 19a913af..3a6d5648 100644 --- a/fuzzer/fuzzer.cpp +++ b/fuzzer/fuzzer.cpp @@ -1,3 +1,6 @@ +#include "command_file.hpp" + +#include "list.hpp" #include "object_schema.hpp" #include "property.hpp" #include "results.hpp" @@ -10,22 +13,15 @@ #include #include -#include #include -#include #include #include #include using namespace realm; -#define FUZZ_SORTED 1 - -#if 0 -#define log(...) fprintf(stderr, __VA_ARGS__) -#else -#define log(...) -#endif +#define FUZZ_SORTED 0 +#define FUZZ_LINKVIEW 1 // Read from a fd until eof into a string // Needs to use unbuffered i/o to work properly with afl @@ -44,160 +40,32 @@ static void read_all(std::string& buffer, int fd) } } -static std::vector read_initial_values(std::istream& input_stream) +static Query query(fuzzer::RealmState& state) { - std::vector 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; +#if FUZZ_LINKVIEW + return state.table.where(state.lv); +#else + return state.table.where().greater(1, 100).less(1, 50000); +#endif } -struct Change { - enum class Action { - Commit, - Add, - Modify, - Delete - } action; - - size_t index = npos; - int64_t value = 0; -}; - -static std::vector read_changes(std::istream& input_stream) +static TableView tableview(fuzzer::RealmState& state) { - std::vector 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(); + auto tv = query(state).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& 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) +static bool apply_changes(fuzzer::CommandFile& commands, fuzzer::RealmState& state) { - auto& table = *r.read_group()->get_table("class_object"); - auto tv = tableview(table); + auto tv = tableview(state); + commands.run(state); - std::vector 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); + auto tv2 = tableview(state); if (tv.size() != tv2.size()) return true; @@ -206,16 +74,16 @@ static bool apply_changes(Realm& r, std::istream& input_stream) 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)) + if (find(begin(state.modified), end(state.modified), tv.get_int(0, i)) != end(state.modified)) return true; } return false; } -static void verify(CollectionChangeIndices const& changes, std::vector values, Table& table) +static void verify(CollectionChangeIndices const& changes, std::vector values, fuzzer::RealmState& state) { - auto tv = tableview(table); + auto tv = tableview(state); // Apply the changes from the transaction log to our copy of the // initial, using UITableView's batching rules (i.e. delete, then @@ -253,20 +121,41 @@ static void verify(CollectionChangeIndices const& changes, std::vector static void test(Realm::Config const& config, SharedRealm& r, SharedRealm& r2, std::istream& input_stream) { - std::vector initial_values = read_initial_values(input_stream); - if (initial_values.empty()) { + fuzzer::RealmState state = { + *r, + *_impl::RealmCoordinator::get_existing_coordinator(r->config().path), + *r->read_group()->get_table("class_object"), + r->read_group()->get_table("class_linklist")->get_linklist(0, 0), + 0, + {} + }; + + fuzzer::CommandFile command(input_stream); + if (command.initial_values.empty()) { return; } - import_initial_values(r, initial_values); + command.import(state); - auto& table = *r->read_group()->get_table("class_object"); - auto results = Results(r, ObjectSchema(), query(table)) + fuzzer::RealmState state2 = { + *r2, + state.coordinator, + *r2->read_group()->get_table("class_object"), + r2->read_group()->get_table("class_linklist")->get_linklist(0, 0), + state.uid, + {} + }; + +#if FUZZ_LINKVIEW && !FUZZ_SORTED + auto results = List(r, ObjectSchema(), state.lv); +#else + auto results = Results(r, ObjectSchema(), query(state)) #if FUZZ_SORTED .sort({{1, 0}, {true, true}}) #endif ; +#endif // FUZZ_LINKVIEW - initial_values.clear(); + std::vector initial_values; for (size_t i = 0; i < results.size(); ++i) initial_values.push_back(results.get(i).get_int(1)); @@ -279,20 +168,19 @@ static void test(Realm::Config const& config, SharedRealm& r, SharedRealm& r2, s ++notification_calls; }); - auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path); - coordinator.on_change(); r->notify(); + state.coordinator.on_change(); r->notify(); if (notification_calls != 1) { abort(); } - bool expect_notification = apply_changes(*r2, input_stream); - coordinator.on_change(); r->notify(); + bool expect_notification = apply_changes(command, state2); + state.coordinator.on_change(); r->notify(); if (notification_calls != 1 + expect_notification) { abort(); } - verify(changes, initial_values, table); + verify(changes, initial_values, state); } int main(int argc, char** argv) { @@ -311,6 +199,9 @@ int main(int argc, char** argv) { {"object", "", { {"id", PropertyTypeInt}, {"value", PropertyTypeInt} + }}, + {"linklist", "", { + {"list", PropertyTypeArray, "object"} }} }; @@ -321,9 +212,11 @@ int main(int argc, char** argv) { auto r2 = Realm::get_shared_realm(config); auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path); - auto test_on = [&](auto& buffer) { - id = 0; + r->begin_transaction(); + r->read_group()->get_table("class_linklist")->add_empty_row(); + r->commit_transaction(); + auto test_on = [&](auto& buffer) { std::istringstream ss(buffer); test(config, r, r2, ss); if (r->is_in_transaction()) diff --git a/fuzzer/input-lv/0 b/fuzzer/input-lv/0 new file mode 100644 index 00000000..cb6a3bd9 --- /dev/null +++ b/fuzzer/input-lv/0 @@ -0,0 +1,38 @@ +3 +100 +200 +400 +1000 +2000 +50 +80 +150 +180 +6000 +5000 +60000 + +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 + +a 500 +d 12 +c +m 11 10000 +a 800 +i 5 13 +s 3 8 +o 2 10 +w 1 6 +r 7 +t 11 diff --git a/fuzzer/input/0 b/fuzzer/input/0 index 50132fd0..675ab157 100644 --- a/fuzzer/input/0 +++ b/fuzzer/input/0 @@ -12,6 +12,7 @@ 5000 60000 + a 500 d 12 c diff --git a/fuzzer/input/1 b/fuzzer/input/1 index 483456fa..1cbf4855 100644 --- a/fuzzer/input/1 +++ b/fuzzer/input/1 @@ -12,6 +12,7 @@ 112 113 + a 114 a 115 a 116