gitnexus 1.6.5-rc.42 → 1.6.5-rc.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -21,9 +21,25 @@
|
|
|
21
21
|
* - `audit::Event& r`, `audit::Event&& rr`
|
|
22
22
|
* - `std::vector<audit::Event>` (template namespace + template-arg namespaces)
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* V2 additionally walks class ancestors (via MRO), so base-class enclosing
|
|
25
|
+
* namespaces also contribute associated namespaces.
|
|
26
|
+
*
|
|
27
|
+
* **GitNexus approximation (not strict ISO C++ ADL):** passing a qualified
|
|
28
|
+
* function reference like `utils::worker` contributes `utils` to the associated
|
|
29
|
+
* set, enabling resolution of unqualified calls like `with_callback(utils::worker)`
|
|
30
|
+
* to `utils::with_callback`. Under ISO C++ `[basic.lookup.argdep]`, associated
|
|
31
|
+
* entities for function-type arguments come from the **parameter types and return
|
|
32
|
+
* type** of each function in the overload set — NOT the function's enclosing
|
|
33
|
+
* namespace. For `void worker()`, the standard-compliant associated set is empty.
|
|
34
|
+
* GitNexus instead contributes the enclosing namespace of any Function/Method
|
|
35
|
+
* def whose simple name matches, because it enables the dominant real-world ADL
|
|
36
|
+
* pattern at reasonable precision cost.
|
|
37
|
+
*
|
|
38
|
+
* For qualified refs (e.g. `utils::worker`) the namespace is confirmed via a
|
|
39
|
+
* workspace lookup (only contributed when a Function/Method named `worker` exists
|
|
40
|
+
* in `utils`). For unqualified refs the workspace is searched for any Function
|
|
41
|
+
* def with that simple name. Locally-declared function-pointer variables
|
|
42
|
+
* (e.g. `void (*g)()`) and function parameters are excluded from this path.
|
|
27
43
|
*
|
|
28
44
|
* The current implementation also short-circuits to ADL only when ordinary lookup is empty
|
|
29
45
|
* (`findCallableBindingInScope` returned undefined). ISO C++ would
|
|
@@ -59,6 +75,7 @@ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexe
|
|
|
59
75
|
* Per-argument shape information collected at capture time. ADL fires for
|
|
60
76
|
* arguments where `simpleClassName !== ''`, including class pointers and
|
|
61
77
|
* references whose declarator chain resolves to a named class type.
|
|
78
|
+
* Free-function reference arguments use `functionRefText`.
|
|
62
79
|
*/
|
|
63
80
|
export interface CppAdlArgInfo {
|
|
64
81
|
/** Simple class-like type name (last segment of qualified name); empty
|
|
@@ -76,6 +93,15 @@ export interface CppAdlArgInfo {
|
|
|
76
93
|
/** Enclosing namespaces extracted from explicit type template arguments,
|
|
77
94
|
* recursively bounded. */
|
|
78
95
|
readonly templateArgNamespaces: readonly string[];
|
|
96
|
+
/** When set, the arg is a potential free-function reference (not a locally-
|
|
97
|
+
* declared function-pointer variable or function parameter). Contains the
|
|
98
|
+
* identifier text as written in source (e.g. `"utils::worker"` or
|
|
99
|
+
* `"worker"`). GitNexus approximation: the function's enclosing namespace
|
|
100
|
+
* is contributed to the ADL associated set. For qualified refs a workspace
|
|
101
|
+
* lookup confirms a Function/Method with that simple name exists in the
|
|
102
|
+
* namespace before contributing; for unqualified refs every namespace
|
|
103
|
+
* containing a matching Function/Method def is contributed. */
|
|
104
|
+
readonly functionRefText?: string;
|
|
79
105
|
}
|
|
80
106
|
/** Sentinel returned by `pickCppAdlCandidates` when ADL surfaces multiple
|
|
81
107
|
* candidates that share normalized parameter types — the caller MUST
|
|
@@ -21,9 +21,25 @@
|
|
|
21
21
|
* - `audit::Event& r`, `audit::Event&& rr`
|
|
22
22
|
* - `std::vector<audit::Event>` (template namespace + template-arg namespaces)
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* V2 additionally walks class ancestors (via MRO), so base-class enclosing
|
|
25
|
+
* namespaces also contribute associated namespaces.
|
|
26
|
+
*
|
|
27
|
+
* **GitNexus approximation (not strict ISO C++ ADL):** passing a qualified
|
|
28
|
+
* function reference like `utils::worker` contributes `utils` to the associated
|
|
29
|
+
* set, enabling resolution of unqualified calls like `with_callback(utils::worker)`
|
|
30
|
+
* to `utils::with_callback`. Under ISO C++ `[basic.lookup.argdep]`, associated
|
|
31
|
+
* entities for function-type arguments come from the **parameter types and return
|
|
32
|
+
* type** of each function in the overload set — NOT the function's enclosing
|
|
33
|
+
* namespace. For `void worker()`, the standard-compliant associated set is empty.
|
|
34
|
+
* GitNexus instead contributes the enclosing namespace of any Function/Method
|
|
35
|
+
* def whose simple name matches, because it enables the dominant real-world ADL
|
|
36
|
+
* pattern at reasonable precision cost.
|
|
37
|
+
*
|
|
38
|
+
* For qualified refs (e.g. `utils::worker`) the namespace is confirmed via a
|
|
39
|
+
* workspace lookup (only contributed when a Function/Method named `worker` exists
|
|
40
|
+
* in `utils`). For unqualified refs the workspace is searched for any Function
|
|
41
|
+
* def with that simple name. Locally-declared function-pointer variables
|
|
42
|
+
* (e.g. `void (*g)()`) and function parameters are excluded from this path.
|
|
27
43
|
*
|
|
28
44
|
* The current implementation also short-circuits to ADL only when ordinary lookup is empty
|
|
29
45
|
* (`findCallableBindingInScope` returned undefined). ISO C++ would
|
|
@@ -130,10 +146,14 @@ export function pickCppAdlCandidates(site, callerParsed, scopes, parsedFiles) {
|
|
|
130
146
|
const args = argInfoBySite.get(key);
|
|
131
147
|
if (args === undefined || args.length === 0)
|
|
132
148
|
return undefined;
|
|
133
|
-
// Collect associated namespace QNames from every participating class-typed arg
|
|
149
|
+
// Collect associated namespace QNames from every participating class-typed arg
|
|
150
|
+
// and from function-reference args.
|
|
134
151
|
const associatedNamespaces = new Set();
|
|
135
152
|
for (const arg of args) {
|
|
136
153
|
collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces);
|
|
154
|
+
if (arg.functionRefText !== undefined) {
|
|
155
|
+
collectFunctionRefNamespaces(arg.functionRefText, parsedFiles, associatedNamespaces);
|
|
156
|
+
}
|
|
137
157
|
}
|
|
138
158
|
if (associatedNamespaces.size === 0)
|
|
139
159
|
return undefined;
|
|
@@ -319,3 +339,75 @@ function findCppClassDefBySimpleName(simpleName, scopes) {
|
|
|
319
339
|
return undefined;
|
|
320
340
|
return { classDef: firstMatch, ambiguous: false };
|
|
321
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Contribute associated namespaces for a function-reference argument.
|
|
344
|
+
*
|
|
345
|
+
* - **Qualified refs** (`utils::worker`, `outer::inner::fn`): the namespace
|
|
346
|
+
* is extracted from the qualifier text (converting `::` to `.` for dot-joined
|
|
347
|
+
* QName matching). A workspace lookup then **verifies** that a Function or
|
|
348
|
+
* Method def named `worker` (the simple name after the last `::`) actually
|
|
349
|
+
* exists in the extracted namespace. This prevents false positives from
|
|
350
|
+
* namespace-qualified variables, enum values, and static data members, which
|
|
351
|
+
* also produce `qualified_identifier` AST nodes in tree-sitter-cpp (the
|
|
352
|
+
* AST node type alone does not distinguish functions from non-function names).
|
|
353
|
+
* - **Unqualified refs** (`worker`): the workspace is searched for any
|
|
354
|
+
* Function/Method def whose simple name matches. Every distinct enclosing
|
|
355
|
+
* namespace found is added — overloads across the same namespace produce
|
|
356
|
+
* a single entry; GitNexus does not select a specific overload at this stage.
|
|
357
|
+
*/
|
|
358
|
+
function collectFunctionRefNamespaces(refText, parsedFiles, out) {
|
|
359
|
+
const colonIdx = refText.lastIndexOf('::');
|
|
360
|
+
if (colonIdx !== -1) {
|
|
361
|
+
// Qualified ref: extract namespace prefix and normalise :: → dot notation.
|
|
362
|
+
const nsText = refText.slice(0, colonIdx).replace(/::/g, '.');
|
|
363
|
+
if (nsText === '')
|
|
364
|
+
return;
|
|
365
|
+
const simpleName = refText.slice(colonIdx + 2);
|
|
366
|
+
// Verify that a Function/Method named `simpleName` exists in `nsText`.
|
|
367
|
+
// Without this guard every `a::b` qualified_identifier arg (variable,
|
|
368
|
+
// enum value, static member, type alias) would blindly contribute `a`
|
|
369
|
+
// to the associated set and risk a false-positive CALLS edge.
|
|
370
|
+
for (const parsed of parsedFiles) {
|
|
371
|
+
const scopesById = new Map();
|
|
372
|
+
for (const sc of parsed.scopes)
|
|
373
|
+
scopesById.set(sc.id, sc);
|
|
374
|
+
for (const scope of parsed.scopes) {
|
|
375
|
+
if (scope.kind !== 'Namespace')
|
|
376
|
+
continue;
|
|
377
|
+
if (computeNamespaceQName(scope, scopesById) !== nsText)
|
|
378
|
+
continue;
|
|
379
|
+
for (const def of scope.ownedDefs) {
|
|
380
|
+
if (def.type !== 'Function' && def.type !== 'Method')
|
|
381
|
+
continue;
|
|
382
|
+
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
383
|
+
if (simple === simpleName) {
|
|
384
|
+
out.add(nsText);
|
|
385
|
+
return; // Namespace confirmed; no need to scan further files.
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
// Unqualified: search all namespace scopes for a Function def with this
|
|
393
|
+
// simple name and contribute its enclosing namespace.
|
|
394
|
+
for (const parsed of parsedFiles) {
|
|
395
|
+
const scopesById = new Map();
|
|
396
|
+
for (const sc of parsed.scopes)
|
|
397
|
+
scopesById.set(sc.id, sc);
|
|
398
|
+
for (const scope of parsed.scopes) {
|
|
399
|
+
if (scope.kind !== 'Namespace')
|
|
400
|
+
continue;
|
|
401
|
+
for (const def of scope.ownedDefs) {
|
|
402
|
+
if (def.type !== 'Function' && def.type !== 'Method')
|
|
403
|
+
continue;
|
|
404
|
+
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
405
|
+
if (simple !== refText)
|
|
406
|
+
continue;
|
|
407
|
+
const nsQName = computeNamespaceQName(scope, scopesById);
|
|
408
|
+
if (nsQName !== '')
|
|
409
|
+
out.add(nsQName);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
@@ -717,15 +717,93 @@ function classifyAdlArg(argNode) {
|
|
|
717
717
|
argNode.type === 'nullptr') {
|
|
718
718
|
return EMPTY_ADL_ARG;
|
|
719
719
|
}
|
|
720
|
+
// Qualified expression (a::b) — may be a function, variable, enum value,
|
|
721
|
+
// or static member. Record as a potential function reference; resolution
|
|
722
|
+
// time verifies via workspace lookup that a Function/Method with this simple
|
|
723
|
+
// name exists in the extracted namespace before contributing to the set.
|
|
724
|
+
if (argNode.type === 'qualified_identifier') {
|
|
725
|
+
return {
|
|
726
|
+
simpleClassName: '',
|
|
727
|
+
templateSimpleClassName: '',
|
|
728
|
+
templateNamespace: '',
|
|
729
|
+
templateArgClassNames: [],
|
|
730
|
+
templateArgNamespaces: [],
|
|
731
|
+
functionRefText: argNode.text,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
720
734
|
// Variable reference — look up its declared type (preserving pointer /
|
|
721
735
|
// reference / qualified-name shape; the existing arity-narrowing helper
|
|
722
736
|
// strips this info).
|
|
723
737
|
if (argNode.type === 'identifier') {
|
|
724
|
-
|
|
738
|
+
const result = lookupAdlIdentifierType(argNode);
|
|
739
|
+
if (result === null) {
|
|
740
|
+
// Not found in the local compound_statement scope — could be a
|
|
741
|
+
// free-function reference (unqualified name, namespace scope).
|
|
742
|
+
return {
|
|
743
|
+
simpleClassName: '',
|
|
744
|
+
templateSimpleClassName: '',
|
|
745
|
+
templateNamespace: '',
|
|
746
|
+
templateArgClassNames: [],
|
|
747
|
+
templateArgNamespaces: [],
|
|
748
|
+
functionRefText: argNode.text,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
return result;
|
|
725
752
|
}
|
|
726
753
|
// Other shapes (calls, member access, operators) — V1 unsupported.
|
|
727
754
|
return EMPTY_ADL_ARG;
|
|
728
755
|
}
|
|
756
|
+
/**
|
|
757
|
+
* Returns `true` when `varName` appears as a parameter name in the nearest
|
|
758
|
+
* enclosing `function_definition` or `function_declarator` that contains
|
|
759
|
+
* `identNode`. Parameters live in `parameter_list` (a sibling of the
|
|
760
|
+
* `compound_statement`), so the `compound_statement`-local declaration scan
|
|
761
|
+
* in `lookupAdlIdentifierType` would not find them — causing them to be
|
|
762
|
+
* mistakenly classified as potential free-function references.
|
|
763
|
+
*
|
|
764
|
+
* In tree-sitter-cpp a `function_definition` does NOT expose `parameters`
|
|
765
|
+
* as a direct named field; parameters live inside the nested
|
|
766
|
+
* `function_declarator`. For `function_declarator` nodes the `parameters`
|
|
767
|
+
* field IS direct. Both cases are handled below.
|
|
768
|
+
*/
|
|
769
|
+
function isIdentifierAFunctionParameter(identNode, varName) {
|
|
770
|
+
let node = identNode.parent;
|
|
771
|
+
let safety = 64;
|
|
772
|
+
while (node !== null && safety-- > 0) {
|
|
773
|
+
let params = null;
|
|
774
|
+
if (node.type === 'function_declarator') {
|
|
775
|
+
// parameters is a direct field on function_declarator.
|
|
776
|
+
params = node.childForFieldName('parameters');
|
|
777
|
+
}
|
|
778
|
+
else if (node.type === 'function_definition') {
|
|
779
|
+
// function_definition carries parameters inside its `declarator` field
|
|
780
|
+
// (which is a function_declarator). Walk through it.
|
|
781
|
+
const decl = node.childForFieldName('declarator');
|
|
782
|
+
if (decl !== null && decl.type === 'function_declarator') {
|
|
783
|
+
params = decl.childForFieldName('parameters');
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (params !== null) {
|
|
787
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
788
|
+
const param = params.namedChild(i);
|
|
789
|
+
if (param === null)
|
|
790
|
+
continue;
|
|
791
|
+
const declNode = param.childForFieldName('declarator');
|
|
792
|
+
if (declNode === null)
|
|
793
|
+
continue;
|
|
794
|
+
const leafName = extractDeclaratorLeafName(declNode);
|
|
795
|
+
if (leafName === varName)
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
// Only check the immediately enclosing function — do not climb further.
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
if (node.type === 'translation_unit')
|
|
802
|
+
break;
|
|
803
|
+
node = node.parent;
|
|
804
|
+
}
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
729
807
|
function lookupAdlIdentifierType(identNode) {
|
|
730
808
|
const varName = identNode.text;
|
|
731
809
|
let scope = identNode.parent;
|
|
@@ -735,7 +813,15 @@ function lookupAdlIdentifierType(identNode) {
|
|
|
735
813
|
scope = scope.parent;
|
|
736
814
|
}
|
|
737
815
|
if (scope === null)
|
|
816
|
+
return null;
|
|
817
|
+
// Function parameters live in the enclosing function's `parameter_list`,
|
|
818
|
+
// NOT inside the `compound_statement`, so the declaration scan below would
|
|
819
|
+
// never find them and would return `null` — incorrectly triggering the
|
|
820
|
+
// free-function-reference path. Check the parameter_list first.
|
|
821
|
+
if (isIdentifierAFunctionParameter(identNode, varName)) {
|
|
738
822
|
return EMPTY_ADL_ARG;
|
|
823
|
+
}
|
|
824
|
+
let foundAsLocalFunctionPointer = false;
|
|
739
825
|
for (let i = 0; i < scope.childCount; i++) {
|
|
740
826
|
const stmt = scope.child(i);
|
|
741
827
|
if (stmt === null || stmt.type !== 'declaration')
|
|
@@ -764,6 +850,9 @@ function lookupAdlIdentifierType(identNode) {
|
|
|
764
850
|
if (inner.type === 'pointer_declarator') {
|
|
765
851
|
if (findFirstDescendantOfType(inner, 'function_declarator') !== null) {
|
|
766
852
|
isFunctionPointer = true;
|
|
853
|
+
// Extract the name from within the function-pointer declarator chain
|
|
854
|
+
// so `foundAsLocalFunctionPointer` can detect a matching declaration.
|
|
855
|
+
nameText = extractDeclaratorLeafName(inner);
|
|
767
856
|
break;
|
|
768
857
|
}
|
|
769
858
|
const next = inner.childForFieldName('declarator');
|
|
@@ -796,12 +885,22 @@ function lookupAdlIdentifierType(identNode) {
|
|
|
796
885
|
}
|
|
797
886
|
if (inner.type === 'function_declarator') {
|
|
798
887
|
isFunctionPointer = true;
|
|
888
|
+
// Extract the name from the inner declarator (e.g. `(*g)` in `void (*g)()`).
|
|
889
|
+
const innerDecl = inner.childForFieldName('declarator');
|
|
890
|
+
if (innerDecl !== null)
|
|
891
|
+
nameText = extractDeclaratorLeafName(innerDecl);
|
|
799
892
|
break;
|
|
800
893
|
}
|
|
801
894
|
// Reached the leaf — usually `identifier`. Take its text.
|
|
802
895
|
nameText = inner.text;
|
|
803
896
|
break;
|
|
804
897
|
}
|
|
898
|
+
if (nameText === varName && isFunctionPointer) {
|
|
899
|
+
// Explicitly declared as a function-pointer variable — must not be
|
|
900
|
+
// treated as a free-function reference by the caller.
|
|
901
|
+
foundAsLocalFunctionPointer = true;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
805
904
|
if (isFunctionPointer || nameText !== varName)
|
|
806
905
|
continue;
|
|
807
906
|
const simpleClassName = extractAdlSimpleTypeName(typeNode);
|
|
@@ -814,7 +913,22 @@ function lookupAdlIdentifierType(identNode) {
|
|
|
814
913
|
templateArgNamespaces,
|
|
815
914
|
};
|
|
816
915
|
}
|
|
817
|
-
|
|
916
|
+
// If the identifier was found in local scope as a function-pointer variable,
|
|
917
|
+
// return EMPTY_ADL_ARG so the caller does NOT treat it as a free-function
|
|
918
|
+
// reference. Otherwise return null to indicate "not in local scope".
|
|
919
|
+
//
|
|
920
|
+
// Known limitation (Finding 4): variables whose type is a typedef/using alias
|
|
921
|
+
// for a function-pointer type are NOT detected here. For example:
|
|
922
|
+
// using Callback = void (*)();
|
|
923
|
+
// Callback g;
|
|
924
|
+
// foo(g); // `g`'s declarator is `identifier` with type `Callback`
|
|
925
|
+
// The declarator has no `pointer_declarator` wrapper, so `isFunctionPointer`
|
|
926
|
+
// stays false and `extractAdlSimpleTypeName` returns `"Callback"`. ADL then
|
|
927
|
+
// looks for a class named `Callback`; if none exists, this degrades to
|
|
928
|
+
// EMPTY_ADL_ARG (class not found → no namespace contributed). If a class
|
|
929
|
+
// named `Callback` does exist, a spurious namespace contribution could occur.
|
|
930
|
+
// Risk is low in practice; a future fix should resolve the typedef/alias chain.
|
|
931
|
+
return foundAsLocalFunctionPointer ? EMPTY_ADL_ARG : null;
|
|
818
932
|
}
|
|
819
933
|
/** Extract the simple class-like type name from a `type:` field node.
|
|
820
934
|
* Returns '' for primitives and any other
|
|
@@ -963,6 +1077,29 @@ function extractNamespaceFromQualifiedText(text) {
|
|
|
963
1077
|
return '';
|
|
964
1078
|
return normalizeCppNamespaceQName(cleaned.slice(0, idx));
|
|
965
1079
|
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Walk a declarator node chain, unwrapping pointer/reference/function/
|
|
1082
|
+
* parenthesized wrappers, and return the text of the innermost identifier.
|
|
1083
|
+
* Returns `null` when no identifier is found within `safety` steps.
|
|
1084
|
+
* Used by `lookupAdlIdentifierType` to extract the variable name from
|
|
1085
|
+
* function-pointer declarator trees such as `(*g)()` in `void (*g)()`.
|
|
1086
|
+
*/
|
|
1087
|
+
function extractDeclaratorLeafName(node) {
|
|
1088
|
+
let cur = node;
|
|
1089
|
+
let safety = 16;
|
|
1090
|
+
while (safety-- > 0) {
|
|
1091
|
+
if (cur.type === 'identifier' || cur.type === 'type_identifier')
|
|
1092
|
+
return cur.text;
|
|
1093
|
+
// Common wrapper nodes — follow the 'declarator' field when present.
|
|
1094
|
+
const next = cur.childForFieldName('declarator') ??
|
|
1095
|
+
// parenthesized_declarator: single named child
|
|
1096
|
+
(cur.type === 'parenthesized_declarator' ? cur.namedChild(0) : null);
|
|
1097
|
+
if (next === null)
|
|
1098
|
+
return null;
|
|
1099
|
+
cur = next;
|
|
1100
|
+
}
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
966
1103
|
/**
|
|
967
1104
|
* Check if a C++ function_definition or declaration has `static` storage class.
|
|
968
1105
|
*/
|
package/package.json
CHANGED