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/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 under one of several
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 the per-type converters (scanVector for an
8
 * atomic `what`, scanFrame for a list `what`).  R's equivalent of a
9
 * csv.reader target.
10
 *
11
 * The first input byte selects the configuration; the rest is the text.
12
 * Running every configuration on every input stopped scaling once there
13
 * were more than a handful, and one-per-input keeps libFuzzer's
14
 * coverage signal attributable.
15
 *
16
 * Adapted from r-afl's scan harness.
17
 */
18
19
#include <stdint.h>
20
#include <string.h>
21
22
#include "common.h"
23
24
53.8k
#define FUZZ_MAX_INPUT (1024 * 64)
25
26.9k
#define N_CALLS 18
26
27
/* Each configuration is an R closure over the text so that named
28
 * arguments need no Rf_lang* plumbing.  The first four match the
29
 * original harness; the rest reach converters and options it never
30
 * exercised (integer/complex/raw/logical readers, scanFrame, escapes,
31
 * quoting, NA strings, decimal marks, comments, fill, skip). */
32
static const char *const sources[N_CALLS] = {
33
    "function(x) scan(text = x, what = \"\", quiet = TRUE)",
34
    "function(x) scan(text = x, what = 0, quiet = TRUE)",
35
    "function(x) scan(text = x, what = \"\", sep = \",\", quiet = TRUE)",
36
    "function(x) scan(text = x, what = \"\", sep = \"\\t\", quiet = TRUE)",
37
    "function(x) scan(text = x, what = integer(), quiet = TRUE)",
38
    "function(x) scan(text = x, what = complex(), quiet = TRUE)",
39
    "function(x) scan(text = x, what = raw(), quiet = TRUE)",
40
    "function(x) scan(text = x, what = logical(), quiet = TRUE)",
41
    "function(x) scan(text = x, what = list(\"\", 0, \"\"), sep = \",\", "
42
    "quiet = TRUE)",
43
    "function(x) scan(text = x, what = \"\", allowEscapes = TRUE, quiet = TRUE)",
44
    "function(x) scan(text = x, what = \"\", sep = \",\", quote = \"\\\"'\", "
45
    "quiet = TRUE)",
46
    "function(x) scan(text = x, what = 0, na.strings = c(\"NA\", \"-\"), "
47
    "quiet = TRUE)",
48
    "function(x) scan(text = x, what = 0, dec = \",\", quiet = TRUE)",
49
    "function(x) scan(text = x, what = \"\", comment.char = \"#\", quiet = TRUE)",
50
    "function(x) scan(text = x, what = \"\", sep = \",\", strip.white = TRUE, "
51
    "quiet = TRUE)",
52
    "function(x) scan(text = x, what = list(\"\", 0, \"\"), sep = \",\", "
53
    "fill = TRUE, multi.line = FALSE, quiet = TRUE)",
54
    "function(x) scan(text = x, what = \"\", skip = 1, nlines = 4, quiet = TRUE)",
55
    "function(x) scan(text = x, what = \"\", sep = \",\", "
56
    "blank.lines.skip = FALSE, quiet = TRUE)",
57
};
58
59
static SEXP x_text;
60
static SEXP calls[N_CALLS];
61
62
int LLVMFuzzerInitialize(int *argc, char ***argv)
63
6
{
64
6
    (void)argc;
65
6
    (void)argv;
66
6
    fuzz_init_r();
67
68
    /* Reusable text container -- each iteration swaps its CHARSXP. */
69
6
    Rf_protect(x_text = Rf_allocVector(STRSXP, 1));
70
71
66
    for (int i = 0; i < N_CALLS; i++) {
72
60
        SEXP wrapper;
73
60
        Rf_protect(wrapper = fuzz_make_wrapper(sources[i]));
74
60
        Rf_protect(calls[i] = Rf_lang2(wrapper, x_text));
75
60
    }
76
77
    /* Warmup: prime scan's type-dispatch and locale state. */
78
6
    SET_STRING_ELT(x_text, 0, Rf_mkChar("1,2,3\n4,5,6\n"));
79
70
    for (int i = 0; i < N_CALLS; i++)
80
64
        fuzz_eval_silent(calls[i], R_GlobalEnv);
81
82
6
    return 0;
83
6
}
84
85
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
86
26.9k
{
87
26.9k
    if (size < 2 || size > FUZZ_MAX_INPUT || memchr(data + 1, '\0', size - 1))
88
97
        return 0;
89
90
26.8k
    char buffer[FUZZ_MAX_INPUT];
91
26.8k
    memcpy(buffer, data + 1, size - 1);
92
26.8k
    buffer[size - 1] = '\0';
93
26.8k
    if (!fuzz_set_string(x_text, buffer))
94
0
        return 0;
95
96
26.8k
    fuzz_eval_silent(calls[data[0] % N_CALLS], R_GlobalEnv);
97
26.8k
    return 0;
98
26.8k
}