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/parse.c
Line
Count
Source
1
/*
2
 * libFuzzer harness for R's parser.
3
 *
4
 * Feeds input to the lexer and parser without evaluating the result.
5
 * Targets parser bugs: crashes, OOB reads, infinite loops, stack
6
 * overflows in deeply nested input.
7
 *
8
 * The first input byte selects the route; the rest is the source text.
9
 * Slot 0 is the bare R_ParseVector of the original harness.  The others
10
 * go through R-level parse() so the srcref machinery runs, then push the
11
 * parsed expressions through deparse() and back, str2lang(), and the
12
 * deparse control options -- deparse.c is the parser's inverse and had
13
 * only ever been reached incidentally.  Nothing here evaluates what was
14
 * parsed.
15
 *
16
 * Adapted from r-afl's parse 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 * 16)
25
26.9k
#define N_CALLS 7
26
27
/* Slot 0 is handled in C; sources[0] is a placeholder so slot numbers
28
 * line up with the table. */
29
static const char *const sources[N_CALLS] = {
30
    NULL,
31
    "function(x) { e <- parse(text = x, keep.source = TRUE); "
32
    "getParseData(e) }",
33
    "function(x) { e <- parse(text = x, keep.source = FALSE); "
34
    "d <- vapply(as.list(e), function(ex) "
35
    "paste(deparse(ex, control = \"all\"), collapse = \"\\n\"), \"\"); "
36
    "identical(e, parse(text = d, keep.source = FALSE)) }",
37
    "function(x) str2lang(x)",
38
    "function(x) str2expression(x)",
39
    "function(x) deparse(parse(text = x, keep.source = FALSE), "
40
    "width.cutoff = 20L, backtick = TRUE, control = c(\"keepInteger\", "
41
    "\"quoteExpressions\", \"showAttributes\", \"digits17\"))",
42
    "function(x) deparse(parse(text = x, keep.source = TRUE), "
43
    "control = c(\"useSource\", \"keepNA\", \"niceNames\"))",
44
};
45
46
static SEXP x_str;
47
static SEXP calls[N_CALLS];
48
49
typedef struct {
50
    SEXP str;
51
    ParseStatus status;
52
} parse_data_t;
53
54
static void do_parse(void *data)
55
970
{
56
970
    parse_data_t *pd = (parse_data_t *)data;
57
970
    SEXP parsed;
58
970
    Rf_protect(parsed = R_ParseVector(pd->str, -1, &pd->status, R_NilValue));
59
970
    Rf_unprotect(1);
60
970
}
61
62
static void run_slot(int selected)
63
5.62k
{
64
5.62k
    if (selected == 0) {
65
970
        parse_data_t pd;
66
970
        pd.status = PARSE_NULL;
67
970
        pd.str = x_str;
68
970
        R_ToplevelExec(do_parse, &pd);
69
970
        return;
70
970
    }
71
72
4.65k
    fuzz_eval_silent(calls[selected], R_GlobalEnv);
73
4.65k
}
74
75
int LLVMFuzzerInitialize(int *argc, char ***argv)
76
2
{
77
2
    fuzz_init_r();
78
79
    /* Enable pipe bind (=>) syntax so the fuzzer exercises that path. */
80
2
    setenv("_R_USE_PIPEBIND_", "true", 0);
81
82
    /* Pre-allocate reusable string container.  Each iteration just
83
     * swaps the CHARSXP inside via SET_STRING_ELT. */
84
2
    Rf_protect(x_str = Rf_allocVector(STRSXP, 1));
85
86
14
    for (int i = 1; i < N_CALLS; i++) {
87
12
        SEXP wrapper;
88
12
        Rf_protect(wrapper = fuzz_make_wrapper(sources[i]));
89
12
        Rf_protect(calls[i] = Rf_lang2(wrapper, x_str));
90
12
    }
91
92
    /* Warmup: prime the parser's internal state so early iterations
93
     * don't diverge from later ones. */
94
2
    {
95
2
        static const char *warmup[] = {
96
2
            "1+1",
97
2
            "x <- function(a, b) a + b",
98
2
            "if (TRUE) 'yes' else 'no'",
99
2
            "for (i in 1:10) i",
100
2
            "list(a=1, b=\"hello\", c=NULL, d=NA)",
101
2
            "\\(x) x + 1",
102
2
            "1:10 |> rev()",
103
2
            NULL
104
2
        };
105
16
        for (int i = 0; warmup[i] != NULL; i++) {
106
14
            SET_STRING_ELT(x_str, 0, Rf_mkChar(warmup[i]));
107
112
            for (int slot = 0; slot < N_CALLS; slot++)
108
98
                run_slot(slot);
109
14
        }
110
2
        R_gc();
111
2
    }
112
113
2
    return 0;
114
2
}
115
116
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
117
26.9k
{
118
26.9k
    if (size < 2 || size > FUZZ_MAX_INPUT || memchr(data + 1, '\0', size - 1))
119
97
        return 0;
120
121
    /* Null-terminate the input for R's string API. */
122
26.8k
    char buf[FUZZ_MAX_INPUT];
123
26.8k
    memcpy(buf, data + 1, size - 1);
124
26.8k
    buf[size - 1] = '\0';
125
126
26.8k
    if (!fuzz_set_string(x_str, buf))
127
0
        return 0;
128
129
26.8k
    run_slot(data[0] % N_CALLS);
130
26.8k
    return 0;
131
26.8k
}