fix(extraction): keep Swift initializer edges to true targets

Follow-up to the review of #2395 (issue #2379). Making init/deinit
Method nodes gives every type an `init` target, and the short-name
registry strategies then bound initializer calls to whichever `init`
was nearest. On Alamofire (bda9ed57) all 8 new CALLS edges into an
initializer were false, and so were the 40 new USAGE edges.

- Name init/deinit in one Swift-gated place, cbm_resolve_func_name;
  resolve_method_name delegates there. The language-agnostic
  func_name_node no longer special-cases the node kinds (the Slang
  grammar has an init_declaration too).
- super.init(...) is emitted as <Superclass>.init, from the first
  entry of the enclosing class's inheritance clause. The class node is
  carried by the unified walk (enclosing_class_node, saved and restored
  with the other frame state) instead of found by climbing parents.
  Without a clause, or with an entry that does not read as a type name,
  there is no call.
- self.init(...) forms no call. Initializer overloads share one node,
  so the only possible edge is a self-loop, and that is false whenever
  the overload it delegates to lives outside the project.
- T.init(...) calls are flagged is_method, and Swift joins the
  weak-member guard in pass_calls.c and pass_parallel.c (in lockstep),
  so only qualified, same-module or import placements survive. No
  other Swift call carries the flag.
- init and deinit are Swift keywords for cbm_is_keyword, so references
  such as .init(...) and Foo.init are not usages resolved by name.

Tests: the exact CALLS set for the #2379 reproduction plus initializer
delegation in a SwiftPM package, through the sequential and the
parallel resolver; extraction tests for the superclass rewrite, the
no-superclass and lower-case cases, the scope of the call flag and
init references. Review nits: renamed the overload test, removed the
stacked helper comment.

Alamofire, main / PR head / this commit: CALLS 5472 / 5487 / 5485;
CALLS into an initializer 0 / 8 (all false) / 6 (all superclass
initializers); USAGE into an initializer 0 / 40 / 0.

Co-authored-by: Andrew Studnicky <a.j.studnicky@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin VogelandAndrew Studnicky committed 2026-10-20 19:30:58 +02:00
1 parent 468102c707
commit 71ab015483
9 files changed
+492 -14

No files matched your search

