/src/r-oss-fuzz/harnesses/sort.c
Line | Count | Source |
1 | | /* |
2 | | * Exercise R's sorting, ordering, ranking and hashing primitives. |
3 | | * |
4 | | * src/main/sort.c (shell/quick sorts, psort, order, rank), radixsort.c |
5 | | * (the data.table forder port, with its CHARSXP truelength bookkeeping |
6 | | * and encoding-specific string comparison) and unique.c (the hash tables |
7 | | * behind unique/duplicated/match/charmatch/pmatch/rowsum) are reached by |
8 | | * no other target beyond R's own startup. The input is split on |
9 | | * newlines into a character vector, from which each slot derives the |
10 | | * numeric, integer, complex, raw, list, factor and encoding-marked |
11 | | * variants it needs. |
12 | | * |
13 | | * Strings are marked UTF-8 only when valid, so every encoding the radix |
14 | | * sort sees is one R itself would produce. |
15 | | */ |
16 | | |
17 | | #include <stdint.h> |
18 | | #include <string.h> |
19 | | |
20 | | #include "common.h" |
21 | | |
22 | 53.8k | #define FUZZ_MAX_INPUT (1024 * 16) |
23 | 1.95M | #define FUZZ_MAX_LINES 2000 |
24 | 187k | #define N_CALLS 22 |
25 | | |
26 | | static SEXP calls[N_CALLS]; |
27 | | |
28 | | /* Each slot is a closure over the character vector x. Several related |
29 | | * calls share a slot so one input exercises a whole family at once; |
30 | | * the families are kept small enough that a 2000-line input stays |
31 | | * well under a millisecond. */ |
32 | | static const char *const sources[N_CALLS] = { |
33 | | /* 0 */ |
34 | | "function(x) { sort(x, method = 'radix'); sort(x, method = 'radix'," |
35 | | " decreasing = TRUE, na.last = TRUE) }", |
36 | | /* 1 */ |
37 | | "function(x) { sort(x, method = 'shell'); sort(x, method = 'shell'," |
38 | | " decreasing = TRUE, na.last = FALSE) }", |
39 | | /* 2 */ |
40 | | "function(x) { n <- as.numeric(x); sort(n, method = 'quick');" |
41 | | " sort(n, method = 'radix', na.last = TRUE); sort(n, method = 'shell') }", |
42 | | /* 3 */ |
43 | | "function(x) { i <- as.integer(as.numeric(x)); sort(i, method = 'shell'," |
44 | | " na.last = FALSE); sort(i, method = 'radix', decreasing = TRUE);" |
45 | | " sort(i, method = 'quick') }", |
46 | | /* 4 */ |
47 | | "function(x) { order(x, method = 'radix'); order(x, method = 'radix'," |
48 | | " decreasing = TRUE, na.last = NA) }", |
49 | | /* 5 */ |
50 | | "function(x) { n <- as.numeric(x); order(x, n, method = 'radix'," |
51 | | " decreasing = c(TRUE, FALSE)); order(n, x, method = 'radix') }", |
52 | | /* 6 */ |
53 | | "function(x) { n <- as.numeric(x); order(x, method = 'shell');" |
54 | | " order(n, method = 'shell', na.last = NA); order(n, x) }", |
55 | | /* 7 */ |
56 | | "function(x) { n <- as.numeric(x); rank(x, ties.method = 'min');" |
57 | | " rank(n, ties.method = 'average'); rank(x, ties.method = 'first');" |
58 | | " rank(n, ties.method = 'last', na.last = 'keep') }", |
59 | | /* 8 */ |
60 | | "function(x) { xtfrm(x); xtfrm(factor(x)); xtfrm(as.numeric(x)) }", |
61 | | /* 9 */ |
62 | | "function(x) { unique(x); duplicated(x); anyDuplicated(x);" |
63 | | " unique(x, fromLast = TRUE); duplicated(x, fromLast = TRUE) }", |
64 | | /* 10 */ |
65 | | "function(x) { n <- as.numeric(x); unique(n); duplicated(n);" |
66 | | " anyDuplicated(n, fromLast = TRUE); i <- as.integer(n); unique(i);" |
67 | | " duplicated(i); match(i, rev(i)); match(n, rev(n)) }", |
68 | | /* 11 */ |
69 | | "function(x) { n <- as.numeric(x); z <- complex(real = n, imaginary = rev(n));" |
70 | | " unique(z); duplicated(z); match(z, rev(z)); anyDuplicated(z) }", |
71 | | /* 12 */ |
72 | | "function(x) { i <- as.integer(as.numeric(x)); i[is.na(i)] <- 0L;" |
73 | | " r <- as.raw(i %% 256L); unique(r); duplicated(r); match(r, rev(r));" |
74 | | " l <- as.logical(i %% 3L - 1L); unique(l); duplicated(l); match(l, rev(l)) }", |
75 | | /* 13 */ |
76 | | "function(x) { l <- as.list(x); unique(l); duplicated(l); match(l, rev(l));" |
77 | | " anyDuplicated(l) }", |
78 | | /* 14 */ |
79 | | "function(x) { match(x, rev(x)); x %in% rev(x); charmatch(x, rev(x));" |
80 | | " pmatch(x, rev(x), duplicates.ok = TRUE); pmatch(x, rev(x)) }", |
81 | | /* 15 */ |
82 | | "function(x) { n <- as.numeric(x); n[is.na(n)] <- 0; rowsum(n, group = x);" |
83 | | " rowsum(matrix(n, ncol = 1L), group = x, reorder = FALSE) }", |
84 | | /* 16 */ |
85 | | "function(x) { if (length(x) >= 3L) { n <- as.numeric(x);" |
86 | | " sort(n, partial = c(2L, 3L), na.last = TRUE); sort.int(as.integer(n), partial = 1L);" |
87 | | " sort.int(n, partial = c(1L, 3L), na.last = TRUE) } }", |
88 | | /* 17 */ |
89 | | "function(x) { n <- as.numeric(x); is.unsorted(x); is.unsorted(n);" |
90 | | " is.unsorted(x, strictly = TRUE); is.unsorted(n, na.rm = TRUE) }", |
91 | | /* 18 */ |
92 | | "function(x) { u <- x; Encoding(u) <- ifelse(validUTF8(u), 'UTF-8', 'unknown');" |
93 | | " sort(u, method = 'radix'); order(u, method = 'radix'); unique(u);" |
94 | | " match(u, x); duplicated(u) }", |
95 | | /* 19 */ |
96 | | "function(x) { l <- x; Encoding(l) <- 'latin1'; sort(l, method = 'radix');" |
97 | | " order(l, method = 'shell'); unique(l); match(l, x); duplicated(c(l, x)) }", |
98 | | /* 20 */ |
99 | | "function(x) { f <- factor(x); sort(f); order(f, method = 'radix');" |
100 | | " unique(f); duplicated(f); match(f, rev(f)); rank(f) }", |
101 | | /* 21 */ |
102 | | "function(x) { sort(x, index.return = TRUE); rev(sort(x, decreasing = TRUE));" |
103 | | " n <- as.numeric(x); sort(n, index.return = TRUE, method = 'radix');" |
104 | | " h <- utils::hashtab('identical'); k <- x[seq_len(min(50L, length(x)))];" |
105 | | " for (s in k) utils::sethash(h, s, nchar(s)); for (s in k) utils::gethash(h, s);" |
106 | | " utils::numhash(h) }", |
107 | | }; |
108 | | |
109 | | typedef struct { |
110 | | const uint8_t *data; |
111 | | size_t size; |
112 | | } lines_input_t; |
113 | | |
114 | | /* Split the input on newlines into a STRSXP and hand it to every slot. |
115 | | * Runs under R_ToplevelExec because the allocations can hit the vector |
116 | | * heap cap. */ |
117 | | static void set_lines(void *data) |
118 | 6.96k | { |
119 | 6.96k | lines_input_t *input = data; |
120 | 6.96k | const uint8_t *p = input->data; |
121 | 6.96k | const uint8_t *end = input->data + input->size; |
122 | | |
123 | 6.96k | int n = 1; |
124 | 1.95M | for (const uint8_t *q = p; q < end && n < FUZZ_MAX_LINES; q++) { |
125 | 1.95M | if (*q == '\n') |
126 | 539k | n++; |
127 | 1.95M | } |
128 | | |
129 | 6.96k | SEXP lines; |
130 | 6.96k | Rf_protect(lines = Rf_allocVector(STRSXP, n)); |
131 | 553k | for (int i = 0; i < n; i++) { |
132 | 546k | const uint8_t *nl = memchr(p, '\n', (size_t)(end - p)); |
133 | 546k | if (nl == NULL || i == n - 1) |
134 | 6.96k | nl = end; |
135 | 546k | SET_STRING_ELT(lines, i, Rf_mkCharLen((const char *)p, (int)(nl - p))); |
136 | 546k | p = nl < end ? nl + 1 : end; |
137 | 546k | } |
138 | | |
139 | 160k | for (int i = 0; i < N_CALLS; i++) |
140 | 153k | SETCADR(calls[i], lines); |
141 | 6.96k | Rf_unprotect(1); |
142 | 6.96k | } |
143 | | |
144 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
145 | 4 | { |
146 | 4 | (void)argc; |
147 | 4 | (void)argv; |
148 | 4 | fuzz_init_r(); |
149 | | |
150 | 4 | SEXP placeholder; |
151 | 4 | Rf_protect(placeholder = Rf_allocVector(STRSXP, 0)); |
152 | 56 | for (int i = 0; i < N_CALLS; i++) { |
153 | 52 | SEXP wrapper; |
154 | 52 | Rf_protect(wrapper = fuzz_make_wrapper(sources[i])); |
155 | 52 | Rf_protect(calls[i] = Rf_lang2(wrapper, placeholder)); |
156 | 52 | } |
157 | 4 | return 0; |
158 | 4 | } |
159 | | |
160 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
161 | 26.9k | { |
162 | 26.9k | if (size < 2 || size > FUZZ_MAX_INPUT || memchr(data + 1, '\0', size - 1)) |
163 | 97 | return 0; |
164 | | |
165 | 26.8k | lines_input_t input = { data + 1, size - 1 }; |
166 | 26.8k | if (!R_ToplevelExec(set_lines, &input)) |
167 | 0 | return 0; |
168 | | |
169 | 26.8k | fuzz_eval_silent(calls[data[0] % N_CALLS], R_GlobalEnv); |
170 | 26.8k | return 0; |
171 | 26.8k | } |