Compare commits

...
9 changed files with 119 additions and 25 deletions
@@ -18,11 +18,11 @@
* The current implementation covers ONE associated-entity rule: an argument that's a directly-named
* class type (`audit::Event e`) contributes its **direct enclosing
* namespace** to the candidate set. V2 extends that one step to
* pointer-typed class args (`audit::Event* p`, `audit::Event** pp`):
* they contribute the pointee class's enclosing namespace too. Reference
* arguments, function-pointer arguments, template specializations,
* base-class associated namespaces, and the rest of the full closure are
* still deliberately excluded.
* pointer-typed and reference-typed class args (`audit::Event* p`,
* `audit::Event& r`, `audit::Event&& rr`): they contribute the pointee /
* referred class's enclosing namespace too. Function-pointer arguments,
* template specializations, base-class associated namespaces, and the
* rest of the full closure are still deliberately excluded.
*
* The current implementation also short-circuits to ADL only when ordinary lookup is empty
* (`findCallableBindingInScope` returned undefined). ISO C++ would
@@ -62,18 +62,13 @@ import {
/**
* Per-argument shape information collected at capture time. ADL fires for
* arguments where `simpleClassName !== ''` AND `!isReference`, including
* class pointers whose declarator chain resolves to a named class type.
* arguments where `simpleClassName !== ''`, including class pointers and
* references whose declarator chain resolves to a named class type.
*/
export interface CppAdlArgInfo {
/** Simple class-like type name (last segment of qualified name); empty
* for primitives, literals, function pointers, template specs, etc. */
readonly simpleClassName: string;
/** True when the variable's declarator contained one or more
* `pointer_declarator` wrappers. */
readonly isPointer: boolean;
/** True when the variable's declarator was a `reference_declarator`. */
readonly isReference: boolean;
}
const argInfoBySite = new Map<string, readonly CppAdlArgInfo[]>();
@@ -152,8 +147,8 @@ export function populateCppAssociatedNamespaces(parsed: ParsedFile): void {
*
* Fires only when:
* - the call site is not in `noAdlSites` (parenthesized form), AND
* - at least one argument resolves to a named class type (value or
* pointer, but not reference, function pointer, literal, or primitive).
* - at least one argument resolves to a named class type (value,
* pointer, or reference; but not function pointer, literal, or primitive).
*/
export function pickCppAdlCandidates(
site: {
@@ -175,7 +170,6 @@ export function pickCppAdlCandidates(
const associatedNamespaces = new Set<string>();
for (const arg of args) {
if (arg.simpleClassName === '') continue;
if (arg.isReference) continue;
const classDef = findCppClassDefBySimpleName(arg.simpleClassName, scopes);
if (classDef === undefined) continue;
const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId);
@@ -743,7 +743,7 @@ function inferCppCallAdlArgs(callNode: SyntaxNode): CppAdlArgInfo[] {
return out;
}
const EMPTY_ADL_ARG: CppAdlArgInfo = { simpleClassName: '', isPointer: false, isReference: false };
const EMPTY_ADL_ARG: CppAdlArgInfo = { simpleClassName: '' };
function classifyAdlArg(argNode: SyntaxNode): CppAdlArgInfo {
// Literals and primitive-shaped expressions never have associated namespaces.
@@ -794,12 +794,11 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo {
// Unwrap declarator chain to find pointer/reference markers and the
// variable name. `init_declarator > pointer_declarator > identifier`
// means pointer-typed; repeated pointer wrappers still count as pointer
// typed; `init_declarator > reference_declarator > ...` means
// reference-typed; bare `init_declarator > identifier` is value.
// typed; `init_declarator > reference_declarator > ...` (or
// `rvalue_reference_declarator`) means reference-typed; bare
// `init_declarator > identifier` is value.
// Function-pointer wrappers (`pointer_declarator > function_declarator`)
// must not contribute ADL associated namespaces.
let isPointer = false;
let isReference = false;
let isFunctionPointer = false;
let inner: SyntaxNode = declarator;
let nameText: string | null = null;
@@ -810,14 +809,12 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo {
isFunctionPointer = true;
break;
}
isPointer = true;
const next = inner.childForFieldName('declarator');
if (next === null) break;
inner = next;
continue;
}
if (inner.type === 'reference_declarator') {
isReference = true;
if (inner.type === 'reference_declarator' || inner.type === 'rvalue_reference_declarator') {
// reference_declarator has a single child (the inner declarator).
let next: SyntaxNode | null = null;
for (let j = 0; j < inner.namedChildCount; j++) {
@@ -848,7 +845,7 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo {
if (isFunctionPointer || nameText !== varName) continue;
const simpleClassName = extractAdlSimpleTypeName(typeNode);
return { simpleClassName, isPointer, isReference };
return { simpleClassName };
}
return EMPTY_ADL_ARG;
}
@@ -0,0 +1,21 @@
#include "audit.h"
namespace app {
void runRef() {
audit::Event e;
audit::Event& s = e;
record(s);
}
void runConstRef() {
audit::Event e;
const audit::Event& constEventRef = e;
recordConst(constEventRef);
}
void runPrimitiveRef() {
int n = 0;
int& r = n;
note(r);
}
}
@@ -0,0 +1,5 @@
#pragma once
namespace audit {
struct Event {};
}
@@ -0,0 +1,8 @@
#pragma once
#include "audit.h"
namespace audit {
void record(Event& e);
void recordConst(const Event& e);
}
@@ -0,0 +1,9 @@
#include "audit.h"
namespace app {
void runRvalueRef() {
audit::Event e;
audit::Event&& rr = static_cast<audit::Event&&>(e);
record(rr);
}
}
@@ -0,0 +1,5 @@
#pragma once
namespace audit {
struct Event {};
}
@@ -0,0 +1,7 @@
#pragma once
#include "audit.h"
namespace audit {
void record(Event&& e);
}
@@ -2103,7 +2103,7 @@ describe('C++ two-phase template lookup — cross-file namespace variant', () =>
// Free-function calls with class-typed arguments must consider candidates
// declared in the argument's enclosing namespace (associated namespace).
// V1 boundary: only direct enclosing-namespace closure for value class-
// typed args; pointer / reference / template-spec args excluded.
// typed args; pointer and reference args included, template-spec args excluded.
// ---------------------------------------------------------------------------
describe('C++ ADL — basic associated-namespace closure', () => {
@@ -2161,6 +2161,54 @@ describe('C++ ADL — pointer arg unwrapping', () => {
});
});
describe('C++ ADL — reference arg unwrapping', () => {
let result: PipelineResult;
beforeAll(async () => {
result = await runPipelineFromRepo(
path.join(FIXTURES, 'cpp-adl-reference-arg-boundary'),
() => {},
);
}, 60000);
it('record(s) where s is audit::Event& resolves to audit::record via ADL', () => {
const calls = getRelationships(result, 'CALLS');
const recordCalls = calls.filter((c) => c.source === 'runRef' && c.target === 'record');
expect(recordCalls.length).toBe(1);
expect(recordCalls[0].targetFilePath).toContain('record.h');
});
it('recordConst(cs) where cs is const audit::Event& resolves via ADL', () => {
const calls = getRelationships(result, 'CALLS');
const recordCalls = calls.filter(
(c) => c.source === 'runConstRef' && c.target === 'recordConst',
);
expect(recordCalls.length).toBe(1);
expect(recordCalls[0].targetFilePath).toContain('record.h');
});
it('note(r) where r is int& emits zero CALLS edges (primitive ref)', () => {
const calls = getRelationships(result, 'CALLS');
const noteCalls = calls.filter((c) => c.source === 'runPrimitiveRef' && c.target === 'note');
expect(noteCalls.length).toBe(0);
});
});
describe('C++ ADL — rvalue reference args participate', () => {
let result: PipelineResult;
beforeAll(async () => {
result = await runPipelineFromRepo(path.join(FIXTURES, 'cpp-adl-rvalue-ref'), () => {});
}, 60000);
it('record(rr) where rr is audit::Event&& resolves to audit::record via ADL', () => {
const calls = getRelationships(result, 'CALLS');
const recordCalls = calls.filter((c) => c.source === 'runRvalueRef' && c.target === 'record');
expect(recordCalls.length).toBe(1);
expect(recordCalls[0].targetFilePath).toContain('record-rvalue.h');
});
});
describe('C++ ADL — function pointer args do not participate', () => {
let result: PipelineResult;