| 1163 | } |
| 1164 | |
| 1165 | CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLanguage language, |
| 1166 | const char *project, const char *rel_path, |
| 1167 | int64_t timeout_micros, const char **extra_defines, |
| 1168 | const char **include_paths, const CBMMacroTable *macro_table, |
| 1169 | const CBMReturnTypeTable *return_type_table) { |
| 1170 | // Allocate result on heap (arena inside for all string data) |
| 1171 | enum { SINGLE = 1 }; |
| 1172 | CBMFileResult *result = (CBMFileResult *)calloc(SINGLE, sizeof(CBMFileResult)); |
| 1173 | if (!result) { |
| 1174 | return NULL; |
| 1175 | } |
| 1176 | |
| 1177 | cbm_arena_init(&result->arena); |
| 1178 | CBMArena *a = &result->arena; |
| 1179 | |
| 1180 | /* Crash-quarantine hard guard (Stage 3c): a file the supervisor pinned as a |
| 1181 | * crasher must NEVER be parsed again. Return a clean empty result BEFORE the |
| 1182 | * marker write and fault injector so no pass (including sequential re-extract |
| 1183 | * passes that miss the result cache) can crash on it. The pipeline extract |
| 1184 | * loops separately record it as a phase="crash" skip. Checked before the |
| 1185 | * marker so quarantined files never overwrite it — the marker keeps pointing |
| 1186 | * at the real (non-quarantined) file being processed when a crash hits. */ |
| 1187 | if (cbm_index_is_quarantined(rel_path)) { |
| 1188 | return result; |
| 1189 | } |
| 1190 | |
| 1191 | cbm_index_mark_start(rel_path); |
| 1192 | #ifdef CBM_ENABLE_TEST_SEAMS |
| 1193 | cbm_test_fault_inject(rel_path); |
| 1194 | #endif |
| 1195 | |
| 1196 | // Get language spec |
| 1197 | const CBMLangSpec *spec = cbm_lang_spec(language); |
| 1198 | if (!spec) { |
| 1199 | result->has_error = true; |
| 1200 | result->error_msg = cbm_arena_strdup(a, "unsupported language"); |
| 1201 | cbm_index_mark_done(rel_path); |
| 1202 | return result; |
| 1203 | } |
| 1204 | |
| 1205 | // Get tree-sitter language |
| 1206 | const TSLanguage *ts_lang = cbm_ts_language(language); |
| 1207 | if (!ts_lang) { |
| 1208 | result->has_error = true; |
| 1209 | result->error_msg = cbm_arena_strdup(a, "no tree-sitter grammar"); |
| 1210 | cbm_index_mark_done(rel_path); |
| 1211 | return result; |
| 1212 | } |
| 1213 | |
| 1214 | // Skip pathologically nested Perl before tree-sitter's recursive GLR stack |
| 1215 | // merge overflows a small stack during the parse (see |
| 1216 | // cbm_source_nesting_exceeds). Scoped to Perl: its ambiguous call grammar is |
| 1217 | // the only one that drives that recursion to the nesting depth. |
| 1218 | if (language == CBM_LANG_PERL && |
| 1219 | cbm_source_nesting_exceeds(source, source_len, CBM_PERL_MAX_PARSE_NESTING)) { |
| 1220 | result->has_error = true; |
| 1221 | result->error_msg = cbm_arena_strdup(a, "perl source nesting too deep; skipped"); |
| 1222 | return result; |