fix(discover): address QA round 1 findings

- pipeline.c: inline userconfig cleanup on early-exit paths (discover
  failure and cancel check) so userconfig is always freed, fixing the
  resource leak when pipeline exits before reaching the cleanup label
- userconfig.c: free partially-built entries array on global config
  parse failure to fix alloc-failure memory leak
- language.c: probe user config for compound extensions (e.g.
  ".blade.php") by scanning from the first dot to the last, falling back
  to built-in single-extension lookup — preserves built-in behavior
  while correctly resolving user-defined compound extensions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Shane McCarronandClaude Sonnet 4.6 committed 2026-03-20 17:25:33 -05:00
1 parent 1cdb98319a
commit 1bfe473a76
3 files changed
+33 -5

No files matched your search

+22 -5
View File
@@ -386,13 +386,30 @@ CBMLanguage cbm_language_for_filename(const char *filename) {
}
}
/* Fall back to extension-based lookup */
const char *dot = strrchr(filename, '.');
if (dot) {
return cbm_language_for_extension(dot);
/* Fall back to extension-based lookup.
* For compound extensions (e.g. ".blade.php") defined in the user config,
* scan from the first dot in the basename toward the last, checking user
* config at each position. Built-in extensions use the last dot only. */
const char *last_dot = strrchr(filename, '.');
if (!last_dot) {
return CBM_LANG_COUNT;
}
return CBM_LANG_COUNT;
/* Probe user config for compound extensions (e.g. ".blade.php"). */
const cbm_userconfig_t *ucfg = cbm_get_user_lang_config();
if (ucfg) {
const char *p = strchr(filename, '.');
while (p && p < last_dot) {
CBMLanguage lang = cbm_userconfig_lookup(ucfg, p);
if (lang != CBM_LANG_COUNT) {
return lang;
}
p = strchr(p + 1, '.');
}
}
/* Standard single-extension lookup (built-ins + user overrides). */
return cbm_language_for_extension(last_dot);
}
const char *cbm_language_name(CBMLanguage lang) {
+4
View File
@@ -304,6 +304,10 @@ cbm_userconfig_t *cbm_userconfig_load(const char *repo_path) {
snprintf(global_path, sizeof(global_path), "%s/config.json", global_dir);
if (load_config_file(global_path, &entries, &count) != 0) {
for (int i = 0; i < count; i++) {
free(entries[i].ext);
}
free(entries);
free(cfg);
return NULL;
}
+7
View File
@@ -322,6 +322,10 @@ int cbm_pipeline_run(cbm_pipeline_t *p) {
int rc = cbm_discover(p->repo_path, &opts, &files, &file_count);
if (rc != 0) {
cbm_log_error("pipeline.err", "phase", "discover", "rc", itoa_buf(rc));
cbm_discover_free(files, file_count);
cbm_set_user_lang_config(NULL);
cbm_userconfig_free(p->userconfig);
p->userconfig = NULL;
return -1;
}
cbm_log_info("pipeline.discover", "files", itoa_buf(file_count), "elapsed_ms",
@@ -329,6 +333,9 @@ int cbm_pipeline_run(cbm_pipeline_t *p) {
if (check_cancel(p)) {
cbm_discover_free(files, file_count);
cbm_set_user_lang_config(NULL);
cbm_userconfig_free(p->userconfig);
p->userconfig = NULL;
return -1;
}