#define _CRT_SECURE_NO_WARNINGS #include #include #include bool g_verbose = false; 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 #include #include #include "../ufbx.h" #include "check_scene.h" #define arraycount(arr) ((sizeof(arr))/(sizeof(*(arr)))) const char *interesting_tokens[] = { "", " ", "/", "0", "1.0", ".0", ".", "1e64", "-1", "-1000", "1000", "#", "-", "-x", "\n", "\\", "\\\n", }; // No need to duplicate `interesting_tokens[]` const char *interesting_leading_tokens[] = { "v", "vn", "vt", "f", "l", "p", "g", "o", "usemtl", "newmtl", "mtllib", "Kd", "map_Kd", }; const char *interesting_lines[] = { "g Objfuzz", "usemtl Objfuzz", "newmtl Objfuzz", "mttlib objfuzz.mtl", "v 1 2 3", "vt 1 2", "vn 1 2 3", "f 1 2 3", "f 1/1 2/2 3/3", "f 1//1 2//2 3//3", "f 1/1/1 2/2/2 3/3/3", "map_Kd base.png", }; bool mutate_insert_token(std::vector &tokens, uint32_t token, uint32_t index) { if (index >= arraycount(interesting_tokens)) return false; tokens.insert(tokens.begin() + token, interesting_tokens[index]); return true; } bool mutate_replace_token(std::vector &tokens, uint32_t token, uint32_t index) { if (index >= arraycount(interesting_tokens)) return false; tokens[token] = interesting_tokens[index]; return true; } bool mutate_duplicate_token(std::vector &tokens, uint32_t token, uint32_t index) { if (index > 0) return false; std::string tok = tokens[token]; tokens.insert(tokens.begin() + token, tok); return true; } bool mutate_remove_token(std::vector &tokens, uint32_t token, uint32_t index) { if (index > 0) return false; tokens.erase(tokens.begin() + token); return true; } bool mutate_replace_first_token(std::vector &tokens, uint32_t token, uint32_t index) { if (token > 0) return false; if (index >= arraycount(interesting_leading_tokens)) return false; tokens[0] = interesting_leading_tokens[index]; return true; } bool mutate_insert_first_token(std::vector &tokens, uint32_t token, uint32_t index) { if (token > 0) return false; if (index >= arraycount(interesting_leading_tokens)) return false; tokens.insert(tokens.begin(), interesting_leading_tokens[index]); return true; } bool mutate_remove_line(std::vector &tokens, uint32_t token, uint32_t index) { if (token > 0 || index > 0) return false; tokens.clear(); return true; } bool mutate_insert_line(std::vector &tokens, uint32_t token, uint32_t index) { if (token > 0) return false; if (index >= arraycount(interesting_lines)) return false; tokens.push_back(interesting_lines[index]); return true; } bool mutate_replace_line(std::vector &tokens, uint32_t token, uint32_t index) { if (token > 0) return false; if (index >= arraycount(interesting_lines)) return false; tokens.clear(); tokens.push_back(interesting_lines[index]); return true; } bool mutate_duplicate_line(std::vector &tokens, uint32_t token, uint32_t index) { if (token > 0 || index > 0) return false; auto copy = tokens; tokens.insert(tokens.end(), copy.begin(), copy.end()); return true; } enum class token_category { initial, slash, comment, whitespace, newline, other, end, }; token_category categorize_char(char c) { switch (c) { case '/': return token_category::slash; case '#': return token_category::comment; case ' ': case '\t': return token_category::whitespace; case '\r': case '\n': return token_category::newline; default: return token_category::other; } } std::vector tokenize_line(const std::string &line) { std::vector result; size_t len = line.length(); size_t prev_begin = 0; token_category prev_cat = token_category::initial; for (size_t i = 0; i <= len; i++) { token_category cat = i < len ? categorize_char(line[i]) : token_category::end; if (cat != prev_cat) { if (prev_begin < i) { result.push_back(line.substr(prev_begin, i - prev_begin)); } prev_begin = i; prev_cat = cat; } } return result; } using mutate_fn = bool(std::vector &tokens, uint32_t token, uint32_t index); struct mutator { const char *name; mutate_fn *fn; }; static const mutator mutators[] = { { "insert token", &mutate_insert_token }, { "replace token", &mutate_replace_token }, { "duplicate token", &mutate_duplicate_token }, { "remove token", &mutate_remove_token }, { "replace first token", &mutate_replace_first_token }, { "insert first token", &mutate_insert_first_token }, { "remove line", &mutate_remove_line }, { "insert line", &mutate_insert_line }, { "replace line", &mutate_replace_line }, { "duplicate line", &mutate_duplicate_line }, }; std::vector read_file(const char *path) { FILE *f = fopen(path, "rb"); fseek(f, 0, SEEK_END); std::vector data; data.resize(ftell(f)); fseek(f, 0, SEEK_SET); fread(data.data(), 1, data.size(), f); fclose(f); return data; } std::vector split_lines(const char *data, size_t length) { std::vector lines; size_t line_begin = 0; for (size_t i = 0; i <= length; i++) { char c = i < length ? data[i] : '\0'; if (c == '\n' || c == '\0') { size_t inclusive = c == '\n' ? 1 : 0; lines.emplace_back(data + line_begin, i - line_begin + inclusive); line_begin = i + 1; } } return lines; } std::string concatenate(const std::string *parts, size_t count) { std::string result; for (size_t i = 0; i < count; i++) { result += parts[i]; } return result; } void process_file(const char *input_file, ufbx_file_format file_format) { std::vector data = read_file(input_file); std::vector lines = split_lines(data.data(), data.size()); int current_step = 0; // Add dummy line to end for mutators lines.emplace_back(); for (uint32_t line = 0; line < lines.size(); line++) { std::vector tokens = tokenize_line(lines[line]); // Add dummy token to end for mutators tokens.emplace_back(); for (uint32_t mut = 0; mut < arraycount(mutators); mut++) { bool done = false; for (uint32_t token = 0; !done && token < tokens.size(); token++) { for (uint32_t index = 0; index < tokens.size(); index++) { std::vector copy = tokens; bool ok = mutators[mut].fn(copy, token, index); if (!ok) { done = index == 0; break; } std::string parts[] = { concatenate(lines.data(), line), concatenate(copy.data(), copy.size()), concatenate(lines.data() + line + 1, lines.size() - (line + 1)), }; std::string file = concatenate(parts, arraycount(parts)); if (g_verbose) { printf("%d: line %u token %u: %s %u: ", current_step, line, token, mutators[mut].name, index); } ufbx_load_opts opts = { }; opts.file_format = file_format; ufbx_error error; ufbx_scene *scene = ufbx_load_memory(file.data(), file.length(), &opts, &error); if (scene) { if (g_verbose) { printf("OK!\n"); } ufbxt_check_scene(scene); ufbx_free_scene(scene); } else { if (g_verbose) { printf("%s\n", error.description.data); } } current_step++; } } } } } int main(int argc, char **argv) { const char *input_file = NULL; ufbx_file_format file_format = UFBX_FILE_FORMAT_OBJ; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "-v")) { g_verbose = true; } else if (!strcmp(argv[i], "--mtl")) { file_format = UFBX_FILE_FORMAT_MTL; } else { input_file = argv[i]; } } if (input_file) { process_file(input_file, file_format); } else { fprintf(stderr, "Usage: objfuzz \n"); } return 0; }