Getting to the way it's supposed to be!

This commit is contained in:
2024-10-12 00:43:51 +02:00
parent 84729f9d27
commit 8f2dad9cec
2663 changed files with 540071 additions and 14 deletions

View File

@@ -0,0 +1,430 @@
#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;
}

View File

@@ -0,0 +1,331 @@
#include "fbxdom.h"
#include <assert.h>
#include <string.h>
#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86))
#define FBXDOM_NATIVE_WRITE 1
#else
#define FBXDOM_NATIVE_WRITE 0
#endif
namespace fbxdom {
struct binary_reader
{
const char *ptr;
const char *start;
const char *end;
binary_reader(const void *data, size_t size)
: ptr((const char*)data), start((const char*)data)
, end((const char*)data + size) { }
size_t offset() const { return ptr - start; }
void skip(size_t size) {
assert((size_t)(end - ptr) >= size);
ptr += size;
}
template <typename T>
T peek() {
assert((size_t)(end - ptr) >= sizeof(T));
uint64_t value = 0;
for (size_t i = 0; i < sizeof(T); i++) {
value |= (uint64_t)(uint8_t)ptr[i] << i * 8;
}
T result;
memcpy(&result, &value, sizeof(T));
return result;
}
template <typename T>
T read() {
T result = peek<T>();
ptr += sizeof(T);
return result;
}
std::vector<char> read_vector(size_t size)
{
std::vector<char> result;
assert((size_t)(end - ptr) >= size);
result.insert(result.begin(), ptr, ptr + size);
ptr += size;
return result;
}
};
struct binary_writer
{
char *ptr;
char *start;
char *end;
binary_writer(const void *data, size_t size)
: ptr((char*)data), start((char*)data)
, end((char*)data + size) { }
size_t offset() { return ptr - start; }
void skip(size_t size)
{
assert((size_t)(end - ptr) >= size);
for (size_t i = 0; i < size; i++) {
*ptr++ = 0;
}
}
template <typename T, typename U>
void write(U value, size_t offset) {
#if FBXDOM_NATIVE_WRITE
*(T*)(start + offset) = (T)value;
#else
T tvalue = (T)value;
uint64_t uint = 0;
memcpy(&uint, &tvalue, sizeof(T));
for (size_t i = 0; i < sizeof(T); i++) {
start[offset + i] = (uint >> (i*8)) & 0xff;
}
#endif
}
template <typename T, typename U>
void write(U value) {
#if FBXDOM_NATIVE_WRITE
*(T*)ptr = (T)value;
ptr += sizeof(T);
#else
T tvalue = (T)value;
uint64_t uint = 0;
memcpy(&uint, &tvalue, sizeof(T));
for (size_t i = 0; i < sizeof(T); i++) {
ptr[i] = (uint >> (i*8)) & 0xff;
}
ptr += sizeof(T);
#endif
}
void write(const char *data, size_t size)
{
memcpy(ptr, data, size);
ptr += size;
}
void write(const std::vector<char> &data)
{
write(data.data(), data.size());
}
};
struct binary_parser
{
binary_reader reader;
uint32_t version = 0;
binary_parser(const void *data, size_t size)
: reader(data, size) { }
void parse_array(value &v, size_t elem_size)
{
v.data_array.length = reader.read<uint32_t>();
v.data_array.encoding = reader.read<uint32_t>();
v.data_array.compressed_length = reader.read<uint32_t>();
v.data = reader.read_vector(v.data_array.compressed_length);
}
value parse_value()
{
value v;
char type = reader.read<char>();
v.type = type;
switch (type) {
case 'B': case 'C': v.data_float = (double)(v.data_int = reader.read<int8_t>()); break;
case 'Y': v.data_float = (double)(v.data_int = reader.read<int16_t>()); break;
case 'I': v.data_float = (double)(v.data_int = reader.read<int32_t>()); break;
case 'L': v.data_float = (double)(v.data_int = reader.read<int64_t>()); break;
case 'F': v.data_int = (int64_t)(v.data_float = reader.read<float>()); break;
case 'D': v.data_int = (int64_t)(v.data_float = reader.read<double>()); break;
case 'b': case 'c': parse_array(v, 1); break;
case 'i': case 'f': parse_array(v, 4); break;
case 'l': case 'd': parse_array(v, 8); break;
case 'S': case 'R': v.data = reader.read_vector(reader.read<uint32_t>()); break;
}
return v;
}
node_ptr parse_node()
{
size_t num_properties;
size_t end_offset;
if (version >= 7500) {
end_offset = (size_t)reader.read<uint64_t>();
num_properties = (size_t)reader.read<uint64_t>();
reader.skip(8);
} else {
end_offset = (size_t)reader.read<uint32_t>();
num_properties = (size_t)reader.read<uint32_t>();
reader.skip(4);
}
size_t name_len = reader.read<uint8_t>();
if (end_offset == 0 && name_len == 0) return { };
node_ptr n = std::make_shared<node>();
n->name = reader.read_vector(name_len);
n->values.reserve(num_properties);
for (size_t i = 0; i < num_properties; i++) {
n->values.push_back(parse_value());
}
while (reader.offset() < end_offset || end_offset == 0) {
node_ptr child = parse_node();
if (!child) break;
n->children.push_back(std::move(child));
}
return n;
}
node_ptr parse_root()
{
std::vector<char> magic = reader.read_vector(21);
if (memcmp(magic.data(), "Kaydara FBX Binary \x00", 21) != 0) return { };
reader.skip(2);
version = reader.read<uint32_t>();
node_ptr root = std::make_shared<node>();
{
value v;
v.data_int = version;
root->values.push_back(v);
}
for (;;) {
node_ptr child = parse_node();
if (!child) break;
root->children.push_back(std::move(child));
}
return root;
}
};
struct binary_dumper
{
binary_writer writer;
uint32_t version = 0;
binary_dumper(void *data, size_t size)
: writer(data, size) { }
void dump_array(const value &v)
{
writer.write<uint32_t>(v.data_array.length);
writer.write<uint32_t>(v.data_array.encoding);
writer.write<uint32_t>(v.data_array.compressed_length);
writer.write(v.data);
}
void dump_value(const value &v)
{
writer.write<char>(v.type);
switch (v.type) {
case 'B': case 'C': writer.write<int8_t>(v.data_int); break;
case 'Y': writer.write<int16_t>(v.data_int); break;
case 'I': writer.write<int32_t>(v.data_int); break;
case 'L': writer.write<int64_t>(v.data_int); break;
case 'F': writer.write<float>(v.data_float); break;
case 'D': writer.write<double>(v.data_float); break;
case 'b': case 'c': dump_array(v); break;
case 'i': case 'f': dump_array(v); break;
case 'l': case 'd': dump_array(v); break;
case 'S': case 'R':
writer.write<uint32_t>(v.data.size());
writer.write(v.data);
break;
}
}
void dump_node(node_ptr node)
{
size_t node_start = writer.offset();
if (version >= 7500) {
writer.skip(8);
writer.write<uint64_t>(node->values.size());
writer.skip(8);
} else {
writer.skip(4);
writer.write<uint32_t>(node->values.size());
writer.skip(4);
}
writer.write<uint8_t>(node->name.size());
writer.write(node->name);
size_t value_start = writer.offset();
for (const value &v : node->values) {
dump_value(v);
}
size_t value_end = writer.offset();
if (!node->children.empty()) {
for (const node_ptr child : node->children) {
dump_node(child);
}
writer.skip(version >= 7500 ? 25 : 13);
}
size_t node_end = writer.offset();
if (version >= 7500) {
writer.write<uint64_t>(node_end, node_start + 0);
writer.write<uint64_t>(value_end - value_start, node_start + 16);
} else {
writer.write<uint32_t>(node_end, node_start + 0);
writer.write<uint32_t>(value_end - value_start, node_start + 8);
}
}
void dump_root(node_ptr root)
{
if (root->values.size() > 0) {
version = (uint32_t)root->values[0].data_int;
} else {
version = 7500;
}
writer.write("Kaydara FBX Binary \x00\x1a\x00", 23);
writer.write<uint32_t>(version);
for (const node_ptr child : root->children) {
dump_node(child);
}
writer.skip(version >= 7500 ? 25 : 13);
}
};
node_ptr node::copy() const
{
return std::make_shared<node>(*this);
}
node_ptr parse(const void *data, size_t size)
{
return binary_parser(data, size).parse_root();
}
size_t dump(void *dst, size_t size, node_ptr root)
{
binary_dumper d { dst, size };
d.dump_root(root);
return d.writer.offset();
}
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <stdint.h>
#include <string.h>
namespace fbxdom {
struct node;
using node_ptr = std::shared_ptr<node>;
struct array_info
{
uint32_t length = 0;
uint32_t encoding = 0;
uint32_t compressed_length = 0;
};
struct value
{
char type = 0;
array_info data_array;
int64_t data_int = 0;
double data_float = 0.0;
std::vector<char> data;
};
struct node
{
std::vector<char> name;
std::vector<value> values;
std::vector<node_ptr> children;
node_ptr copy() const;
};
node_ptr parse(const void *data, size_t size);
size_t dump(void *dst, size_t size, node_ptr root);
}