Coverage Report

Created: 2026-03-31 07:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fuzz_ruby_parser.cpp
Line
Count
Source
1
/* Copyright 2025 Google LLC
2
Licensed under the Apache License, Version 2.0 (the "License");
3
you may not use this file except in compliance with the License.
4
You may obtain a copy of the License at
5
      http://www.apache.org/licenses/LICENSE-2.0
6
Unless required by applicable law or agreed to in writing, software
7
distributed under the License is distributed on an "AS IS" BASIS,
8
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
See the License for the specific language governing permissions and
10
limitations under the License.
11
*/
12
13
/*
14
 * Fuzzer for Ruby's parser (ruby_parser.c)
15
 * Tests parsing of Ruby source code with malformed/random input
16
 * to find bugs in the Ruby parser implementation.
17
 */
18
19
#include <stdint.h>
20
#include <stdlib.h>
21
#include <string>
22
#include <fuzzer/FuzzedDataProvider.h>
23
#include "ruby.h"
24
#include "ruby/ruby.h"
25
#include "ruby/encoding.h"
26
27
/* Forward declarations for parser functions */
28
extern "C" {
29
extern VALUE rb_parser_new(void);
30
extern VALUE rb_parser_compile_string(VALUE vparser, const char *f, VALUE s, int line);
31
extern VALUE ruby_verbose;
32
}
33
34
/* Silence stderr output during fuzzing */
35
static VALUE
36
silenced_parse(VALUE parser_str_pair)
37
16.8k
{
38
16.8k
    VALUE *args = (VALUE *)parser_str_pair;
39
16.8k
    VALUE parser = args[0];
40
16.8k
    VALUE code_str = args[1];
41
    
42
    /* Compile the string - this will parse it */
43
16.8k
    return rb_parser_compile_string(parser, "(fuzz)", code_str, 1);
44
16.8k
}
45
46
extern "C" int
47
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
48
16.8k
{
49
16.8k
    static int initialized = 0;
50
16.8k
    int state;
51
    
52
16.8k
    if (!initialized) {
53
1
        ruby_init();
54
1
        ruby_init_loadpath();
55
1
        initialized = 1;
56
        
57
        // Suppress Ruby warnings to avoid log noise
58
1
        ruby_verbose = Qfalse;
59
1
    }
60
    
61
16.8k
    if (size == 0) {
62
0
        return 0;
63
0
    }
64
    
65
    // Limit input size to avoid pathologically slow parsing
66
    // Ruby parser can be exponentially slow with deeply nested
67
    // structures resulting in timeouts.
68
16.8k
    if (size > 50000) {
69
6
        return 0;
70
6
    }
71
    // Use FuzzedDataProvider for structured data consumption
72
16.8k
    FuzzedDataProvider fdp(data, size);
73
    
74
    /* Create a Ruby string from the fuzz input */
75
16.8k
    std::string code_data = fdp.ConsumeRemainingBytesAsString();
76
16.8k
    VALUE code_str = rb_str_new(code_data.data(), code_data.size());
77
    
78
    /* Create a new parser instance */
79
16.8k
    VALUE parser = rb_parser_new();
80
16.8k
    if (NIL_P(parser)) {
81
0
        return 0;
82
0
    }
83
    
84
    /* Prepare arguments for protected call */
85
16.8k
    VALUE args[2];
86
16.8k
    args[0] = parser;
87
16.8k
    args[1] = code_str;
88
    
89
    /* Parse the code with exception protection */
90
16.8k
    rb_protect(silenced_parse, (VALUE)args, &state);
91
    
92
    /* If an exception occurred, clear it and continue */
93
16.8k
    if (state) {
94
398
        rb_set_errinfo(Qnil);
95
398
    }
96
    
97
    /* Force GC to clean up */
98
16.8k
    rb_gc_start();
99
    
100
16.8k
    return 0;
101
16.8k
}