Coverage Report

Created: 2026-03-31 07:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fuzz_json.c
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 JSON parser (ext/json)
15
 * Tests JSON parsing with malformed/corrupted input
16
 */
17
18
#include <stdint.h>
19
#include <stddef.h>
20
#include <stdlib.h>
21
#include "ruby.h"
22
#include "../ruby/ext/json/json.h"
23
#include "../ruby/ext/json/vendor/ryu.h"
24
#include "../ruby/ext/json/parser/parser.c"
25
26
static int ruby_initialized = 0;
27
28
// External declaration for ruby_verbose
29
extern VALUE ruby_verbose;
30
31
// JSON parser wrapper - parses JSON string with default config
32
10.1k
static VALUE json_fuzzer_parse(VALUE json_str) {
33
10.1k
    JSON_ParserConfig config = {
34
10.1k
        .on_load_proc = Qfalse,
35
10.1k
        .decimal_class = Qfalse,
36
10.1k
        .decimal_method_id = 0,
37
10.1k
        .on_duplicate_key = JSON_RAISE,
38
10.1k
        .max_nesting = 100,
39
10.1k
        .allow_nan = 0,
40
10.1k
        .allow_trailing_comma = 0,
41
10.1k
        .symbolize_names = 0,
42
10.1k
        .freeze = 0
43
10.1k
    };
44
    
45
10.1k
    return cParser_parse(&config, json_str);
46
10.1k
}
47
48
// Test JSON parsing with fuzzer input
49
10.1k
static VALUE call_json_parse(VALUE arg) {
50
10.1k
    VALUE json_str = (VALUE)arg;
51
10.1k
    VALUE result = json_fuzzer_parse(json_str);
52
    
53
    // Access the result to ensure it was properly parsed
54
10.1k
    if (!NIL_P(result)) {
55
3.45k
        rb_funcall(result, rb_intern("class"), 0);
56
        
57
3.45k
        if (RB_TYPE_P(result, T_HASH)) {
58
0
            rb_funcall(result, rb_intern("keys"), 0);
59
3.45k
        } else if (RB_TYPE_P(result, T_ARRAY)) {
60
0
            rb_funcall(result, rb_intern("size"), 0);
61
0
        }
62
3.45k
    }
63
    
64
10.1k
    return result;
65
10.1k
}
66
67
10.1k
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
68
10.1k
    if (!ruby_initialized) {
69
1
        ruby_init();
70
1
        ruby_initialized = 1;
71
        
72
        // Suppress Ruby warnings to avoid log noise
73
1
        ruby_verbose = Qfalse;
74
1
    }
75
    
76
10.1k
    if (size == 0) {
77
0
        return 0;
78
0
    }
79
    
80
10.1k
    VALUE json_str = rb_str_new((const char *)data, size);
81
    
82
10.1k
    int state = 0;
83
10.1k
    rb_protect(call_json_parse, json_str, &state);
84
    
85
10.1k
    if (state) {
86
10.1k
        rb_set_errinfo(Qnil);
87
10.1k
    }
88
    
89
10.1k
    rb_gc_start();
90
    
91
10.1k
    return 0;
92
10.1k
}