Coverage Report

Created: 2026-10-16 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}