1 /* radare - LGPL - Copyright 2020 - thestr4ng3r */
2 
3 #include "r2r.h"
4 
5 #include <assert.h>
6 
7 #define LINEFMT "%s, line %"PFMT64u": "
8 
r2r_cmd_test_new(void)9 R_API R2RCmdTest *r2r_cmd_test_new(void) {
10 	return R_NEW0 (R2RCmdTest);
11 }
12 
r2r_cmd_test_free(R2RCmdTest * test)13 R_API void r2r_cmd_test_free(R2RCmdTest *test) {
14 	if (!test) {
15 		return;
16 	}
17 #define DO_KEY_STR(key, field) free (test->field.value);
18 	R2R_CMD_TEST_FOREACH_RECORD(DO_KEY_STR, R2R_CMD_TEST_FOREACH_RECORD_NOP, R2R_CMD_TEST_FOREACH_RECORD_NOP)
19 #undef DO_KEY_STR
20 	free (test);
21 }
22 
readline(char * buf,size_t * linesz)23 static char *readline(char *buf, size_t *linesz) {
24 	char *end = strchr (buf, '\n');
25 	if (end) {
26 		size_t len = end - buf;
27 		*end = '\0';
28 		if (len > 0 && buf[len - 1] == '\r') {
29 			buf[len - 1] = '\0';
30 			len--;
31 		}
32 		*linesz = len;
33 		return end + 1;
34 	} else {
35 		*linesz = strlen (buf);
36 		return NULL;
37 	}
38 }
39 
40 // read the (possibly multiline) string value of some key in the file
41 // e.g. for
42 //
43 // 0    CMDS=<<EOF
44 // 1    Hello
45 // 2    World
46 // 3    EOF
47 // 4    ...
48 //
49 // if nextline is at the beginning of line 1,
50 // read_string_val(&nextline, "<<EOF\0")
51 // will return "Hello\nWorld\n" with nextline being at the beginning of line 4 afterwards.
read_string_val(char ** nextline,const char * val,ut64 * linenum)52 static char *read_string_val(char **nextline, const char *val, ut64 *linenum) {
53 	if (val[0] == '\'') {
54 		size_t len = strlen (val);
55 		if (len > 1 && val[len - 1] == '\'') {
56 			eprintf ("Error: Invalid string syntax, use <<EOF instead of '...'\n");
57 			return NULL;
58 		}
59 	}
60 	if (val[0] == '<' && val[1] == '<') {
61 		// <<EOF syntax
62 		const char *endtoken = val + 2;
63 		if (!*endtoken) {
64 			eprintf ("Error: Missing opening end token after <<\n");
65 			return NULL;
66 		}
67 		if (strcmp (endtoken, "EOF") != 0) {
68 			// In case there will be strings containing "EOF" inside of them, this requirement
69 			// can be weakened to only apply for strings which do not contain "EOF".
70 			eprintf ("Error: End token must be \"EOF\", got \"%s\" instead.", endtoken);
71 			return NULL;
72 		}
73 		RStrBuf *buf = r_strbuf_new ("");
74 		char *line = *nextline;
75 		size_t linesz = 0;
76 		do {
77 			*nextline = readline (line, &linesz);
78 			(*linenum)++;
79 			char *end = strstr (line, endtoken);
80 			if (end != line) {
81 				// Require the EOF to be at the beginning of the line.
82 				// This means makes it impossible to write multiline tests without a trailing newline.
83 				// This requirement could be lifted later if necessary.
84 				end = NULL;
85 			}
86 			if (end) {
87 				*end = '\0';
88 			}
89 			r_strbuf_append (buf, line);
90 			if (end) {
91 				return r_strbuf_drain (buf);
92 			} else {
93 				r_strbuf_append (buf, "\n");
94 			}
95 		} while ((line = *nextline));
96 		eprintf ("Error: Missing closing end token %s\n", endtoken);
97 		r_strbuf_free (buf);
98 		return NULL;
99 	}
100 
101 	return strdup (val);
102 }
103 
r2r_load_cmd_test_file(const char * file)104 R_API RPVector *r2r_load_cmd_test_file(const char *file) {
105 	char *contents = r_file_slurp (file, NULL);
106 	if (!contents) {
107 		eprintf ("Failed to open file \"%s\"\n", file);
108 		return NULL;
109 	}
110 
111 	RPVector *ret = r_pvector_new (NULL);
112 	if (!ret) {
113 		free (contents);
114 		return NULL;
115 	}
116 	R2RCmdTest *test = r2r_cmd_test_new ();
117 	if (!test) {
118 		free (contents);
119 		r_pvector_free (ret);
120 		return NULL;
121 	}
122 
123 	ut64 linenum = 0;
124 	char *line = contents;
125 	size_t linesz;
126 	char *nextline;
127 	do {
128 		nextline = readline (line, &linesz);
129 		linenum++;
130 		if (!linesz) {
131 			continue;
132 		}
133 		if (*line == '#') {
134 			continue;
135 		}
136 		char *val = strchr (line, '=');
137 		if (val) {
138 			*val = '\0';
139 			val++;
140 		}
141 
142 		// RUN is the only cmd without value
143 		if (strcmp (line, "RUN") == 0) {
144 			test->run_line = linenum;
145 			if (!test->cmds.value) {
146 				eprintf (LINEFMT "Error: Test without CMDS key\n", file, linenum);
147 				goto fail;
148 			}
149 			if (!(test->expect.value || test->expect_err.value)) {
150 				if (!(test->regexp_out.value || test->regexp_err.value)) {
151 					eprintf (LINEFMT "Error: Test without EXPECT or EXPECT_ERR key"
152 						 " (did you forget an EOF?)\n", file, linenum);
153 					goto fail;
154 				}
155 			}
156 			r_pvector_push (ret, test);
157 			test = r2r_cmd_test_new ();
158 			if (!test) {
159 				goto beach;
160 			}
161 			continue;
162 		}
163 
164 #define DO_KEY_STR(key, field) \
165 		if (strcmp (line, key) == 0) { \
166 			if (test->field.value) { \
167 				free (test->field.value); \
168 				eprintf (LINEFMT "Warning: Duplicate key \"%s\"\n", file, linenum, key); \
169 			} \
170 			if (!val) { \
171 				eprintf (LINEFMT "Error: No value for key \"%s\"\n", file, linenum, key); \
172 				goto fail; \
173 			} \
174 			test->field.line_begin = linenum; \
175 			test->field.value = read_string_val (&nextline, val, &linenum); \
176 			test->field.line_end = linenum + 1; \
177 			if (!test->field.value) { \
178 				eprintf (LINEFMT "Error: Failed to read value for key \"%s\"\n", file, linenum, key); \
179 				goto fail; \
180 			} \
181 			continue; \
182 		}
183 
184 #define DO_KEY_BOOL(key, field) \
185 		if (strcmp (line, key) == 0) { \
186 			if (test->field.value) { \
187 				eprintf (LINEFMT "Warning: Duplicate key \"%s\"\n", file, linenum, key); \
188 			} \
189 			test->field.set = true; \
190 			/* Strip comment */ \
191 			char *cmt = strchr (val, '#'); \
192 			if (cmt) { \
193 				*cmt = '\0'; \
194 				cmt--; \
195 				while (cmt > val && *cmt == ' ') { \
196 					*cmt = '\0'; \
197 					cmt--; \
198 				} \
199 			} \
200 			if (!strcmp (val, "1")) { \
201 				test->field.value = true; \
202 			} else if (!strcmp (val, "0")) { \
203 				test->field.value = false; \
204 			} else { \
205 				eprintf (LINEFMT "Error: Invalid value \"%s\" for boolean key \"%s\", only \"1\" or \"0\" allowed.\n", file, linenum, val, key); \
206 				goto fail; \
207 			} \
208 			continue; \
209 		}
210 
211 #define DO_KEY_NUM(key, field) \
212 		if (strcmp (line, key) == 0) { \
213 			if (test->field.value) { \
214 				eprintf (LINEFMT "Warning: Duplicate key \"%s\"\n", file, linenum, key); \
215 			} \
216 			test->field.set = true; \
217 			/* Strip comment */ \
218 			char *cmt = strchr (val, '#'); \
219 			if (cmt) { \
220 				*cmt = '\0'; \
221 				cmt--; \
222 				while (cmt > val && *cmt == ' ') { \
223 					*cmt = '\0'; \
224 					cmt--; \
225 				} \
226 			} \
227 			char *endval; \
228 			test->field.value = strtol (val, &endval, 0); \
229 			if (!endval || *endval) { \
230 				eprintf (LINEFMT "Error: Invalid value \"%s\" for numeric key \"%s\", only numbers allowed.\n", file, linenum, val, key); \
231 				goto fail; \
232 			} \
233 			continue; \
234 		}
235 
236 		R2R_CMD_TEST_FOREACH_RECORD(DO_KEY_STR, DO_KEY_BOOL, DO_KEY_NUM)
237 #undef DO_KEY_STR
238 #undef DO_KEY_BOOL
239 #undef DO_KEY_NUM
240 
241 		eprintf (LINEFMT "Unknown key \"%s\".\n", file, linenum, line);
242 	} while ((line = nextline));
243 beach:
244 	free (contents);
245 
246 	if (test && (test->name.value || test->cmds.value || test->expect.value)) {
247 		eprintf ("Warning: found test tokens at the end of \"%s\" without RUN.\n", file);
248 	}
249 	r2r_cmd_test_free (test);
250 	return ret;
251 fail:
252 	r2r_cmd_test_free (test);
253 	test = NULL;
254 	r_pvector_free (ret);
255 	ret = NULL;
256 	goto beach;
257 }
258 
r2r_asm_test_new(void)259 R_API R2RAsmTest *r2r_asm_test_new(void) {
260 	return R_NEW0 (R2RAsmTest);
261 }
262 
r2r_asm_test_free(R2RAsmTest * test)263 R_API void r2r_asm_test_free(R2RAsmTest *test) {
264 	if (!test) {
265 		return;
266 	}
267 	free (test->disasm);
268 	free (test->bytes);
269 	free (test);
270 }
271 
parse_asm_path(const char * path,RStrConstPool * strpool,const char ** arch_out,const char ** cpuout,int * bitsout)272 static bool parse_asm_path(const char *path, RStrConstPool *strpool, const char **arch_out, const char **cpuout, int *bitsout) {
273 	RList *file_tokens = r_str_split_duplist (path, R_SYS_DIR, true);
274 	if (r_list_empty (file_tokens)) {
275 		r_list_free (file_tokens);
276 		return false;
277 	}
278 
279 	// Possibilities:
280 	// arm
281 	// arm_32
282 	// arm_cortex_32
283 
284 	char *arch = r_list_last (file_tokens);
285 	if (!*arch) {
286 		r_list_free (file_tokens);
287 		return false;
288 	}
289 	char *second = strchr (arch, '_');
290 	if (second) {
291 		*second = '\0';
292 		second++;
293 		char *third = strchr (second, '_');
294 		if (third) {
295 			*third = '\0';
296 			third++;
297 			*cpuout = r_str_constpool_get (strpool, second);
298 			*bitsout = atoi (third);
299 		} else {
300 			*cpuout = NULL;
301 			*bitsout = atoi (second);
302 		}
303 	} else {
304 		*cpuout = NULL;
305 		*bitsout = 0;
306 	}
307 	*arch_out = r_str_constpool_get (strpool, arch);
308 	r_list_free (file_tokens);
309 	return true;
310 }
311 
r2r_load_asm_test_file(RStrConstPool * strpool,const char * file)312 R_API RPVector *r2r_load_asm_test_file(RStrConstPool *strpool, const char *file) {
313 	const char *arch;
314 	const char *cpu;
315 	int bits;
316 	if (!parse_asm_path (file, strpool, &arch, &cpu, &bits)) {
317 		eprintf ("Failed to parse arch/cpu/bits from path %s\n", file);
318 		return NULL;
319 	}
320 
321 	char *contents = r_file_slurp (file, NULL);
322 	if (!contents) {
323 		eprintf ("Failed to open file \"%s\"\n", file);
324 		return NULL;
325 	}
326 
327 	RPVector *ret = r_pvector_new (NULL);
328 	if (!ret) {
329 		return NULL;
330 	}
331 
332 	ut64 linenum = 0;
333 	char *line = contents;
334 	size_t linesz;
335 	char *nextline;
336 	do {
337 		nextline = readline (line, &linesz);
338 		linenum++;
339 		if (!linesz) {
340 			continue;
341 		}
342 		if (*line == '#') {
343 			continue;
344 		}
345 
346 		int mode = 0;
347 		while (*line && *line != ' ') {
348 			switch (*line) {
349 			case 'a':
350 				mode |= R2R_ASM_TEST_MODE_ASSEMBLE;
351 				break;
352 			case 'd':
353 				mode |= R2R_ASM_TEST_MODE_DISASSEMBLE;
354 				break;
355 			case 'E':
356 				mode |= R2R_ASM_TEST_MODE_BIG_ENDIAN;
357 				break;
358 			case 'B':
359 				mode |= R2R_ASM_TEST_MODE_BROKEN;
360 				break;
361 			default:
362 				eprintf (LINEFMT "Warning: Invalid mode char '%c'\n", file, linenum, *line);
363 				goto fail;
364 			}
365 			line++;
366 		}
367 		if (!(mode & R2R_ASM_TEST_MODE_ASSEMBLE) && !(mode & R2R_ASM_TEST_MODE_DISASSEMBLE)) {
368 			eprintf (LINEFMT "Warning: Mode specifies neither assemble nor disassemble.\n", file, linenum);
369 			continue;
370 		}
371 
372 		char *disasm = strchr (line, '"');
373 		if (!disasm) {
374 			eprintf (LINEFMT "Error: Expected \" to begin disassembly.\n", file, linenum);
375 			goto fail;
376 		}
377 		disasm++;
378 		char *hex = strchr (disasm, '"');
379 		if (!hex) {
380 			eprintf (LINEFMT "Error: Expected \" to end disassembly.\n", file, linenum);
381 			goto fail;
382 		}
383 		*hex = '\0';
384 		hex++;
385 		r_str_trim (disasm);
386 
387 		while (*hex && *hex == ' ') {
388 			hex++;
389 		}
390 
391 		char *offset = strchr (hex, ' ');
392 		if (offset) {
393 			*offset = '\0';
394 			offset++;
395 		}
396 
397 		size_t hexlen = strlen (hex);
398 		if (!hexlen) {
399 			eprintf (LINEFMT "Error: Expected hex chars.\n", file, linenum);
400 			goto fail;
401 		}
402 		ut8 *bytes = malloc (hexlen);
403 		if (!bytes) {
404 			break;
405 		}
406 		int bytesz = r_hex_str2bin (hex, bytes);
407 		if (bytesz == 0) {
408 			eprintf (LINEFMT "Error: Expected hex chars.\n", file, linenum);
409 			goto fail;
410 		}
411 		if (bytesz < 0) {
412 			eprintf (LINEFMT "Error: Odd number of hex chars: %s\n", file, linenum, hex);
413 			goto fail;
414 		}
415 
416 		R2RAsmTest *test = r2r_asm_test_new ();
417 		if (!test) {
418 			free (bytes);
419 			goto fail;
420 		}
421 		test->line = linenum;
422 		test->bits = bits;
423 		test->arch = arch;
424 		test->cpu = cpu;
425 		test->mode = mode;
426 		test->offset = offset ? (ut64)strtoull (offset, NULL, 0) : 0;
427 		test->disasm = strdup (disasm);
428 		test->bytes = bytes;
429 		test->bytes_size = (size_t)bytesz;
430 		r_pvector_push (ret, test);
431 	} while ((line = nextline));
432 
433 beach:
434 	free (contents);
435 	return ret;
436 fail:
437 	r_pvector_free (ret);
438 	ret = NULL;
439 	goto beach;
440 }
441 
r2r_json_test_new(void)442 R_API R2RJsonTest *r2r_json_test_new(void) {
443 	return R_NEW0 (R2RJsonTest);
444 }
445 
r2r_json_test_free(R2RJsonTest * test)446 R_API void r2r_json_test_free(R2RJsonTest *test) {
447 	if (!test) {
448 		return;
449 	}
450 	free (test->cmd);
451 	free (test);
452 }
453 
r2r_load_json_test_file(const char * file)454 R_API RPVector *r2r_load_json_test_file(const char *file) {
455 	char *contents = r_file_slurp (file, NULL);
456 	if (!contents) {
457 		eprintf ("Failed to open file \"%s\"\n", file);
458 		return NULL;
459 	}
460 
461 	RPVector *ret = r_pvector_new (NULL);
462 	if (!ret) {
463 		free (contents);
464 		return NULL;
465 	}
466 
467 	ut64 linenum = 0;
468 	char *line = contents;
469 	size_t linesz;
470 	char *nextline;
471 	do {
472 		nextline = readline (line, &linesz);
473 		linenum++;
474 		if (!linesz) {
475 			continue;
476 		}
477 		if (*line == '#') {
478 			continue;
479 		}
480 
481 		char *broken_token = strstr (line, "BROKEN");
482 		if (broken_token) {
483 			*broken_token = '\0';
484 		}
485 
486 		r_str_trim (line);
487 		if (!*line) {
488 			// empty line
489 			continue;
490 		}
491 
492 		R2RJsonTest *test = r2r_json_test_new ();
493 		if (!test) {
494 			break;
495 		}
496 		test->line = linenum;
497 		test->cmd = strdup (line);
498 		if (!test->cmd) {
499 			r2r_json_test_free (test);
500 			break;
501 		}
502 		test->broken = broken_token ? true : false;
503 		r_pvector_push (ret, test);
504 	} while ((line = nextline));
505 
506 	free (contents);
507 	return ret;
508 }
509 
r2r_fuzz_test_free(R2RFuzzTest * test)510 R_API void r2r_fuzz_test_free(R2RFuzzTest *test) {
511 	if (!test) {
512 		return;
513 	}
514 	free (test->file);
515 	free (test);
516 }
517 
r2r_test_free(R2RTest * test)518 R_API void r2r_test_free(R2RTest *test) {
519 	if (!test) {
520 		return;
521 	}
522 	switch (test->type) {
523 	case R2R_TEST_TYPE_CMD:
524 		r2r_cmd_test_free (test->cmd_test);
525 		break;
526 	case R2R_TEST_TYPE_ASM:
527 		r2r_asm_test_free (test->asm_test);
528 		break;
529 	case R2R_TEST_TYPE_JSON:
530 		r2r_json_test_free (test->json_test);
531 		break;
532 	case R2R_TEST_TYPE_FUZZ:
533 		r2r_fuzz_test_free (test->fuzz_test);
534 		break;
535 	}
536 	free (test);
537 }
538 
r2r_test_database_new(void)539 R_API R2RTestDatabase *r2r_test_database_new(void) {
540 	R2RTestDatabase *db = R_NEW (R2RTestDatabase);
541 	if (!db) {
542 		return NULL;
543 	}
544 	r_pvector_init (&db->tests, (RPVectorFree)r2r_test_free);
545 	r_str_constpool_init (&db->strpool);
546 	return db;
547 }
548 
r2r_test_database_free(R2RTestDatabase * db)549 R_API void r2r_test_database_free(R2RTestDatabase *db) {
550 	if (!db) {
551 		return;
552 	}
553 	r_pvector_clear (&db->tests);
554 	r_str_constpool_fini (&db->strpool);
555 	free (db);
556 }
557 
test_type_for_path(const char * path,bool * load_plugins)558 static R2RTestType test_type_for_path(const char *path, bool *load_plugins) {
559 	R2RTestType ret = R2R_TEST_TYPE_CMD;
560 	char *pathdup = strdup (path);
561 	RList *tokens = r_str_split_list (pathdup, R_SYS_DIR, 0);
562 	if (!tokens) {
563 		return ret;
564 	}
565 	if (!r_list_empty (tokens)) {
566 		r_list_pop (tokens);
567 	}
568 	RListIter *it;
569 	char *token;
570 	*load_plugins = false;
571 	r_list_foreach (tokens, it, token) {
572 		if (!strcmp (token, "asm")) {
573 			ret = R2R_TEST_TYPE_ASM;
574 			continue;
575 		}
576 		if (!strcmp (token, "json")) {
577 			ret = R2R_TEST_TYPE_JSON;
578 			continue;
579 		}
580 		if (!strcmp (token, "extras")) {
581 			*load_plugins = true;
582 		}
583 	}
584 	r_list_free (tokens);
585 	free (pathdup);
586 	return ret;
587 }
588 
database_load(R2RTestDatabase * db,const char * path,int depth)589 static bool database_load(R2RTestDatabase *db, const char *path, int depth) {
590 	if (depth <= 0) {
591 		eprintf ("Directories for loading tests too deep: %s\n", path);
592 		return false;
593 	}
594 	if (r_file_is_directory (path)) {
595 		RList *dir = r_sys_dir (path);
596 		if (!dir) {
597 			return false;
598 		}
599 		RListIter *it;
600 		const char *subname;
601 		RStrBuf subpath;
602 		r_strbuf_init (&subpath);
603 		char *sa = r_sys_getenv ("R2R_SKIP_ARCHOS");
604 		bool skip_archos = (sa && !strcmp (sa, "1"));
605 		free (sa);
606 		bool ret = true;
607 		r_list_foreach (dir, it, subname) {
608 			if (*subname == '.') {
609 				continue;
610 			}
611 			if (!strcmp (subname, "extras")) {
612 				// Only load "extras" dirs if explicitly specified
613 				eprintf ("Skipping %s"R_SYS_DIR"%s because it requires additional dependencies.\n", path, subname);
614 				continue;
615 			}
616 			bool is_archos_folder = !strcmp (path, "archos") || r_str_endswith (path, R_SYS_DIR"archos");
617 			if (is_archos_folder && (skip_archos || strcmp (subname, R2R_ARCH_OS))) {
618 				eprintf ("Skipping %s"R_SYS_DIR"%s because it does not match the current platform.\n", path, subname);
619 				continue;
620 			}
621 			r_strbuf_setf (&subpath, "%s%s%s", path, R_SYS_DIR, subname);
622 			if (!database_load (db, r_strbuf_get (&subpath), depth - 1)) {
623 				ret = false;
624 				break;
625 			}
626 		}
627 		r_strbuf_fini (&subpath);
628 		r_list_free (dir);
629 		return ret;
630 	}
631 
632 	if (!r_file_exists (path)) {
633 		eprintf ("Path \"%s\" does not exist\n", path);
634 		return false;
635 	}
636 
637 	// Not a directory but exists, load a file
638 	const char *pooled_path = r_str_constpool_get (&db->strpool, path);
639 	bool load_plugins = false;
640 	R2RTestType test_type = test_type_for_path (path, &load_plugins);
641 	switch (test_type) {
642 	case R2R_TEST_TYPE_CMD: {
643 		RPVector *cmd_tests = r2r_load_cmd_test_file (path);
644 		if (!cmd_tests) {
645 			return false;
646 		}
647 		void **it;
648 		r_pvector_foreach (cmd_tests, it) {
649 			R2RTest *test = R_NEW (R2RTest);
650 			if (!test) {
651 				continue;
652 			}
653 			test->type = R2R_TEST_TYPE_CMD;
654 			test->path = pooled_path;
655 			test->cmd_test = *it;
656 			test->cmd_test->load_plugins = load_plugins;
657 			r_pvector_push (&db->tests, test);
658 		}
659 		r_pvector_free (cmd_tests);
660 		break;
661 	}
662 	case R2R_TEST_TYPE_ASM: {
663 		RPVector *asm_tests = r2r_load_asm_test_file (&db->strpool, path);
664 		if (!asm_tests) {
665 			return false;
666 		}
667 		void **it;
668 		r_pvector_foreach (asm_tests, it) {
669 			R2RTest *test = R_NEW (R2RTest);
670 			if (!test) {
671 				continue;
672 			}
673 			test->type = R2R_TEST_TYPE_ASM;
674 			test->path = pooled_path;
675 			test->asm_test = *it;
676 			r_pvector_push (&db->tests, test);
677 		}
678 		r_pvector_free (asm_tests);
679 		break;
680 	}
681 	case R2R_TEST_TYPE_JSON: {
682 		RPVector *json_tests = r2r_load_json_test_file (path);
683 		if (!json_tests) {
684 			return false;
685 		}
686 		void **it;
687 		r_pvector_foreach (json_tests, it) {
688 			R2RTest *test = R_NEW (R2RTest);
689 			if (!test) {
690 				continue;
691 			}
692 			test->type = R2R_TEST_TYPE_JSON;
693 			test->path = pooled_path;
694 			test->json_test = *it;
695 			test->json_test->load_plugins = load_plugins;
696 			r_pvector_push (&db->tests, test);
697 		}
698 		r_pvector_free (json_tests);
699 		break;
700 	}
701 	case R2R_TEST_TYPE_FUZZ:
702 		// shouldn't come here, fuzz tests are loaded differently
703 		break;
704 	}
705 
706 	return true;
707 }
708 
r2r_test_database_load(R2RTestDatabase * db,const char * path)709 R_API bool r2r_test_database_load(R2RTestDatabase *db, const char *path) {
710 	return database_load (db, path, 4);
711 }
712 
database_load_fuzz_file(R2RTestDatabase * db,const char * path,const char * file)713 static void database_load_fuzz_file(R2RTestDatabase *db, const char *path, const char *file) {
714 	R2RFuzzTest *fuzz_test = R_NEW (R2RFuzzTest);
715 	if (!fuzz_test) {
716 		return;
717 	}
718 	fuzz_test->file = strdup (file);
719 	if (!fuzz_test->file) {
720 		free (fuzz_test);
721 		return;
722 	}
723 	R2RTest *test = R_NEW (R2RTest);
724 	if (!test) {
725 		free (fuzz_test->file);
726 		free (fuzz_test);
727 		return;
728 	}
729 	test->type = R2R_TEST_TYPE_FUZZ;
730 	test->fuzz_test = fuzz_test;
731 	test->path = r_str_constpool_get (&db->strpool, path);
732 	r_pvector_push (&db->tests, test);
733 }
734 
r2r_test_database_load_fuzz(R2RTestDatabase * db,const char * path)735 R_API bool r2r_test_database_load_fuzz(R2RTestDatabase *db, const char *path) {
736 	if (r_file_is_directory (path)) {
737 		RList *dir = r_sys_dir (path);
738 		if (!dir) {
739 			return false;
740 		}
741 		RListIter *it;
742 		const char *subname;
743 		RStrBuf subpath;
744 		r_strbuf_init (&subpath);
745 		bool ret = true;
746 		r_list_foreach (dir, it, subname) {
747 			if (*subname == '.') {
748 				continue;
749 			}
750 			r_strbuf_setf (&subpath, "%s%s%s", path, R_SYS_DIR, subname);
751 			if (r_file_is_directory (r_strbuf_get (&subpath))) {
752 				// only load 1 level deep
753 				continue;
754 			}
755 			database_load_fuzz_file (db, path, r_strbuf_get (&subpath));
756 		}
757 		r_strbuf_fini (&subpath);
758 		r_list_free (dir);
759 		return ret;
760 	}
761 
762 	if (!r_file_exists (path)) {
763 		eprintf ("Path \"%s\" does not exist\n", path);
764 		return false;
765 	}
766 
767 	// Just a single file
768 	database_load_fuzz_file (db, path, path);
769 	return true;
770 }
771