| 1127 | } |
| 1128 | |
| 1129 | CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLanguage language, |
| 1130 | const char *project, const char *rel_path, |
| 1131 | int64_t timeout_micros, const char **extra_defines, |
| 1132 | const char **include_paths, const CBMMacroTable *macro_table, |
| 1133 | const CBMReturnTypeTable *return_type_table) { |
| 1134 | // Allocate result on heap (arena inside for all string data) |
| 1135 | enum { SINGLE = 1 }; |
| 1136 | CBMFileResult *result = (CBMFileResult *)calloc(SINGLE, sizeof(CBMFileResult)); |
| 1137 | if (!result) { |
| 1138 | return NULL; |
| 1139 | } |
| 1140 | |
| 1141 | cbm_arena_init(&result->arena); |
| 1142 | CBMArena *a = &result->arena; |
| 1143 | |
| 1144 | /* Crash-quarantine hard guard (Stage 3c): a file the supervisor pinned as a |
| 1145 | * crasher must NEVER be parsed again. Return a clean empty result BEFORE the |
| 1146 | * marker write and fault injector so no pass (including sequential re-extract |
| 1147 | * passes that miss the result cache) can crash on it. The pipeline extract |
| 1148 | * loops separately record it as a phase="crash" skip. Checked before the |
| 1149 | * marker so quarantined files never overwrite it — the marker keeps pointing |
| 1150 | * at the real (non-quarantined) file being processed when a crash hits. */ |
| 1151 | if (cbm_index_is_quarantined(rel_path)) { |
| 1152 | return result; |
| 1153 | } |
| 1154 | |
| 1155 | cbm_index_mark_start(rel_path); |
| 1156 | #ifdef CBM_ENABLE_TEST_SEAMS |
| 1157 | cbm_test_fault_inject(rel_path); |
| 1158 | #endif |
| 1159 | |
| 1160 | // Get language spec |
| 1161 | const CBMLangSpec *spec = cbm_lang_spec(language); |
| 1162 | if (!spec) { |
| 1163 | result->has_error = true; |
| 1164 | result->error_msg = cbm_arena_strdup(a, "unsupported language"); |
| 1165 | cbm_index_mark_done(rel_path); |
| 1166 | return result; |
| 1167 | } |
| 1168 | |
| 1169 | // Get tree-sitter language |
| 1170 | const TSLanguage *ts_lang = cbm_ts_language(language); |
| 1171 | if (!ts_lang) { |
| 1172 | result->has_error = true; |
| 1173 | result->error_msg = cbm_arena_strdup(a, "no tree-sitter grammar"); |
| 1174 | cbm_index_mark_done(rel_path); |
| 1175 | return result; |
| 1176 | } |
| 1177 | |
| 1178 | // Skip pathologically nested Perl before tree-sitter's recursive GLR stack |
| 1179 | // merge overflows a small stack during the parse (see |
| 1180 | // cbm_source_nesting_exceeds). Scoped to Perl: its ambiguous call grammar is |
| 1181 | // the only one that drives that recursion to the nesting depth. |
| 1182 | if (language == CBM_LANG_PERL && |
| 1183 | cbm_source_nesting_exceeds(source, source_len, CBM_PERL_MAX_PARSE_NESTING)) { |
| 1184 | result->has_error = true; |
| 1185 | result->error_msg = cbm_arena_strdup(a, "perl source nesting too deep; skipped"); |
| 1186 | return result; |