Coverage Report

Created: 2026-09-14 06:56

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 R_ParseVector -- exercising the lexer and parser
5
 * without evaluating the result.  Targets parser bugs: crashes,
6
 * OOB reads, infinite loops, stack overflows in deeply nested input.
7
 *
8
 * Adapted from r-afl's parse harness.
9
 */
10
11
#include <stdint.h>
12
#include <string.h>
13
14
#include "common.h"
15
16
10.8k
#define FUZZ_MAX_INPUT (1024 * 16)
17
18
static SEXP x_str;
19
20
typedef struct {
21
    SEXP str;
22
    ParseStatus status;
23
} parse_data_t;
24
25
static void do_parse(void *data)
26
6.50k
{
27
6.50k
    parse_data_t *pd = (parse_data_t *)data;
28
6.50k
    SEXP parsed;
29
6.50k
    Rf_protect(parsed = R_ParseVector(pd->str, -1, &pd->status, R_NilValue));
30
6.50k
    Rf_unprotect(1);
31
6.50k
}
32
33
int LLVMFuzzerInitialize(int *argc, char ***argv)
34
2
{
35
2
    fuzz_init_r();
36
37
    /* Enable pipe bind (=>) syntax so the fuzzer exercises that path. */
38
2
    setenv("_R_USE_PIPEBIND_", "true", 0);
39
40
    /* Pre-allocate reusable string container.  Each iteration just
41
     * swaps the CHARSXP inside via SET_STRING_ELT. */
42
2
    Rf_protect(x_str = Rf_allocVector(STRSXP, 1));
43
44
    /* Warmup: prime the parser's internal state so early iterations
45
     * don't diverge from later ones. */
46
2
    {
47
2
        static const char *warmup[] = {
48
2
            "1+1",
49
2
            "x <- function(a, b) a + b",
50
2
            "if (TRUE) 'yes' else 'no'",
51
2
            "for (i in 1:10) i",
52
2
            "list(a=1, b=\"hello\", c=NULL, d=NA)",
53
2
            "\\(x) x + 1",
54
2
            "1:10 |> rev()",
55
2
            NULL
56
2
        };
57
2
        parse_data_t pd;
58
16
        for (int i = 0; warmup[i] != NULL; i++) {
59
14
            SET_STRING_ELT(x_str, 0, Rf_mkChar(warmup[i]));
60
14
            pd.str = x_str;
61
14
            pd.status = PARSE_NULL;
62
14
            R_ToplevelExec(do_parse, &pd);
63
14
        }
64
2
        R_gc();
65
2
    }
66
67
2
    return 0;
68
2
}
69
70
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
71
10.8k
{
72
10.8k
    if (size == 0 || size > FUZZ_MAX_INPUT)
73
16
        return 0;
74
75
    /* Null-terminate the input for R's string API. */
76
10.8k
    char buf[FUZZ_MAX_INPUT + 1];
77
10.8k
    memcpy(buf, data, size);
78
10.8k
    buf[size] = '\0';
79
80
10.8k
    if (!fuzz_set_string(x_str, buf))
81
0
        return 0;
82
83
10.8k
    parse_data_t pd;
84
10.8k
    pd.status = PARSE_NULL;
85
10.8k
    pd.str = x_str;
86
10.8k
    R_ToplevelExec(do_parse, &pd);
87
88
10.8k
    return 0;
89
10.8k
}