fuzz_regexp_compile.c (5700B)
1 // Copyright 2025 Google LLC 2 // Fuzz target for QuickJS RegExp compilation 3 4 #include "quickjs.h" 5 #include "quickjs-libc.h" 6 #include <stdint.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <stdio.h> 10 11 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 12 if (size < 2) return 0; 13 14 JSRuntime* rt = JS_NewRuntime(); 15 if (!rt) return 0; 16 17 JSContext* ctx = JS_NewContext(rt); 18 if (!ctx) { 19 JS_FreeRuntime(rt); 20 return 0; 21 } 22 23 size_t pattern_len = size / 2; 24 size_t flags_len = size - pattern_len; 25 26 if (pattern_len == 0 || flags_len == 0) { 27 JS_FreeContext(ctx); 28 JS_FreeRuntime(rt); 29 return 0; 30 } 31 32 char* pattern = malloc(pattern_len + 1); 33 char* flags = malloc(flags_len + 1); 34 35 if (!pattern || !flags) { 36 free(pattern); 37 free(flags); 38 JS_FreeContext(ctx); 39 JS_FreeRuntime(rt); 40 return 0; 41 } 42 43 memcpy(pattern, data, pattern_len); 44 pattern[pattern_len] = '\0'; 45 46 memcpy(flags, data + pattern_len, flags_len); 47 flags[flags_len] = '\0'; 48 49 char valid_flags[16]; 50 size_t valid_idx = 0; 51 const char* valid = "gimsuy"; 52 for (size_t i = 0; i < flags_len && valid_idx < sizeof(valid_flags) - 1; i++) { 53 if (strchr(valid, flags[i]) && !strchr(valid_flags, flags[i])) { 54 valid_flags[valid_idx++] = flags[i]; 55 } 56 } 57 valid_flags[valid_idx] = '\0'; 58 59 char script[8192]; 60 char escaped_pattern[4096]; 61 size_t esc_idx = 0; 62 for (size_t i = 0; i < pattern_len && esc_idx < sizeof(escaped_pattern) - 2; i++) { 63 if (pattern[i] == '\\' || pattern[i] == '"' || pattern[i] == '\n' || 64 pattern[i] == '\r' || pattern[i] == '\t') { 65 escaped_pattern[esc_idx++] = '\\'; 66 } 67 escaped_pattern[esc_idx++] = pattern[i]; 68 } 69 escaped_pattern[esc_idx] = '\0'; 70 71 snprintf(script, sizeof(script), "new RegExp(\"%s\", \"%s\")", 72 escaped_pattern, valid_flags); 73 74 JSValue regexp_result = JS_Eval(ctx, script, strlen(script), "<regexp>", 0); 75 76 if (!JS_IsException(regexp_result)) { 77 const char* test_strings[] = { 78 "'test string'", 79 "''", 80 "'aaaaaaaaaa'", 81 "'1234567890'", 82 "'!@#$%^&*()'", 83 }; 84 85 for (size_t i = 0; i < sizeof(test_strings) / sizeof(test_strings[0]); i++) { 86 char match_script[4096]; 87 snprintf(match_script, sizeof(match_script), 88 "var re = %s; re.test(%s); re.exec(%s); %s.match(re);", 89 script, test_strings[i], test_strings[i], test_strings[i]); 90 91 JSValue match_result = JS_Eval(ctx, match_script, strlen(match_script), 92 "<regexp-match>", 0); 93 if (!JS_IsException(match_result)) { 94 JS_FreeValue(ctx, match_result); 95 } else { 96 JS_GetException(ctx); 97 } 98 } 99 100 const char* split_test = "'a,b,c,d'.split(/,/)"; 101 JSValue split_result = JS_Eval(ctx, split_test, strlen(split_test), 102 "<regexp-split>", 0); 103 if (!JS_IsException(split_result)) { 104 JS_FreeValue(ctx, split_result); 105 } else { 106 JS_GetException(ctx); 107 } 108 109 const char* replace_test = "'hello world'.replace(/world/, 'universe')"; 110 JSValue replace_result = JS_Eval(ctx, replace_test, strlen(replace_test), 111 "<regexp-replace>", 0); 112 if (!JS_IsException(replace_result)) { 113 JS_FreeValue(ctx, replace_result); 114 } else { 115 JS_GetException(ctx); 116 } 117 118 const char* search_test = "'abc123def'.search(/[0-9]+/)"; 119 JSValue search_result = JS_Eval(ctx, search_test, strlen(search_test), 120 "<regexp-search>", 0); 121 if (!JS_IsException(search_result)) { 122 JS_FreeValue(ctx, search_result); 123 } else { 124 JS_GetException(ctx); 125 } 126 127 JS_FreeValue(ctx, regexp_result); 128 } else { 129 JS_GetException(ctx); 130 } 131 132 char literal_script[4096]; 133 char slash_escaped[2048]; 134 size_t slash_idx = 0; 135 for (size_t i = 0; i < pattern_len && slash_idx < sizeof(slash_escaped) - 2; i++) { 136 if (pattern[i] == '/') { 137 slash_escaped[slash_idx++] = '\\'; 138 } 139 slash_escaped[slash_idx++] = pattern[i]; 140 } 141 slash_escaped[slash_idx] = '\0'; 142 143 snprintf(literal_script, sizeof(literal_script), "/%s/%s.test('test')", slash_escaped, valid_flags); 144 145 JSValue literal_result = JS_Eval(ctx, literal_script, strlen(literal_script), 146 "<regexp-literal>", 0); 147 if (!JS_IsException(literal_result)) { 148 JS_FreeValue(ctx, literal_result); 149 } else { 150 JS_GetException(ctx); 151 } 152 153 const char* builtin_tests[] = { 154 "RegExp.prototype.compile", 155 "/a/g[Symbol.match]('a')", 156 "/a/g[Symbol.replace]('a', 'b')", 157 "/a/g[Symbol.search]('a')", 158 "/a/g[Symbol.split]('a,b,a')", 159 }; 160 161 for (size_t i = 0; i < sizeof(builtin_tests) / sizeof(builtin_tests[0]); i++) { 162 JSValue r = JS_Eval(ctx, builtin_tests[i], strlen(builtin_tests[i]), 163 "<regexp-builtin>", 0); 164 if (!JS_IsException(r)) { 165 JS_FreeValue(ctx, r); 166 } else { 167 JS_GetException(ctx); 168 } 169 } 170 171 free(pattern); 172 free(flags); 173 JS_FreeContext(ctx); 174 JS_FreeRuntime(rt); 175 176 return 0; 177 }