Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f57ea8d2cb | ||
|
|
fc4e813a5c | ||
|
|
094798199b | ||
|
|
9efe1d7c79 | ||
|
|
532cd27f8e | ||
|
|
be7abd86d9 |
@@ -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;
|
||||
}
|
||||
|
||||
+21
@@ -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 {};
|
||||
}
|
||||
+8
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user