/src/r-oss-fuzz/harnesses/scan.c
Line | Count | Source |
1 | | /* |
2 | | * libFuzzer harness for R's scan / delimited-text parser. |
3 | | * |
4 | | * Feeds fuzzed input to scan() as text with several type/separator |
5 | | * configurations, exercising the C-level parsing state machine in |
6 | | * src/main/scan.c: field separation, quoting, escapes, comment chars, |
7 | | * NA-string matching, and type coercion. R's equivalent of a |
8 | | * csv.reader target. |
9 | | * |
10 | | * Adapted from r-afl's scan harness. |
11 | | */ |
12 | | |
13 | | #include <stdint.h> |
14 | | #include <string.h> |
15 | | |
16 | | #include "common.h" |
17 | | |
18 | 2.21k | #define FUZZ_MAX_INPUT (1024 * 64) |
19 | 11.0k | #define N_CALLS 4 |
20 | | |
21 | | static SEXP x_text; |
22 | | static SEXP calls[N_CALLS]; |
23 | | |
24 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
25 | 2 | { |
26 | 2 | fuzz_init_r(); |
27 | | |
28 | 2 | SEXP sym_scan = Rf_install("scan"); |
29 | | |
30 | 2 | SEXP str_empty, str_comma, str_tab, val_zero, val_true; |
31 | 2 | Rf_protect(str_empty = Rf_mkString("")); |
32 | 2 | Rf_protect(str_comma = Rf_mkString(",")); |
33 | 2 | Rf_protect(str_tab = Rf_mkString("\t")); |
34 | 2 | Rf_protect(val_zero = Rf_ScalarReal(0.0)); |
35 | 2 | Rf_protect(val_true = Rf_ScalarLogical(TRUE)); |
36 | | |
37 | | /* Reusable text container -- each iteration swaps its CHARSXP. */ |
38 | 2 | Rf_protect(x_text = Rf_allocVector(STRSXP, 1)); |
39 | | |
40 | | /* [0] scan(text=x, what="", quiet=TRUE) -- whitespace separated */ |
41 | 2 | Rf_protect(calls[0] = Rf_lang4(sym_scan, x_text, str_empty, val_true)); |
42 | 2 | SET_TAG(CDR(calls[0]), Rf_install("text")); |
43 | 2 | SET_TAG(CDDR(calls[0]), Rf_install("what")); |
44 | 2 | SET_TAG(CDDDR(calls[0]), Rf_install("quiet")); |
45 | | |
46 | | /* [1] scan(text=x, what=0, quiet=TRUE) -- numeric reading */ |
47 | 2 | Rf_protect(calls[1] = Rf_lang4(sym_scan, x_text, val_zero, val_true)); |
48 | 2 | SET_TAG(CDR(calls[1]), Rf_install("text")); |
49 | 2 | SET_TAG(CDDR(calls[1]), Rf_install("what")); |
50 | 2 | SET_TAG(CDDDR(calls[1]), Rf_install("quiet")); |
51 | | |
52 | | /* [2] scan(text=x, what="", sep=",", quiet=TRUE) -- CSV */ |
53 | 2 | Rf_protect(calls[2] = Rf_lang5(sym_scan, x_text, str_empty, |
54 | 2 | val_true, str_comma)); |
55 | 2 | SET_TAG(CDR(calls[2]), Rf_install("text")); |
56 | 2 | SET_TAG(CDDR(calls[2]), Rf_install("what")); |
57 | 2 | SET_TAG(CDDDR(calls[2]), Rf_install("quiet")); |
58 | 2 | SET_TAG(CDR(CDDDR(calls[2])), Rf_install("sep")); |
59 | | |
60 | | /* [3] scan(text=x, what="", sep="\t", quiet=TRUE) -- TSV */ |
61 | 2 | Rf_protect(calls[3] = Rf_lang5(sym_scan, x_text, str_empty, |
62 | 2 | val_true, str_tab)); |
63 | 2 | SET_TAG(CDR(calls[3]), Rf_install("text")); |
64 | 2 | SET_TAG(CDDR(calls[3]), Rf_install("what")); |
65 | 2 | SET_TAG(CDDDR(calls[3]), Rf_install("quiet")); |
66 | 2 | SET_TAG(CDR(CDDDR(calls[3])), Rf_install("sep")); |
67 | | |
68 | | /* Warmup: prime scan's type-dispatch and locale state. */ |
69 | 2 | { |
70 | 2 | int error = 0; |
71 | 2 | SET_STRING_ELT(x_text, 0, Rf_mkChar("1,2,3")); |
72 | 10 | for (int i = 0; i < N_CALLS; i++) { |
73 | 8 | R_tryEval(calls[i], R_GlobalEnv, &error); |
74 | 8 | error = 0; |
75 | 8 | } |
76 | 2 | } |
77 | | |
78 | 2 | return 0; |
79 | 2 | } |
80 | | |
81 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
82 | 2.21k | { |
83 | 2.21k | if (size == 0 || size > FUZZ_MAX_INPUT) |
84 | 3 | return 0; |
85 | | |
86 | 2.21k | char buf[FUZZ_MAX_INPUT + 1]; |
87 | 2.21k | memcpy(buf, data, size); |
88 | 2.21k | buf[size] = '\0'; |
89 | | |
90 | 2.21k | if (!fuzz_set_string(x_text, buf)) |
91 | 0 | return 0; |
92 | | |
93 | 2.21k | fuzz_eval_data_t ed = { .env = R_GlobalEnv }; |
94 | 11.0k | for (int i = 0; i < N_CALLS; i++) { |
95 | 8.86k | ed.call = calls[i]; |
96 | 8.86k | R_ToplevelExec(fuzz_do_eval, &ed); |
97 | 8.86k | } |
98 | | |
99 | 2.21k | return 0; |
100 | 2.21k | } |