Files
coven/modules/ufbx/test/domfuzz/domfuzz_main.cpp

431 lines
10 KiB
C++

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <cctype>
static void ufbxt_assert_fail_imp(const char *func, const char *file, size_t line, const char *msg)
{
fprintf(stderr, "%s:%zu: %s(%s) failed\n", file, line, func, msg);
exit(2);
}
#define ufbxt_assert_fail(file, line, msg) ufbxt_assert_fail_imp("ufbxt_assert_fail", file, line, msg)
#define ufbxt_assert(m_cond) do { if (!(m_cond)) ufbxt_assert_fail_imp("ufbxt_assert", __FILE__, __LINE__, #m_cond); } while (0)
#include "fbxdom.h"
#include <vector>
#include <string>
#include <algorithm>
#include "../../ufbx.h"
#include "../check_scene.h"
bool g_verbose = false;
std::vector<char> read_file(const char *path)
{
FILE *f = fopen(path, "rb");
fseek(f, 0, SEEK_END);
std::vector<char> data;
data.resize(ftell(f));
fseek(f, 0, SEEK_SET);
fread(data.data(), 1, data.size(), f);
fclose(f);
return data;
}
using mutate_simple_fn = bool(fbxdom::node_ptr node, uint32_t index);
bool mutate_remove_child(fbxdom::node_ptr node, uint32_t index)
{
if (index >= node->children.size()) return false;
node->children.erase(node->children.begin() + index);
return true;
}
bool mutate_duplicate_child(fbxdom::node_ptr node, uint32_t index)
{
if (index >= node->children.size()) return false;
fbxdom::node_ptr child = node->children[index];
node->children.insert(node->children.begin() + index, std::move(child));
return true;
}
bool mutate_reverse_children(fbxdom::node_ptr node, uint32_t index)
{
if (index > 0) return false;
std::reverse(node->children.begin(), node->children.end());
return true;
}
bool mutate_remove_value(fbxdom::node_ptr node, uint32_t index)
{
if (node->values.size() > 10) return false;
if (index >= node->values.size()) return false;
node->values.erase(node->values.begin() + index);
return true;
}
bool mutate_duplicate_value(fbxdom::node_ptr node, uint32_t index)
{
if (node->values.size() > 10) return false;
if (index >= node->values.size()) return false;
fbxdom::value value = node->values[index];
node->values.insert(node->values.begin() + index, std::move(value));
return true;
}
bool mutate_clear_value(fbxdom::node_ptr node, uint32_t index)
{
if (node->values.size() > 10) return false;
if (index >= node->values.size()) return false;
fbxdom::value &value = node->values[index];
value.data_int = 0;
value.data_float = 0.0f;
value.data_array.length = 0;
value.data_array.compressed_length = 0;
value.data_array.encoding = 0;
return true;
}
bool mutate_shrink_value(fbxdom::node_ptr node, uint32_t index)
{
if (node->values.size() > 10) return false;
if (index >= node->values.size()) return false;
fbxdom::value &value = node->values[index];
if (value.type == 'L') {
value.type = 'I';
} else if (value.type == 'D') {
value.type = 'F';
} else if (value.type == 'I') {
value.type = 'Y';
} else if (value.type == 'Y') {
value.type = 'C';
} else if (value.type == 'S') {
value.type = 'L';
} else {
value.type = 'I';
}
return true;
}
bool mutate_reverse_values(fbxdom::node_ptr node, uint32_t index)
{
if (index > 0) return false;
std::reverse(node->values.begin(), node->values.end());
return true;
}
struct mutator
{
const char *name;
mutate_simple_fn *simple_fn;
};
static const mutator mutators[] = {
{ "remove child", &mutate_remove_child },
{ "duplicate child", &mutate_duplicate_child },
{ "reverse children", &mutate_reverse_children },
{ "remove value", &mutate_remove_value },
{ "duplicate value", &mutate_duplicate_value },
{ "clear value", &mutate_clear_value },
{ "shrink value", &mutate_shrink_value },
{ "reverse values", &mutate_reverse_values },
};
static const size_t num_mutators = sizeof(mutators) / sizeof(*mutators);
struct mutable_result
{
fbxdom::node_ptr self;
fbxdom::node_ptr target;
};
fbxdom::node_ptr find_path(fbxdom::node_ptr root, const std::vector<uint32_t> &path)
{
fbxdom::node_ptr node = root;
for (uint32_t ix : path) {
node = node->children[ix];
}
return node;
}
void format_path(char *buf, size_t buf_size, fbxdom::node_ptr root, const std::vector<uint32_t> &path)
{
int res = snprintf(buf, buf_size, "Root");
if (res < 0 || res >= buf_size) return;
size_t pos = (size_t)res;
fbxdom::node_ptr node = root;
for (uint32_t ix : path) {
node = node->children[ix];
int res = snprintf(buf + pos, buf_size - pos, "/%u/%.*s", ix, (int)node->name.size(), node->name.data());
pos += res;
if (res < 0 || pos >= buf_size) break;
}
}
fbxdom::node_ptr mutate_path(fbxdom::node_ptr &node, const std::vector<uint32_t> &path, uint32_t depth=0)
{
fbxdom::node_ptr copy = node->copy();
if (depth < path.size()) {
fbxdom::node_ptr &child_ref = copy->children[path[depth]];
fbxdom::node_ptr child = mutate_path(child_ref, path, depth + 1);
node = copy;
return child;
} else {
node = copy;
return copy;
}
}
bool increment_path(fbxdom::node_ptr root, std::vector<uint32_t> &path)
{
{
fbxdom::node_ptr node = find_path(root, path);
if (!node->children.empty()) {
path.push_back(0);
return true;
}
}
while (!path.empty()) {
uint32_t index = path.back();
path.pop_back();
fbxdom::node_ptr current = find_path(root, path);
if (index + 1 < current->children.size()) {
path.push_back(index + 1);
return true;
}
}
return false;
}
struct mutation_iterator
{
std::vector<uint32_t> path = { };
uint32_t mutator_index = 0;
uint32_t mutator_internal_index = 0;
};
fbxdom::node_ptr next_mutation(fbxdom::node_ptr root, mutation_iterator &iter)
{
for (;;) {
fbxdom::node_ptr new_root = root;
fbxdom::node_ptr new_node = mutate_path(new_root, iter.path);
if (mutators[iter.mutator_index].simple_fn(new_node, iter.mutator_internal_index)) {
iter.mutator_internal_index++;
return new_root;
}
iter.mutator_internal_index = 0;
if (iter.mutator_index + 1 < num_mutators) {
iter.mutator_index++;
continue;
}
iter.mutator_index = 0;
if (increment_path(root, iter.path)) {
continue;
}
return { };
}
}
static char dump_buffer[16*1024*1024];
size_t g_coverage_counter = 0;
#if defined(DOMFUZZ_COVERAGE)
extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop)
{
std::fill(start, stop, 1u);
}
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t *guard)
{
if (!*guard) return;
g_coverage_counter++;
*guard = 0;
}
#endif
int selected_step = -1;
int start_step = -1;
size_t coverage_output_count = 0;
bool process_fbx_file(const char *input_file, const char *coverage_path, bool dry_run)
{
std::vector<char> data = read_file(input_file);
{
ufbx_scene *scene = ufbx_load_memory(data.data(), data.size(), NULL, NULL);
if (!scene) return 0;
if (scene->metadata.ascii || scene->metadata.big_endian) {
ufbx_free_scene(scene);
return false;
}
ufbx_free_scene(scene);
}
fbxdom::node_ptr root = fbxdom::parse(data.data(), data.size());
ufbx_error error = { };
{
size_t dump_size = fbxdom::dump(dump_buffer, sizeof(dump_buffer), root);
ufbx_scene *scene = ufbx_load_memory(data.data(), data.size(), NULL, &error);
ufbxt_assert(scene);
ufbxt_check_scene(scene);
ufbx_free_scene(scene);
}
char path_str[512];
mutation_iterator iter;
fbxdom::node_ptr mut_root;
int current_step = 0;
while ((mut_root = next_mutation(root, iter)) != nullptr) {
if (selected_step >= 0) {
if (current_step != selected_step) {
current_step++;
continue;
}
}
if (start_step >= 0) {
if (current_step < start_step) {
current_step++;
continue;
}
}
format_path(path_str, sizeof(path_str), root, iter.path);
if (g_verbose) {
printf("%d: %s/ %s %u: ", current_step, path_str, mutators[iter.mutator_index].name, iter.mutator_internal_index);
}
size_t dump_size = fbxdom::dump(dump_buffer, sizeof(dump_buffer), mut_root);
if (dry_run) {
if (g_verbose) {
printf("SKIP (dry run)\n");
}
continue;
}
size_t pre_cov = g_coverage_counter;
ufbx_scene *scene = ufbx_load_memory(dump_buffer, dump_size, NULL, &error);
size_t post_cov = g_coverage_counter;
if (scene) {
ufbxt_check_scene(scene);
if (g_verbose) {
printf("OK!\n");
}
} else {
if (g_verbose) {
printf("%s\n", error.description.data);
}
}
if (coverage_path && pre_cov != post_cov) {
size_t index = ++coverage_output_count;
char dst_path[1024];
if (!g_verbose) {
printf("%d: %s/ %s %u: ", current_step, path_str, mutators[iter.mutator_index].name, iter.mutator_internal_index);
}
snprintf(dst_path, sizeof(dst_path), "%s%06zu.fbx", coverage_path, coverage_output_count);
printf("%zu new edges! Writing to: %s\n", post_cov - pre_cov, dst_path);
FILE *f = fopen(dst_path, "wb");
ufbxt_assert(f);
ufbxt_assert(fwrite(dump_buffer, 1, dump_size, f) == dump_size);
ufbxt_assert(fclose(f) == 0);
}
ufbx_free_scene(scene);
current_step++;
}
return true;
}
void process_coverage(const char *input_file, const char *output)
{
FILE *f = fopen(input_file, "r");
ufbxt_assert(f);
std::vector<std::string> files;
printf("== DRY RUN ==\n");
char line_buf[1024];
while (fgets(line_buf, sizeof(line_buf), f)) {
std::string line { line_buf };
while (!line.empty() && std::isspace(line.back())) {
line.pop_back();
}
if (line.empty()) continue;
printf("-- %s --\n", line.c_str());
if (!process_fbx_file(line.c_str(), NULL, true)) {
fprintf(stderr, "Failed to load test case\n");
exit(3);
}
files.push_back(std::move(line));
}
fclose(f);
if (g_coverage_counter == 0) {
fprintf(stderr, "No coverage detected.. make sure to compile with DOMFUZZ_COVERAGE");
exit(3);
}
printf("Found %zu initial edges\n", g_coverage_counter);
printf("== REAL RUN ==\n");
for (const std::string &file : files) {
printf("-- %s --\n", file.c_str());
if (!process_fbx_file(file.c_str(), output, false)) {
fprintf(stderr, "Failed to load test case\n");
exit(3);
}
}
}
int main(int argc, char **argv)
{
const char *input_file = NULL;
const char *coverage_output = NULL;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-v")) {
g_verbose = 1;
} else if (!strcmp(argv[i], "--step")) {
if (++i < argc) selected_step = atoi(argv[i]);
} else if (!strcmp(argv[i], "--start")) {
if (++i < argc) start_step = atoi(argv[i]);
} else if (!strcmp(argv[i], "--coverage")) {
if (++i < argc) coverage_output = argv[i];
} else {
input_file = argv[i];
}
}
if (coverage_output) {
process_coverage(input_file, coverage_output);
} else if (input_file) {
process_fbx_file(input_file, NULL, false);
} else {
fprintf(stderr, "Usage: domfuzz <file.fbx>\n");
}
return 0;
}