Callback context for import_map_suffix scan */ Strategy 1: Import map lookup (exact → suffix fallback) */
| 588 | /* Callback context for import_map_suffix scan */ |
| 589 | /* Strategy 1: Import map lookup (exact → suffix fallback) */ |
| 590 | static cbm_resolution_t resolve_import_map(const cbm_registry_t *r, const char *prefix, |
| 591 | const char *suffix, const char **keys, const char **vals, |
| 592 | int map_count) { |
| 593 | if (!keys || !vals || map_count <= 0) { |
| 594 | return empty_result(); |
| 595 | } |
| 596 | |
| 597 | /* Find prefix in import map keys. Prefer the per-file TLS hash |
| 598 | * cache (O(1)) over the linear scan when available. */ |
| 599 | const char *resolved = NULL; |
| 600 | if (_import_map_cache) { |
| 601 | resolved = (const char *)cbm_ht_get(_import_map_cache, prefix); |
| 602 | } else { |
| 603 | for (int i = 0; i < map_count; i++) { |
| 604 | if (strcmp(keys[i], prefix) == 0) { |
| 605 | resolved = vals[i]; |
| 606 | break; |
| 607 | } |
| 608 | } |
| 609 | } |
| 610 | if (!resolved) { |
| 611 | return empty_result(); |
| 612 | } |
| 613 | |
| 614 | /* Build candidate: resolved.suffix or resolved.prefix. |
| 615 | * When the callee has a dot ("pkg.Func"), prefix="pkg" is the import key |
| 616 | * and suffix="Func" is the function name, so the target QN is |
| 617 | * resolved.Func. When the callee is bare ("requireAdmin"), prefix IS the |
| 618 | * function name and suffix is NULL, so the target QN must be |
| 619 | * resolved.requireAdmin — not just resolved, which would point at the |
| 620 | * module node and miss the function entirely. */ |
| 621 | /* Direct hit ONLY for suffix-less callees (an aliased direct-symbol |
| 622 | * import called bare: `from m import f as g; g()` — #875/#979). With a |
| 623 | * suffix present (`imported.method()`), returning the bare base here |
| 624 | * would swallow the suffix and bind the call to the imported symbol's |
| 625 | * own node (a Variable/Class/module) instead of base.method — exactly |
| 626 | * the mis-resolution the comment above warns about. That regressed |
| 627 | * django-scale graphs by ~11K CALLS/TESTS edges (Signal.send calls |
| 628 | * degraded to edges onto the signal variables themselves). #1000 */ |
| 629 | if (!suffix || !suffix[0]) { |
| 630 | const char *direct = cbm_ht_get_key(r->exact, resolved); |
| 631 | if (direct) { |
| 632 | return (cbm_resolution_t){direct, "import_map", CONF_IMPORT_MAP, REG_RESOLVED}; |
| 633 | } |
| 634 | } |
| 635 | char candidate[CBM_SZ_512]; |
| 636 | if (suffix && suffix[0]) { |
| 637 | snprintf(candidate, sizeof(candidate), "%s.%s", resolved, suffix); |
| 638 | } else { |
| 639 | snprintf(candidate, sizeof(candidate), "%s.%s", resolved, prefix); |
| 640 | } |
| 641 | /* Use cbm_ht_get_key to get the persistent heap-owned key string */ |
| 642 | const char *stored_key = cbm_ht_get_key(r->exact, candidate); |
| 643 | if (stored_key) { |
| 644 | return (cbm_resolution_t){stored_key, "import_map", CONF_IMPORT_MAP, REG_RESOLVED}; |
| 645 | } |
| 646 | |
| 647 | /* import_map_suffix fallback: find a QN starting with resolved+"." and |
no test coverage detected