| 333 | /* ── method lookup with parent walk ─────────────────────────────── */ |
| 334 | |
| 335 | const CBMRegisteredFunc *php_lookup_method(PHPLSPContext *ctx, const char *class_qn, |
| 336 | const char *method_name) { |
| 337 | if (!class_qn || !method_name) |
| 338 | return NULL; |
| 339 | |
| 340 | /* Direct lookup by the registry's receiver_qn -> method_name index. */ |
| 341 | const CBMRegisteredFunc *f = cbm_registry_lookup_method(ctx->registry, class_qn, method_name); |
| 342 | if (f) |
| 343 | return f; |
| 344 | |
| 345 | /* If the QN we have isn't the registry's canonical key, resolve the |
| 346 | * type to its registered identity and retry. */ |
| 347 | const CBMRegisteredType *t = cbm_registry_lookup_type(ctx->registry, class_qn); |
| 348 | if (!t) |
| 349 | t = lookup_type_with_project(ctx, class_qn); |
| 350 | if (!t) |
| 351 | return NULL; |
| 352 | if (strcmp(t->qualified_name, class_qn) != 0) { |
| 353 | f = cbm_registry_lookup_method(ctx->registry, t->qualified_name, method_name); |
| 354 | if (f) |
| 355 | return f; |
| 356 | } |
| 357 | |
| 358 | /* Walk the full ancestor chain. PHP only allows single inheritance for |
| 359 | * `extends`, but a class may also pick up methods from `implements` / |
| 360 | * traits, both of which are recorded in embedded_types. We iterate |
| 361 | * across all entries and recurse into each branch with cycle-detection. |
| 362 | * |
| 363 | * Cap depth at 16 across all visited types to bound runtime. */ |
| 364 | const char *visited[32]; |
| 365 | int visited_count = 0; |
| 366 | const char *frontier[32]; |
| 367 | int frontier_count = 0; |
| 368 | if (t->embedded_types) { |
| 369 | for (int i = 0; t->embedded_types[i] && frontier_count < 32; i++) { |
| 370 | frontier[frontier_count++] = t->embedded_types[i]; |
| 371 | } |
| 372 | } |
| 373 | while (frontier_count > 0 && visited_count < 32) { |
| 374 | const char *parent = frontier[--frontier_count]; |
| 375 | bool seen = false; |
| 376 | for (int v = 0; v < visited_count; v++) { |
| 377 | if (strcmp(visited[v], parent) == 0) { |
| 378 | seen = true; |
| 379 | break; |
| 380 | } |
| 381 | } |
| 382 | if (seen) |
| 383 | continue; |
| 384 | visited[visited_count++] = parent; |
| 385 | |
| 386 | f = cbm_registry_lookup_method(ctx->registry, parent, method_name); |
| 387 | if (f) |
| 388 | return f; |
| 389 | |
| 390 | const CBMRegisteredType *next = cbm_registry_lookup_type(ctx->registry, parent); |
| 391 | if (!next) |
| 392 | next = lookup_type_with_project(ctx, parent); |
no test coverage detected