+75
View File
@@ -4278,6 +4278,70 @@ static TSNode language_specific_callee_leaf(CBMLanguage language, TSNode expr,
return terminal_callee_leaf(ctx, expr);
}
/* `Base.init` for a `super.init(...)` call: Base is the first entry of the
* enclosing class's inheritance clause, where Swift requires the superclass to
* stand. The class node is carried by the walk, so no ancestor climb. NULL (no
* call) outside a class, without a clause, or when the entry does not read as
* a plain type name -- a lower-case or attributed root would pass the
* registry's receiver-chain check and bind by short name, which is the false
* edge this exists to prevent. */
static const char *swift_superclass_init_callee(CBMExtractCtx *ctx, const WalkState *state) {
TSNode cls = state ? state->enclosing_class_node : (TSNode){0};
if (ts_node_is_null(cls) || strcmp(ts_node_type(cls), "class_declaration") != 0) {
return NULL;
}
TSNode spec = cbm_find_child_by_kind(cls, "inheritance_specifier");
char *base = ts_node_is_null(spec) ? NULL : cbm_node_text(ctx->arena, spec, ctx->source);
if (!base || base[0] < 'A' || base[0] > 'Z') {
return NULL;
}
size_t len = strcspn(base, "<"); /* Base<T> names Base */
for (size_t i = 0; i < len; i++) {
if (!isalnum((unsigned char)base[i]) && base[i] != '_' && base[i] != '.') {
return NULL;
}
}
base[len] = '\0';
return cbm_arena_sprintf(ctx->arena, "%s.init", base);
}
/* True for a Swift initializer callee `T.init` (T non-empty). */
static bool swift_callee_is_initializer(const char *callee) {
static const char suffix[] = ".init";
size_t n = callee ? strlen(callee) : 0;
return n > sizeof(suffix) - 1 && strcmp(callee + n - (sizeof(suffix) - 1), suffix) == 0;
}
/* Swift initializer delegation (#2379). `super.init(...)` and `self.init(...)`
* name no type, and a lower-case receiver passes the registry's receiver-chain
* check, so a short-name match binds them to whichever project `init` is
* nearest: a nested type's, a sibling type's, or the caller's own. On
* Alamofire all 8 such edges were false.
*
* `super.init` is rewritten to name the superclass (`Base.init`), so the
* qualified strategies place it on the project's Base.init. When Base is not
* in the project (UIViewController, NSObject) nothing places it, and the
* initializer guard in handle_calls keeps a short-name guess from doing so.
*
* `self.init` delegates to another initializer of the caller's own type.
* Initializer overloads share one node (#2061), so the only edge it could form
* is a self-loop, and that one is false whenever the overload it calls lives
* outside the project (`extension Notification { init(...) { self.init(...) } }`
* calls Foundation's). It forms no CALLS edge. */
static const char *resolve_swift_delegating_init(CBMExtractCtx *ctx, const WalkState *state,
const char *callee) {
if (ctx->language != CBM_LANG_SWIFT || !callee) {
return callee;
}
if (strcmp(callee, "self.init") == 0) {
return NULL;
}
if (strcmp(callee, "super.init") == 0) {
return swift_superclass_init_callee(ctx, state);
}
return callee;
}
// Produce the textual callee and the AST occurrence from one operation. The
// generic fallback retains the former field/terminal behavior, while grammars
// whose extractor uses a sibling, a special field, or a synthetic literal are
@@ -4288,6 +4352,7 @@ static CBMPrimaryCalleeSelection select_primary_callee(CBMExtractCtx *ctx, TSNod
CBMPrimaryCalleeSelection selection = {0};
selection.name = extract_callee_name(ctx->arena, node, ctx->source, ctx->language);
selection.name = resolve_objectscript_callee(ctx, node, state, selection.name);
selection.name = resolve_swift_delegating_init(ctx, state, selection.name);
if (!selection.name || !selection.name[0]) {
return selection;
}
@@ -4511,6 +4576,16 @@ CBMInvocationDescriptor handle_calls(CBMExtractCtx *ctx, TSNode node, const CBML
call.is_method = true;
}
}
// Swift initializer guard (#2379; same direction). Flag `T.init(...)`,
// as written or as a rewritten super.init. Nearly every type has an
// `init`, so a short-name match onto one is a guess: with a project
// module imported, `UIViewController.init` bound an unrelated
// project initializer. The registry's qualified / same-module /
// import strategies still place T.init when T is a project type.
// No other Swift call carries the flag.
if (ctx->language == CBM_LANG_SWIFT && swift_callee_is_initializer(call.callee_name)) {
call.is_method = true;
}
TSNode args = ts_node_child_by_field_name(node, TS_FIELD("arguments"));
/* tree-sitter-elixir attaches NO field name to a call's arguments
+15 -5
View File
@@ -236,11 +236,6 @@ static void extract_elixir_call(CBMExtractCtx *ctx, TSNode node, const CBMLangSp
// Get "name" field from a node
static TSNode func_name_node(TSNode node) {
const char *node_type = ts_node_type(node);
if (strcmp(node_type, "init_declaration") == 0 ||
strcmp(node_type, "deinit_declaration") == 0) {
return node;
}
TSNode name = ts_node_child_by_field_name(node, TS_FIELD("name"));
if (ts_node_is_null(name)) {
/* Protobuf rpc: name is in rpc_name child, not "name" field */
@@ -774,6 +769,15 @@ TSNode cbm_resolve_func_name(TSNode node, CBMLanguage lang) {
return cbm_find_child_by_kind(node, "query_name");
}
/* Swift init/deinit (#2379) have no name node at all. The declaration
* itself stands in for it, and cbm_func_name_node_text maps that
* node kind to "init" / "deinit". Gated on Swift: Slang's grammar
* also has an init_declaration node. */
if (lang == CBM_LANG_SWIFT &&
(strcmp(kind, "init_declaration") == 0 || strcmp(kind, "deinit_declaration") == 0)) {
return node;
}
TSNode name = func_name_node(node);
if (lang == CBM_LANG_R && strcmp(kind, "function_definition") == 0) {
@@ -8674,6 +8678,12 @@ static TSNode resolve_method_name(TSNode child, CBMLanguage lang) {
return cbm_find_child_by_kind(child, "simple_identifier");
}
// Swift init/deinit: named in one place, cbm_resolve_func_name (#2379).
if (lang == CBM_LANG_SWIFT &&
(strcmp(ck, "init_declaration") == 0 || strcmp(ck, "deinit_declaration") == 0)) {
return cbm_resolve_func_name(child, lang);
}
// Squirrel: function_declaration's name is a plain `identifier` child.
if (lang == CBM_LANG_SQUIRREL && strcmp(ck, "function_declaration") == 0) {
return cbm_find_child_by_kind(child, "identifier");
+8
View File
@@ -324,6 +324,7 @@ static bool push_scope(WalkState *state, uint8_t kind, uint32_t depth, const cha
* CBMWalkScope comment for the quadratic recompute this replaces. */
f->prev_enclosing_func_qn = state->enclosing_func_qn;
f->prev_enclosing_class_qn = state->enclosing_class_qn;
f->prev_enclosing_class_node = state->enclosing_class_node;
f->prev_invocation_kind = state->invocation_kind;
f->prev_callee_expr = state->callee_expr;
f->prev_callee_leaf = state->callee_leaf;
@@ -337,6 +338,8 @@ static bool push_scope(WalkState *state, uint8_t kind, uint32_t depth, const cha
case SCOPE_CLASS:
case SCOPE_NAMESPACE:
state->enclosing_class_qn = qn;
/* push_lexical_scope records the node; a node-less push has none. */
state->enclosing_class_node = (TSNode){0};
break;
case SCOPE_CALL:
/* The invocation triple is applied by push_call_scope once the caller
@@ -364,6 +367,9 @@ static bool push_lexical_scope(WalkState *state, uint8_t walk_kind, uint32_t dep
state->lexical_binding_tracking_failed = true;
return false;
}
if (walk_kind == SCOPE_CLASS || walk_kind == SCOPE_NAMESPACE) {
state->enclosing_class_node = node;
}
state->scopes[state->scope_top - SKIP_ONE].lexical_scope_id =
add_lexical_scope(state, node, lexical_kind);
return true;
@@ -442,6 +448,7 @@ static void pop_expired_scopes(WalkState *state, uint32_t cur_depth) {
const CBMWalkScope *f = &state->scopes[--state->scope_top];
state->enclosing_func_qn = f->prev_enclosing_func_qn;
state->enclosing_class_qn = f->prev_enclosing_class_qn;
state->enclosing_class_node = f->prev_enclosing_class_node;
state->invocation_kind = f->prev_invocation_kind;
state->callee_expr = f->prev_callee_expr;
state->callee_leaf = f->prev_callee_leaf;
@@ -2671,6 +2678,7 @@ void cbm_extract_unified(CBMExtractCtx *ctx) {
* recompute_state call): module scope, nothing else active. */
state.enclosing_func_qn = ctx->module_qn;
state.enclosing_class_qn = NULL;
state.enclosing_class_node = (TSNode){0};
state.invocation_kind = CBM_INVOCATION_NONE;
state.callee_expr = (TSNode){0};
state.callee_leaf = (TSNode){0};
+2
View File
@@ -78,6 +78,7 @@ typedef struct {
* suite-budget kills on every non-M4 venue). */
const char *prev_enclosing_func_qn;
const char *prev_enclosing_class_qn;
TSNode prev_enclosing_class_node;
CBMInvocationKind prev_invocation_kind;
TSNode prev_callee_expr;
TSNode prev_callee_leaf;
@@ -133,6 +134,7 @@ typedef struct {
typedef struct {
const char *enclosing_func_qn; // current function QN (module_qn at top level)
const char *enclosing_class_qn; // current class QN (NULL outside class)
TSNode enclosing_class_node; // that class scope's node (null when not known)
const TSTreeCursor *current_cursor; // unified walk cursor at the current node
TSTreeCursor *occurrence_cursor; // reusable parent-preserving classifier cursor
CBMInvocationKind invocation_kind; // exact active invocation/reference role
+10
View File
@@ -301,6 +301,16 @@ bool cbm_is_keyword(const char *name, CBMLanguage lang) {
case CBM_LANG_PUPPET:
keywords = puppet_keywords;
break;
case CBM_LANG_SWIFT:
/* `init` and `deinit` are Swift keywords. A reference to one
* (`.init(...)`, `Foo.init`) names an initializer of a type the bare
* name does not carry, so resolving it by name can only guess among
* every type's initializer (#2379). Otherwise the generic set. */
if (strcmp(name, "init") == 0 || strcmp(name, "deinit") == 0) {
return true;
}
keywords = generic_keywords;
break;
default:
keywords = generic_keywords;
break;
+5 -1
View File
@@ -1133,7 +1133,11 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBM
* to a docs bundle (2026-09-16 probe: 4,207 junk
* edges on JetBrains/Exposed). */
lang == CBM_LANG_HTML || lang == CBM_LANG_VUE ||
lang == CBM_LANG_SVELTE || lang == CBM_LANG_ASTRO;
lang == CBM_LANG_SVELTE || lang == CBM_LANG_ASTRO ||
/* Swift flags only initializer calls `T.init(...)`
* (#2379, extract_calls.c); every other Swift call
* keeps is_method=false and is unaffected. */
lang == CBM_LANG_SWIFT;
/* Bare-call local-binding suppression. A member call has a receiver the
* guard above can reason about; a bare `run()` has none, so that guard
* cannot see this class at all. Python-only today because the extraction
+3 -1
View File
@@ -3401,7 +3401,9 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
lang == CBM_LANG_ARKTS ||
/* embedded-script hosts — see pass_calls.c */
lang == CBM_LANG_HTML || lang == CBM_LANG_VUE ||
lang == CBM_LANG_SVELTE || lang == CBM_LANG_ASTRO;
lang == CBM_LANG_SVELTE || lang == CBM_LANG_ASTRO ||
/* Swift initializer calls only — see pass_calls.c */
lang == CBM_LANG_SWIFT;
/* Bare-call local-binding suppression — see the note in pass_calls.c.
* This gate MUST stay identical to the one there. */
bool suppress_weak_local_binding = lang == CBM_LANG_PYTHON;
+125 -7
View File
@@ -79,8 +79,6 @@ static int has_def_qn(CBMFileResult *r, const char *qn) {
return 0;
}
/* Return the definition with the given qualified name, or NULL. Distinct from
* find_def_by_name for the same reason has_def_qn is distinct from has_def. */
/* Find a definition by its qualified name relative to the file's module QN
* (e.g. "Box.init" resolves to "<module_qn>.Box.init"). */
static const CBMDefinition *find_def_in_module(CBMFileResult *r, const char *rel_qn) {
@@ -2599,9 +2597,11 @@ TEST(swift_init_convenience_delegates_issue2379) {
const CBMDefinition *init_def = find_def_in_module(r, "Holder.init");
ASSERT_NOT_NULL(init_def);
/* self.init(...) is a plain member call today, same as any other
* `receiver.member(...)` invocation -- no dedicated delegation handling. */
ASSERT(has_call(r, "self.init"));
/* self.init(...) delegates to another overload of Holder.init, which
* shares this node (#2061): it forms no call, because the only edge it
* could produce is a self-loop. Its argument call stays. */
ASSERT_FALSE(has_call(r, "self.init"));
ASSERT_FALSE(has_call(r, "Holder.init"));
const CBMCall *call = find_call_by_callee(r, "other");
ASSERT_NOT_NULL(call);
@@ -2619,7 +2619,7 @@ TEST(swift_init_convenience_delegates_issue2379) {
* Swift methods onto one node (DeusData/codebase-memory-mcp#2061), and an
* init is no exception; distinguishing overloads by signature is that
* issue's concern, not this one's. */
TEST(swift_init_multiple_not_collapsed_issue2379) {
TEST(swift_init_overloads_share_one_node_issue2379) {
CBMFileResult *r = extract("func shared() -> Int { 3 }\n"
"final class Holder {\n"
" let value: Int\n"
@@ -2761,6 +2761,120 @@ TEST(swift_deinit_issue2379) {
PASS();
}
/* #2379: `super.init(...)` names no type. Left as written, the registry binds it
* by short name to whichever project `init` is nearest (on Alamofire: a nested
* `Options.init`, a sibling decoder's). It is emitted as the superclass's
* initializer instead -- the first inheritance entry, generic arguments
* dropped -- and stays attributed to the calling initializer. */
TEST(swift_super_init_names_superclass_issue2379) {
CBMFileResult *r = extract("final class Sub: Base<Int>, Sendable {\n"
" override init() {\n"
" super.init()\n"
" }\n"
"}\n",
CBM_LANG_SWIFT, "t", "Sub.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
const CBMDefinition *init_def = find_def_in_module(r, "Sub.init");
ASSERT_NOT_NULL(init_def);
ASSERT_FALSE(has_call(r, "super.init"));
const CBMCall *call = find_call_by_callee(r, "Base.init");
ASSERT_NOT_NULL(call);
ASSERT_NOT_NULL(call->enclosing_func_qn);
ASSERT_STR_EQ(call->enclosing_func_qn, init_def->qualified_name);
cbm_free_result(r);
PASS();
}
/* #2379: without a superclass to name -- no inheritance clause, or a first entry
* that does not read as a type -- `super.init` forms no call at all. A
* lower-case root would pass the registry's receiver-chain check and bind by
* short name again. */
TEST(swift_super_init_without_superclass_no_call_issue2379) {
CBMFileResult *r = extract("class Root {\n"
" init() {\n"
" super.init()\n"
" }\n"
"}\n"
"class Odd: base {\n"
" init() {\n"
" super.init()\n"
" }\n"
"}\n",
CBM_LANG_SWIFT, "t", "Root.swift");
ASSERT_NOT_NULL(r);
ASSERT_NOT_NULL(find_def_in_module(r, "Root.init"));
ASSERT_NOT_NULL(find_def_in_module(r, "Odd.init"));
for (int i = 0; i < r->calls.count; i++) {
const char *callee = r->calls.items[i].callee_name;
ASSERT(!callee || !strstr(callee, "init"));
}
cbm_free_result(r);
PASS();
}
/* #2379: an initializer call `T.init(...)` is flagged so the resolvers'
* weak-member guard keeps a short-name guess from placing it (nearly every
* type has an `init`); every other Swift call stays unflagged and resolves
* exactly as before. */
TEST(swift_initializer_calls_flagged_issue2379) {
CBMFileResult *r = extract("func run(obj: Worker) {\n"
" _ = UIAlertAction.init(title: nil)\n"
" helper()\n"
" obj.start()\n"
"}\n",
CBM_LANG_SWIFT, "t", "Run.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
const CBMCall *init_call = find_call_by_callee(r, "UIAlertAction.init");
const CBMCall *bare = find_call_by_callee(r, "helper");
const CBMCall *member = find_call_by_callee(r, "obj.start");
ASSERT_NOT_NULL(init_call);
ASSERT_NOT_NULL(bare);
ASSERT_NOT_NULL(member);
ASSERT_TRUE(init_call->is_method);
ASSERT_FALSE(bare->is_method);
ASSERT_FALSE(member->is_method);
cbm_free_result(r);
PASS();
}
/* #2379: `init` is a Swift keyword. A reference to one (`.init(...)`,
* `Box.init`) names the initializer of a type the bare name does not carry,
* so it is not recorded as a usage -- by name it could only be guessed among
* every type's initializer (on Alamofire, 26 references landed on one nested
* `Output.init`). The type in `Box.init` is still a usage. */
TEST(swift_init_reference_is_not_a_usage_issue2379) {
CBMFileResult *r = extract("struct Box {\n"
" init(raw: Int) {}\n"
"}\n"
"func make() -> Box {\n"
" .init(raw: 1)\n"
"}\n"
"func maker() -> (Int) -> Box {\n"
" Box.init\n"
"}\n",
CBM_LANG_SWIFT, "t", "Make.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
int init_usages = 0;
int box_usages = 0;
for (int i = 0; i < r->usages.count; i++) {
const char *ref = r->usages.items[i].ref_name;
init_usages += ref && strcmp(ref, "init") == 0;
box_usages += ref && strcmp(ref, "Box") == 0;
}
ASSERT_EQ(init_usages, 0);
ASSERT_GT(box_usages, 0);
cbm_free_result(r);
PASS();
}
/* --- Objective-C --- */
TEST(objc_interface) {
CBMFileResult *r =
@@ -13488,11 +13602,15 @@ SUITE(extraction) {
RUN_TEST(swift_init_struct_issue2379);
RUN_TEST(swift_init_class_designated_issue2379);
RUN_TEST(swift_init_convenience_delegates_issue2379);
RUN_TEST(swift_init_multiple_not_collapsed_issue2379);
RUN_TEST(swift_init_overloads_share_one_node_issue2379);
RUN_TEST(swift_init_failable_enum_issue2379);
RUN_TEST(swift_init_extension_issue2379);
RUN_TEST(swift_method_still_attributed_issue2379);
RUN_TEST(swift_deinit_issue2379);
RUN_TEST(swift_super_init_names_superclass_issue2379);
RUN_TEST(swift_super_init_without_superclass_no_call_issue2379);
RUN_TEST(swift_initializer_calls_flagged_issue2379);
RUN_TEST(swift_init_reference_is_not_a_usage_issue2379);
RUN_TEST(swift_call_string_arg_issue1892);
RUN_TEST(swift_nested_url_constructor_issue1892);
RUN_TEST(swift_nested_url_no_bang_issue1892);
+249
View File
@@ -18979,6 +18979,253 @@ TEST(pipeline_swift_cross_package_import) {
PASS();
}
/* #2379 at graph level, as a SwiftPM package. Sources/Kit/Inits.swift is the
* issue's reproduction. Sources/App/Sub.swift imports the Kit target -- which
* sends name resolution down the import-aware path -- and calls initializers
* the way real code does: `super.init` to a project superclass in another
* target, `super.init` to a superclass outside the project, an explicit
* `T.init(...)` on a project type and on a type outside the project, `self.init`
* from an extension of a type outside the project, and an implicit `.init(...)`.
* Each of those can attract a false CALLS edge now that initializers are nodes. */
static const char *const SWIFT_INIT_PACKAGE_SRC =
"let package = Package(\n"
" name: \"Kit\",\n"
" products: [.library(name: \"Kit\", targets: [\"Kit\"])],\n"
" targets: [\n"
" .target(name: \"Kit\", dependencies: []),\n"
" .target(name: \"App\", dependencies: [\"Kit\"])\n"
" ]\n"
")\n";
static const char *const SWIFT_INIT_INITS_SRC = "func helper() -> Int { 1 }\n"
"func other() -> Int { 2 }\n"
"\n"
"struct Box {\n"
" let value: Int\n"
" init() {\n"
" value = helper()\n"
" }\n"
" func read() -> Int {\n"
" other()\n"
" }\n"
"}\n"
"\n"
"final class Holder {\n"
" let value: Int\n"
" init(seed: Int) {\n"
" value = helper() + seed\n"
" }\n"
" convenience init() {\n"
" self.init(seed: other())\n"
" }\n"
"}\n"
"\n"
"enum Mode {\n"
" case on\n"
" init?(flag: Bool) {\n"
" if helper() > 0 { self = .on } else { "
"return nil }\n"
" }\n"
"}\n"
"\n"
"extension Box {\n"
" init(raw: Int) {\n"
" value = other()\n"
" }\n"
"}\n";
static const char *const SWIFT_INIT_BASE_SRC = "open class Base {\n"
" public init() {}\n"
"}\n";
static const char *const SWIFT_INIT_SUB_SRC = "import Kit\n"
"import UIKit\n"
"\n"
"class Sub: Base {\n"
" override init() {\n"
" super.init()\n"
" }\n"
"}\n"
"class Screen: UIViewController {\n"
" init() {\n"
" super.init(nibName: nil, bundle: nil)\n"
" }\n"
" func alert() -> UIAlertAction {\n"
" UIAlertAction.init(title: nil, style: "
".default)\n"
" }\n"
"}\n"
"extension Notification {\n"
" init(tag: String) {\n"
" self.init(name: Notification.Name(tag))\n"
" }\n"
"}\n"
"func make() -> Box {\n"
" .init(raw: 1)\n"
"}\n"
"func explicit() -> Box {\n"
" Box.init(raw: 2)\n"
"}\n";
enum { SWIFT_INIT_EDGE_CAP = 32, SWIFT_INIT_EDGE_LEN = 192 };
typedef struct {
int run_rc;
int calls; /* CALLS edges seen; -1 if the store was not read */
int init_usages; /* USAGE edges into a node named init/deinit */
char edges[SWIFT_INIT_EDGE_CAP][SWIFT_INIT_EDGE_LEN]; /* "src -> tgt", sorted */
} SwiftInitObservation;
static int swift_init_edge_cmp(const void *a, const void *b) {
return strcmp((const char *)a, (const char *)b);
}
/* Qualified name relative to the project ("Inits.Box.init"). */
static const char *swift_init_rel_qn(const char *qn, const char *project) {
size_t plen = strlen(project);
return strncmp(qn, project, plen) == 0 && qn[plen] == '.' ? qn + plen + 1 : qn;
}
static void swift_init_observe(cbm_store_t *s, const char *project, SwiftInitObservation *o) {
cbm_edge_t *edges = NULL;
int count = 0;
o->calls = 0;
if (cbm_store_find_edges_by_type(s, project, "CALLS", &edges, &count) == CBM_STORE_OK) {
for (int i = 0; i < count; i++) {
cbm_node_t src = {0};
cbm_node_t tgt = {0};
if (cbm_store_find_node_by_id(s, edges[i].source_id, &src) == CBM_STORE_OK &&
cbm_store_find_node_by_id(s, edges[i].target_id, &tgt) == CBM_STORE_OK &&
o->calls < SWIFT_INIT_EDGE_CAP) {
snprintf(o->edges[o->calls], SWIFT_INIT_EDGE_LEN, "%s -> %s",
swift_init_rel_qn(src.qualified_name, project),
swift_init_rel_qn(tgt.qualified_name, project));
}
o->calls++;
cbm_node_free_fields(&src);
cbm_node_free_fields(&tgt);
}
if (edges) {
cbm_store_free_edges(edges, count);
}
}
int kept = o->calls < SWIFT_INIT_EDGE_CAP ? o->calls : SWIFT_INIT_EDGE_CAP;
qsort(o->edges, (size_t)kept, sizeof(o->edges[0]), swift_init_edge_cmp);
edges = NULL;
count = 0;
o->init_usages = 0;
if (cbm_store_find_edges_by_type(s, project, "USAGE", &edges, &count) == CBM_STORE_OK) {
for (int i = 0; i < count; i++) {
cbm_node_t tgt = {0};
if (cbm_store_find_node_by_id(s, edges[i].target_id, &tgt) == CBM_STORE_OK &&
tgt.name && (strcmp(tgt.name, "init") == 0 || strcmp(tgt.name, "deinit") == 0)) {
o->init_usages++;
}
cbm_node_free_fields(&tgt);
}
if (edges) {
cbm_store_free_edges(edges, count);
}
}
}
/* `pad` filler files push the run past MIN_FILES_FOR_PARALLEL so that, with
* `parallel`, it goes through pass_parallel.c rather than pass_calls.c. */
static void swift_init_case(bool parallel, int pad, SwiftInitObservation *o) {
memset(o, 0, sizeof(*o));
o->run_rc = -1;
o->calls = -1;
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_swift_init_XXXXXX");
if (!cbm_mkdtemp(tmp)) {
return;
}
char path[512];
bool written = true;
const char *names[] = {"Package.swift", "Sources/Kit/Inits.swift", "Sources/Kit/Base.swift",
"Sources/App/Sub.swift"};
const char *sources[] = {SWIFT_INIT_PACKAGE_SRC, SWIFT_INIT_INITS_SRC, SWIFT_INIT_BASE_SRC,
SWIFT_INIT_SUB_SRC};
for (int i = 0; i < 4 && written; i++) {
snprintf(path, sizeof(path), "%s/%s", tmp, names[i]);
written = th_write_file(path, sources[i]) == 0;
}
for (int i = 0; i < pad && written; i++) {
char source[96];
snprintf(path, sizeof(path), "%s/Pad%02d.swift", tmp, i);
snprintf(source, sizeof(source), "func pad%02d() -> Int { %d }\n", i, i);
written = th_write_file(path, source) == 0;
}
if (written) {
XlangEnvSave save = xlang_env_select(parallel);
char db_path[512];
snprintf(db_path, sizeof(db_path), "%s/swift_init.db", tmp);
cbm_pipeline_t *pipeline = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
if (pipeline) {
o->run_rc = cbm_pipeline_run(pipeline);
const char *project = cbm_pipeline_project_name(pipeline);
cbm_store_t *store = cbm_store_open_path(db_path);
if (store && project) {
swift_init_observe(store, project, o);
}
if (store) {
cbm_store_close(store);
}
cbm_pipeline_free(pipeline);
}
xlang_env_restore(&save);
}
th_rmtree(tmp);
}
/* The exact CALLS set. Every initializer's own calls come from that
* initializer (the repro's file-level collapse is gone); Sub.init reaches
* Base.init in the other target and `Box.init(raw:)` reaches Box.init.
* UIViewController's and UIAlertAction's initializers, Notification's own and
* the implicit `.init(...)` are outside the project and form no edge -- in
* particular none to a project `init` that merely shares the name. Holder's
* convenience `self.init` is not a self-loop. */
static int swift_init_assert_exact(const SwiftInitObservation *o) {
static const char *const expected[] = {
"Sources.App.Sub.Sub.init -> Sources.Kit.Base.Base.init",
"Sources.App.Sub.explicit -> Sources.Kit.Inits.Box.init",
"Sources.Kit.Inits.Box.init -> Sources.Kit.Inits.helper",
"Sources.Kit.Inits.Box.init -> Sources.Kit.Inits.other",
"Sources.Kit.Inits.Box.read -> Sources.Kit.Inits.other",
"Sources.Kit.Inits.Holder.init -> Sources.Kit.Inits.helper",
"Sources.Kit.Inits.Holder.init -> Sources.Kit.Inits.other",
"Sources.Kit.Inits.Mode.init -> Sources.Kit.Inits.helper",
};
enum { EXPECTED_CALLS = sizeof(expected) / sizeof(expected[0]) };
ASSERT_EQ(o->run_rc, 0);
ASSERT_EQ(o->init_usages, 0);
ASSERT_EQ(o->calls, (int)EXPECTED_CALLS);
for (int i = 0; i < (int)EXPECTED_CALLS; i++) {
ASSERT_STR_EQ(o->edges[i], expected[i]);
}
return 0;
}
TEST(pipeline_swift_init_exact_calls_sequential_issue2379) {
SwiftInitObservation o;
swift_init_case(false, 0, &o);
if (swift_init_assert_exact(&o) != 0) {
return 1;
}
PASS();
}
/* Same input through pass_parallel.c: 55 fillers exceed MIN_FILES_FOR_PARALLEL. */
TEST(pipeline_swift_init_exact_calls_parallel_issue2379) {
SwiftInitObservation o;
swift_init_case(true, 55, &o);
if (swift_init_assert_exact(&o) != 0) {
return 1;
}
PASS();
}
TEST(pipeline_python_cross_module_call) {
/* Port of TestPythonCrossModuleCallViaImport */
const char *files[] = {"utils.py", "main.py"};
@@ -27638,6 +27885,8 @@ SUITE(pipeline) {
RUN_TEST(pipeline_imports_multi_symbol_edges);
RUN_TEST(pipeline_go_cross_package_call);
RUN_TEST(pipeline_swift_cross_package_import);
RUN_TEST(pipeline_swift_init_exact_calls_sequential_issue2379);
RUN_TEST(pipeline_swift_init_exact_calls_parallel_issue2379);
RUN_TEST(pipeline_python_cross_module_call);
RUN_TEST(pipeline_cross_language_same_name_does_not_share_calls_issue725);
RUN_TEST(pipeline_go_type_classification);