@@ -152,6 +152,190 @@ static bool def_label_is_preprocessed_callable(const char *label) {
152152 return label && (strcmp (label , "Function" ) == 0 || strcmp (label , "Method" ) == 0 );
153153}
154154
155+ static bool cbm_ident_char (char c ) {
156+ return isalnum ((unsigned char )c ) || c == '_' ;
157+ }
158+
159+ static bool cbm_plain_identifier (const char * s ) {
160+ if (!s || (!isalpha ((unsigned char )s [0 ]) && s [0 ] != '_' )) {
161+ return false;
162+ }
163+ for (const char * p = s + 1 ; * p ; p ++ ) {
164+ if (!cbm_ident_char (* p )) {
165+ return false;
166+ }
167+ }
168+ return true;
169+ }
170+
171+ static const char * cbm_last_qn_segment (const char * qn ) {
172+ if (!qn ) {
173+ return NULL ;
174+ }
175+ const char * dot = strrchr (qn , '.' );
176+ return dot ? dot + 1 : qn ;
177+ }
178+
179+ static bool cbm_ident_at (const char * source , int source_len , int pos , const char * ident ,
180+ int ident_len ) {
181+ if (pos < 0 || ident_len <= 0 || pos + ident_len > source_len ) {
182+ return false;
183+ }
184+ if (pos > 0 && cbm_ident_char (source [pos - 1 ])) {
185+ return false;
186+ }
187+ if (pos + ident_len < source_len && cbm_ident_char (source [pos + ident_len ])) {
188+ return false;
189+ }
190+ return strncmp (source + pos , ident , (size_t )ident_len ) == 0 ;
191+ }
192+
193+ static bool cbm_scope_before_name_matches (const char * source , int name_pos , const char * scope ) {
194+ if (!scope || !scope [0 ]) {
195+ return true;
196+ }
197+ const char * begin = source ;
198+ const char * p = source + name_pos ;
199+ while (p > begin && isspace ((unsigned char )p [-1 ])) {
200+ p -- ;
201+ }
202+ if (p - begin < 2 || p [-1 ] != ':' || p [-2 ] != ':' ) {
203+ return false;
204+ }
205+ p -= 2 ;
206+ while (p > begin && isspace ((unsigned char )p [-1 ])) {
207+ p -- ;
208+ }
209+ const char * end = p ;
210+ while (p > begin && cbm_ident_char (p [-1 ])) {
211+ p -- ;
212+ }
213+ size_t len = (size_t )(end - p );
214+ return len == strlen (scope ) && strncmp (p , scope , len ) == 0 ;
215+ }
216+
217+ static int cbm_find_matching_paren (const char * source , int source_len , int open_pos ) {
218+ int depth = 0 ;
219+ for (int i = open_pos ; i < source_len ; i ++ ) {
220+ if (source [i ] == '(' ) {
221+ depth ++ ;
222+ } else if (source [i ] == ')' ) {
223+ if (-- depth == 0 ) {
224+ return i ;
225+ }
226+ }
227+ }
228+ return -1 ;
229+ }
230+
231+ static bool cbm_span_ws_equal (const char * a , int alen , const char * b ) {
232+ int ai = 0 ;
233+ int bi = 0 ;
234+ int blen = b ? (int )strlen (b ) : 0 ;
235+ while (ai < alen || bi < blen ) {
236+ while (ai < alen && isspace ((unsigned char )a [ai ])) {
237+ ai ++ ;
238+ }
239+ while (bi < blen && isspace ((unsigned char )b [bi ])) {
240+ bi ++ ;
241+ }
242+ if (ai >= alen || bi >= blen ) {
243+ break ;
244+ }
245+ if (a [ai ++ ] != b [bi ++ ]) {
246+ return false;
247+ }
248+ }
249+ while (ai < alen && isspace ((unsigned char )a [ai ])) {
250+ ai ++ ;
251+ }
252+ while (bi < blen && isspace ((unsigned char )b [bi ])) {
253+ bi ++ ;
254+ }
255+ return ai == alen && bi == blen ;
256+ }
257+
258+ static int cbm_find_definition_open_brace (const char * source , int source_len , int after_paren ) {
259+ for (int i = after_paren + 1 ; i < source_len ; i ++ ) {
260+ if (source [i ] == '{' ) {
261+ return i ;
262+ }
263+ if (source [i ] == ';' ) {
264+ return -1 ; /* declaration, not definition */
265+ }
266+ }
267+ return -1 ;
268+ }
269+
270+ static int cbm_find_definition_close_brace (const char * source , int source_len , int open_pos ) {
271+ int depth = 0 ;
272+ for (int i = open_pos ; i < source_len ; i ++ ) {
273+ if (source [i ] == '{' ) {
274+ depth ++ ;
275+ } else if (source [i ] == '}' ) {
276+ if (-- depth == 0 ) {
277+ return i ;
278+ }
279+ }
280+ }
281+ return open_pos ;
282+ }
283+
284+ static uint32_t cbm_line_for_byte (const char * source , int byte_pos ) {
285+ uint32_t line = 1 ;
286+ for (int i = 0 ; i < byte_pos ; i ++ ) {
287+ if (source [i ] == '\n' ) {
288+ line ++ ;
289+ }
290+ }
291+ return line ;
292+ }
293+
294+ static bool cbm_remap_preprocessed_callable_lines (CBMDefinition * def , const char * source ,
295+ int source_len ) {
296+ if (!def || !source || source_len <= 0 || !cbm_plain_identifier (def -> name )) {
297+ return false;
298+ }
299+ const char * scope = NULL ;
300+ if (def -> label && strcmp (def -> label , "Method" ) == 0 ) {
301+ scope = cbm_last_qn_segment (def -> parent_class );
302+ }
303+ int name_len = (int )strlen (def -> name );
304+ for (int i = 0 ; i + name_len < source_len ; i ++ ) {
305+ if (!cbm_ident_at (source , source_len , i , def -> name , name_len )) {
306+ continue ;
307+ }
308+ if (!cbm_scope_before_name_matches (source , i , scope )) {
309+ continue ;
310+ }
311+ int p = i + name_len ;
312+ while (p < source_len && isspace ((unsigned char )source [p ])) {
313+ p ++ ;
314+ }
315+ if (p >= source_len || source [p ] != '(' ) {
316+ continue ;
317+ }
318+ int close_paren = cbm_find_matching_paren (source , source_len , p );
319+ if (close_paren < 0 ) {
320+ continue ;
321+ }
322+ if (def -> signature && !cbm_span_ws_equal (source + p , close_paren - p + 1 ,
323+ def -> signature )) {
324+ continue ;
325+ }
326+ int open_brace = cbm_find_definition_open_brace (source , source_len , close_paren );
327+ if (open_brace < 0 ) {
328+ continue ;
329+ }
330+ int close_brace = cbm_find_definition_close_brace (source , source_len , open_brace );
331+ def -> start_line = cbm_line_for_byte (source , i );
332+ def -> end_line = cbm_line_for_byte (source , close_brace );
333+ def -> lines = (int )(def -> end_line - def -> start_line + 1 );
334+ return true;
335+ }
336+ return false;
337+ }
338+
155339static bool result_has_same_def_identity (const CBMFileResult * result , const CBMDefinition * def ) {
156340 if (!result || !def || !def -> label || !def -> qualified_name ) {
157341 return false;
@@ -170,7 +354,8 @@ static bool result_has_same_def_identity(const CBMFileResult *result, const CBMD
170354}
171355
172356static void merge_missing_preprocessed_callables (CBMFileResult * dst , const CBMFileResult * src ,
173- CBMArena * arena ) {
357+ CBMArena * arena , const char * original_source ,
358+ int original_source_len ) {
174359 if (!dst || !src ) {
175360 return ;
176361 }
@@ -185,7 +370,10 @@ static void merge_missing_preprocessed_callables(CBMFileResult *dst, const CBMFi
185370 if (result_has_same_def_identity (dst , def )) {
186371 continue ;
187372 }
188- cbm_defs_push (& dst -> defs , arena , * def );
373+ CBMDefinition remapped = * def ;
374+ (void )cbm_remap_preprocessed_callable_lines (& remapped , original_source ,
375+ original_source_len );
376+ cbm_defs_push (& dst -> defs , arena , remapped );
189377 }
190378}
191379
@@ -858,10 +1046,165 @@ static bool cbm_region_is_recovered(uint32_t rs, uint32_t re, const CBMDefArray
8581046 return covered_to >= re ;
8591047}
8601048
861- static void cbm_subtract_recovered_regions (cbm_error_regions_t * regs , const CBMDefArray * defs ) {
1049+ typedef struct {
1050+ uint32_t starts [256 ];
1051+ uint32_t ends [256 ];
1052+ int count ;
1053+ } cbm_line_intervals_t ;
1054+
1055+ static bool cbm_lang_uses_c_preprocessor (CBMLanguage language ) {
1056+ return language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA ;
1057+ }
1058+
1059+ static bool cbm_def_is_recovery_evidence (const CBMDefinition * d , uint32_t rs , uint32_t re ) {
1060+ return d && d -> label && strcmp (d -> label , "Module" ) != 0 && strcmp (d -> label , "Package" ) != 0 &&
1061+ d -> start_line >= rs && d -> start_line <= re ;
1062+ }
1063+
1064+ static void cbm_line_intervals_push (cbm_line_intervals_t * ivals , uint32_t start , uint32_t end ) {
1065+ if (!ivals || ivals -> count >= (int )(sizeof (ivals -> starts ) / sizeof (ivals -> starts [0 ]))) {
1066+ return ;
1067+ }
1068+ ivals -> starts [ivals -> count ] = start ;
1069+ ivals -> ends [ivals -> count ] = end < start ? start : end ;
1070+ ivals -> count ++ ;
1071+ }
1072+
1073+ static bool cbm_line_intervals_cover (const cbm_line_intervals_t * ivals , uint32_t line ) {
1074+ if (!ivals ) {
1075+ return false;
1076+ }
1077+ for (int i = 0 ; i < ivals -> count ; i ++ ) {
1078+ if (line >= ivals -> starts [i ] && line <= ivals -> ends [i ]) {
1079+ return true;
1080+ }
1081+ }
1082+ return false;
1083+ }
1084+
1085+ static bool cbm_line_blank_or_preproc (const char * source , int start , int end , bool * is_preproc ) {
1086+ int p = start ;
1087+ while (p < end && isspace ((unsigned char )source [p ])) {
1088+ p ++ ;
1089+ }
1090+ if (is_preproc ) {
1091+ * is_preproc = p < end && source [p ] == '#' ;
1092+ }
1093+ return p >= end || (p < end && source [p ] == '#' );
1094+ }
1095+
1096+ static void cbm_collect_cpp_preproc_alt_signatures (const char * source , int source_len , uint32_t rs ,
1097+ uint32_t re , const CBMDefArray * defs ,
1098+ cbm_line_intervals_t * alts ) {
1099+ int line_start = 0 ;
1100+ uint32_t line = 1 ;
1101+ for (int p = 0 ; p <= source_len ; p ++ ) {
1102+ if (p < source_len && source [p ] != '\n' ) {
1103+ continue ;
1104+ }
1105+ int line_end = p ;
1106+ if (line >= rs && line <= re ) {
1107+ for (int di = 0 ; di < defs -> count ; di ++ ) {
1108+ const CBMDefinition * d = & defs -> items [di ];
1109+ if (!cbm_def_is_recovery_evidence (d , rs , re ) ||
1110+ !def_label_is_preprocessed_callable (d -> label ) || !cbm_plain_identifier (d -> name )) {
1111+ continue ;
1112+ }
1113+ const char * scope = NULL ;
1114+ if (strcmp (d -> label , "Method" ) == 0 ) {
1115+ scope = cbm_last_qn_segment (d -> parent_class );
1116+ }
1117+ int name_len = (int )strlen (d -> name );
1118+ for (int pos = line_start ; pos + name_len <= line_end ; pos ++ ) {
1119+ if (!cbm_ident_at (source , source_len , pos , d -> name , name_len ) ||
1120+ !cbm_scope_before_name_matches (source , pos , scope )) {
1121+ continue ;
1122+ }
1123+ int sig_start = pos + name_len ;
1124+ while (sig_start < source_len && isspace ((unsigned char )source [sig_start ])) {
1125+ sig_start ++ ;
1126+ }
1127+ if (sig_start >= source_len || source [sig_start ] != '(' ) {
1128+ continue ;
1129+ }
1130+ int close_paren = cbm_find_matching_paren (source , source_len , sig_start );
1131+ if (close_paren < 0 ) {
1132+ continue ;
1133+ }
1134+ int open_brace = cbm_find_definition_open_brace (source , source_len , close_paren );
1135+ if (open_brace < 0 ) {
1136+ continue ;
1137+ }
1138+ uint32_t open_line = cbm_line_for_byte (source , open_brace );
1139+ if (open_line >= line && open_line <= re ) {
1140+ cbm_line_intervals_push (alts , line , open_line );
1141+ }
1142+ }
1143+ }
1144+ }
1145+ line ++ ;
1146+ line_start = p + 1 ;
1147+ }
1148+ }
1149+
1150+ /* C-family preprocessor recovery: the raw tree can report one ERROR region
1151+ * spanning an entire #ifdef/#else signature block plus later definitions. Once
1152+ * the preprocessed pass recovers the selected callable and the trailing defs,
1153+ * unselected same-name signature lines are not a graph miss for that selected
1154+ * configuration. Different-name branches still stay flagged. */
1155+ static bool cbm_region_is_recovered_with_cpp_preproc (uint32_t rs , uint32_t re ,
1156+ const CBMDefArray * defs ,
1157+ const char * source , int source_len ,
1158+ CBMLanguage language ) {
1159+ if (!cbm_lang_uses_c_preprocessor (language ) || !source || source_len <= 0 ) {
1160+ return false;
1161+ }
1162+
1163+ cbm_line_intervals_t defs_cover = {{0 }, {0 }, 0 };
1164+ for (int i = 0 ; i < defs -> count ; i ++ ) {
1165+ const CBMDefinition * d = & defs -> items [i ];
1166+ if (cbm_def_is_recovery_evidence (d , rs , re )) {
1167+ cbm_line_intervals_push (& defs_cover , d -> start_line , d -> end_line );
1168+ }
1169+ }
1170+ if (defs_cover .count == 0 ) {
1171+ return false;
1172+ }
1173+
1174+ cbm_line_intervals_t alt_sigs = {{0 }, {0 }, 0 };
1175+ cbm_collect_cpp_preproc_alt_signatures (source , source_len , rs , re , defs , & alt_sigs );
1176+
1177+ bool saw_preproc = false;
1178+ int line_start = 0 ;
1179+ uint32_t line = 1 ;
1180+ for (int p = 0 ; p <= source_len ; p ++ ) {
1181+ if (p < source_len && source [p ] != '\n' ) {
1182+ continue ;
1183+ }
1184+ int line_end = p ;
1185+ if (line >= rs && line <= re ) {
1186+ bool is_preproc = false;
1187+ if (cbm_line_blank_or_preproc (source , line_start , line_end , & is_preproc )) {
1188+ saw_preproc = saw_preproc || is_preproc ;
1189+ } else if (!cbm_line_intervals_cover (& defs_cover , line ) &&
1190+ !cbm_line_intervals_cover (& alt_sigs , line )) {
1191+ return false;
1192+ }
1193+ }
1194+ line ++ ;
1195+ line_start = p + 1 ;
1196+ }
1197+ return saw_preproc ;
1198+ }
1199+
1200+ static void cbm_subtract_recovered_regions (cbm_error_regions_t * regs , const CBMDefArray * defs ,
1201+ const char * source , int source_len ,
1202+ CBMLanguage language ) {
8621203 int kept = 0 ;
8631204 for (int i = 0 ; i < regs -> count ; i ++ ) {
864- if (!cbm_region_is_recovered (regs -> starts [i ], regs -> ends [i ], defs )) {
1205+ if (!cbm_region_is_recovered (regs -> starts [i ], regs -> ends [i ], defs ) &&
1206+ !cbm_region_is_recovered_with_cpp_preproc (regs -> starts [i ], regs -> ends [i ], defs ,
1207+ source , source_len , language )) {
8651208 regs -> starts [kept ] = regs -> starts [i ];
8661209 regs -> ends [kept ] = regs -> ends [i ];
8671210 kept ++ ;
@@ -1141,7 +1484,8 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
11411484 CBMExtractCtx pp_defs_ctx = pp_ctx ;
11421485 pp_defs_ctx .result = & pp_defs_result ;
11431486 cbm_extract_definition_nodes (& pp_defs_ctx );
1144- merge_missing_preprocessed_callables (result , & pp_defs_result , a );
1487+ merge_missing_preprocessed_callables (result , & pp_defs_result , a , source ,
1488+ source_len );
11451489
11461490 // Re-run unified extraction on expanded source.
11471491 // This adds macro-expanded calls; duplicates with original calls are
@@ -1273,7 +1617,7 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
12731617 } else {
12741618 cbm_collect_error_regions (root , & regs );
12751619 }
1276- cbm_subtract_recovered_regions (& regs , & result -> defs );
1620+ cbm_subtract_recovered_regions (& regs , & result -> defs , source , source_len , language );
12771621 if (regs .count > 0 ) {
12781622 result -> parse_incomplete = true;
12791623 result -> error_region_count = regs .count ;
0 commit comments