fix(union): complete downstream container handling
This commit is contained in:
@@ -86,6 +86,45 @@ int dispatch(struct ops o) { return o.handler(); }
|
|||||||
expect(edges.every((e) => e.via === 'ops.handler')).toBe(true);
|
expect(edges.every((e) => e.via === 'ops.handler')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('bridges function-pointer fields declared in a union', async () => {
|
||||||
|
write('union-ops.c', `
|
||||||
|
union ops { int (*handler)(void); };
|
||||||
|
static int on_open(void) { return 1; }
|
||||||
|
static union ops the_ops = { .handler = on_open };
|
||||||
|
|
||||||
|
int dispatch(union ops o) { return o.handler(); }
|
||||||
|
`);
|
||||||
|
const edges = await load();
|
||||||
|
expect(has(edges, 'dispatch', 'on_open')).toBe(true);
|
||||||
|
expect(edges.every((e) => e.via === 'ops.handler')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bridges an inline union table whose entries are macro-built', async () => {
|
||||||
|
write('inline-union.c', `
|
||||||
|
#define SLOT(fn) { fn }
|
||||||
|
static int on_open(void) { return 1; }
|
||||||
|
static union inline_ops { int (*handler)(void); } ops[] = { SLOT(on_open) };
|
||||||
|
|
||||||
|
int dispatch(union inline_ops o) { return o.handler(); }
|
||||||
|
`);
|
||||||
|
const edges = await load();
|
||||||
|
expect(has(edges, 'dispatch', 'on_open')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bridges a union table declared through an object-macro type alias', async () => {
|
||||||
|
write('alias-union.c', `
|
||||||
|
#define OPS_TYPE union ops
|
||||||
|
#define SLOT(fn) { fn }
|
||||||
|
union ops { int (*handler)(void); };
|
||||||
|
static int on_open(void) { return 1; }
|
||||||
|
static OPS_TYPE ops[] = { SLOT(on_open) };
|
||||||
|
|
||||||
|
int dispatch(union ops o) { return o.handler(); }
|
||||||
|
`);
|
||||||
|
const edges = await load();
|
||||||
|
expect(has(edges, 'dispatch', 'on_open')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('bridges the typedef-field + field←field double-hop (the hook_demo.c shape)', async () => {
|
it('bridges the typedef-field + field←field double-hop (the hook_demo.c shape)', async () => {
|
||||||
write('hook.c', `
|
write('hook.c', `
|
||||||
typedef void (*hook_func)(void);
|
typedef void (*hook_func)(void);
|
||||||
|
|||||||
@@ -135,10 +135,16 @@ export function validateEmail(email: string): boolean {
|
|||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(srcDir, 'callback_ops.c'),
|
||||||
|
`union CallbackOps { int (*run)(int); };
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
// Initialize CodeGraph
|
// Initialize CodeGraph
|
||||||
cg = CodeGraph.initSync(testDir, {
|
cg = CodeGraph.initSync(testDir, {
|
||||||
config: {
|
config: {
|
||||||
include: ['**/*.ts'],
|
include: ['**/*.ts', '**/*.c'],
|
||||||
exclude: [],
|
exclude: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -194,6 +200,15 @@ export function validateEmail(email: string): boolean {
|
|||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('includes union definitions in the default context search', async () => {
|
||||||
|
const result = await cg.findRelevantContext('CallbackOps');
|
||||||
|
const union = [...result.nodes.values()].find(
|
||||||
|
(node) => node.kind === 'union' && node.name === 'CallbackOps'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(union).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
it('should include edges in the result', async () => {
|
it('should include edges in the result', async () => {
|
||||||
const result = await cg.findRelevantContext('checkout', {
|
const result = await cg.findRelevantContext('checkout', {
|
||||||
traversalDepth: 2,
|
traversalDepth: 2,
|
||||||
|
|||||||
@@ -1045,6 +1045,34 @@ void initialize() { Packet(); }
|
|||||||
expect(outgoing.some((e) => e.kind === 'calls' && e.target === packet!.id)).toBe(false);
|
expect(outgoing.some((e) => e.kind === 'calls' && e.target === packet!.id)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('resolves a static call through an imported C++ union to its member', async () => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tempDir, 'ops.hpp'),
|
||||||
|
`union Ops {
|
||||||
|
static int run() { return 1; }
|
||||||
|
};
|
||||||
|
`
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tempDir, 'main.cpp'),
|
||||||
|
`#include "ops.hpp"
|
||||||
|
|
||||||
|
int invoke() { return Ops::run(); }
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
cg = await CodeGraph.init(tempDir, { index: true });
|
||||||
|
cg.resolveReferences();
|
||||||
|
|
||||||
|
const invoke = cg.getNodesByKind('function').find((n) => n.name === 'invoke');
|
||||||
|
const run = cg.getNodesByKind('method').find((n) => n.name === 'run');
|
||||||
|
expect(invoke).toBeDefined();
|
||||||
|
expect(run).toBeDefined();
|
||||||
|
|
||||||
|
const outgoing = cg.getOutgoingEdges(invoke!.id);
|
||||||
|
expect(outgoing.some((e) => e.kind === 'calls' && e.target === run!.id)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('records instantiates for C++ stack/brace construction, targeting the class (#1035)', async () => {
|
it('records instantiates for C++ stack/brace construction, targeting the class (#1035)', async () => {
|
||||||
// `Calculator calc(0)` (direct-init) and `Widget w{1, 2}` (brace-init)
|
// `Calculator calc(0)` (direct-init) and `Widget w{1, 2}` (brace-init)
|
||||||
// carry the constructor args directly on the declarator — there's no
|
// carry the constructor args directly on the declarator — there's no
|
||||||
|
|||||||
@@ -432,8 +432,19 @@ struct InlineScan {
|
|||||||
fn scan_inline_structs(s: &[u8]) -> InlineScan {
|
fn scan_inline_structs(s: &[u8]) -> InlineScan {
|
||||||
let mut out = InlineScan { ptr: false, types: Vec::new(), tags: Vec::new() };
|
let mut out = InlineScan { ptr: false, types: Vec::new(), tags: Vec::new() };
|
||||||
let mut last = 0;
|
let mut last = 0;
|
||||||
while let Some(t) = find_word(s, b"struct", last) {
|
loop {
|
||||||
let after_kw = t + 6;
|
let next_struct = find_word(s, b"struct", last);
|
||||||
|
let next_union = find_word(s, b"union", last);
|
||||||
|
let Some((t, keyword_len)) = (match (next_struct, next_union) {
|
||||||
|
(Some(st), Some(un)) if st < un => Some((st, 6)),
|
||||||
|
(Some(_), Some(un)) => Some((un, 5)),
|
||||||
|
(Some(st), None) => Some((st, 6)),
|
||||||
|
(None, Some(un)) => Some((un, 5)),
|
||||||
|
(None, None) => None,
|
||||||
|
}) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
let after_kw = t + keyword_len;
|
||||||
let ws = skip_jsws(s, after_kw);
|
let ws = skip_jsws(s, after_kw);
|
||||||
if ws == after_kw || !is_word_at(s, ws) {
|
if ws == after_kw || !is_word_at(s, ws) {
|
||||||
last = t + 1;
|
last = t + 1;
|
||||||
@@ -569,10 +580,10 @@ fn init_body(s: &[u8], p: usize) -> Option<(String, usize)> {
|
|||||||
let i = skip_jsws(s, p);
|
let i = skip_jsws(s, p);
|
||||||
let mods = modifier_positions(s, i);
|
let mods = modifier_positions(s, i);
|
||||||
for &pos in mods.iter().rev() {
|
for &pos in mods.iter().rev() {
|
||||||
for with_struct in [true, false] {
|
for keyword in [Some(b"struct".as_slice()), Some(b"union".as_slice()), None] {
|
||||||
let q = if with_struct {
|
let q = if let Some(keyword) = keyword {
|
||||||
if s.len() >= pos + 6 && &s[pos..pos + 6] == b"struct" {
|
if s.len() >= pos + keyword.len() && &s[pos..pos + keyword.len()] == keyword {
|
||||||
let e = pos + 6;
|
let e = pos + keyword.len();
|
||||||
let w = skip_jsws(s, e);
|
let w = skip_jsws(s, e);
|
||||||
if w == e {
|
if w == e {
|
||||||
continue;
|
continue;
|
||||||
@@ -729,12 +740,19 @@ fn alias_line(line: &[u8]) -> Option<&[u8]> {
|
|||||||
if v0 == name_end {
|
if v0 == name_end {
|
||||||
return None; // [ \t]+ before the value
|
return None; // [ \t]+ before the value
|
||||||
}
|
}
|
||||||
// (?:struct[ \t]+)* greedy, k-descending on value failure.
|
// (?:(?:struct|union)[ \t]+)* greedy, k-descending on value failure.
|
||||||
let mut stack = vec![v0];
|
let mut stack = vec![v0];
|
||||||
loop {
|
loop {
|
||||||
let cur = *stack.last().unwrap();
|
let cur = *stack.last().unwrap();
|
||||||
if line.len() >= cur + 6 && &line[cur..cur + 6] == b"struct" {
|
let keyword_len = if line.len() >= cur + 6 && &line[cur..cur + 6] == b"struct" {
|
||||||
let e = cur + 6;
|
Some(6)
|
||||||
|
} else if line.len() >= cur + 5 && &line[cur..cur + 5] == b"union" {
|
||||||
|
Some(5)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(keyword_len) = keyword_len {
|
||||||
|
let e = cur + keyword_len;
|
||||||
let w2 = skip_sp_tab(line, e);
|
let w2 = skip_sp_tab(line, e);
|
||||||
if w2 > e {
|
if w2 > e {
|
||||||
stack.push(w2);
|
stack.push(w2);
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ const DEFAULT_BUILD_OPTIONS: Required<BuildContextOptions> = {
|
|||||||
* they tell you something exists, not how it works.
|
* they tell you something exists, not how it works.
|
||||||
*/
|
*/
|
||||||
const HIGH_VALUE_NODE_KINDS: NodeKind[] = [
|
const HIGH_VALUE_NODE_KINDS: NodeKind[] = [
|
||||||
'function', 'method', 'class', 'interface', 'type_alias', 'struct', 'trait',
|
'function', 'method', 'class', 'interface', 'type_alias', 'struct', 'union', 'trait',
|
||||||
'component', 'route', 'variable', 'constant', 'enum', 'module', 'namespace',
|
'component', 'route', 'variable', 'constant', 'enum', 'module', 'namespace',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -503,7 +503,7 @@ export class ContextBuilder {
|
|||||||
// like RestController, BulkRequest, AllocationService — not nodes named exactly that.
|
// like RestController, BulkRequest, AllocationService — not nodes named exactly that.
|
||||||
// Also tries stem variants: "caching" → "cache" finds Cache, CacheBuilder.
|
// Also tries stem variants: "caching" → "cache" finds Cache, CacheBuilder.
|
||||||
if (symbolsFromQuery.length > 0) {
|
if (symbolsFromQuery.length > 0) {
|
||||||
const definitionKinds: NodeKind[] = ['class', 'interface', 'struct', 'trait',
|
const definitionKinds: NodeKind[] = ['class', 'interface', 'struct', 'union', 'trait',
|
||||||
'protocol', 'enum', 'type_alias'];
|
'protocol', 'enum', 'type_alias'];
|
||||||
// Expand symbols with stem variants for broader definition matching
|
// Expand symbols with stem variants for broader definition matching
|
||||||
const expandedSymbols = new Set(symbolsFromQuery);
|
const expandedSymbols = new Set(symbolsFromQuery);
|
||||||
@@ -754,7 +754,7 @@ export class ContextBuilder {
|
|||||||
// LIKE reliably finds these substring matches. Results are appended with
|
// LIKE reliably finds these substring matches. Results are appended with
|
||||||
// guaranteed slots so they don't compete with higher-scoring prefix matches.
|
// guaranteed slots so they don't compete with higher-scoring prefix matches.
|
||||||
if (symbolsFromQuery.length > 0) {
|
if (symbolsFromQuery.length > 0) {
|
||||||
const camelDefinitionKinds: NodeKind[] = ['class', 'interface', 'struct', 'trait',
|
const camelDefinitionKinds: NodeKind[] = ['class', 'interface', 'struct', 'union', 'trait',
|
||||||
'protocol', 'enum', 'type_alias'];
|
'protocol', 'enum', 'type_alias'];
|
||||||
// Callable kinds participate too: in service-layer codebases the
|
// Callable kinds participate too: in service-layer codebases the
|
||||||
// camel-infix definers of a queried FIELD are methods/functions
|
// camel-infix definers of a queried FIELD are methods/functions
|
||||||
@@ -977,7 +977,7 @@ export class ContextBuilder {
|
|||||||
// before reaching extends/implements neighbors. This dedicated step
|
// before reaching extends/implements neighbors. This dedicated step
|
||||||
// ensures subclasses and superclasses always appear in results.
|
// ensures subclasses and superclasses always appear in results.
|
||||||
// Budget: up to maxNodes/4 hierarchy nodes to avoid flooding.
|
// Budget: up to maxNodes/4 hierarchy nodes to avoid flooding.
|
||||||
const typeHierarchyKinds = new Set<string>(['class', 'interface', 'struct', 'trait', 'protocol']);
|
const typeHierarchyKinds = new Set<string>(['class', 'interface', 'struct', 'union', 'trait', 'protocol']);
|
||||||
const maxHierarchyNodes = Math.ceil(opts.maxNodes / 4);
|
const maxHierarchyNodes = Math.ceil(opts.maxNodes / 4);
|
||||||
let hierarchyNodesAdded = 0;
|
let hierarchyNodesAdded = 0;
|
||||||
for (const result of filteredResults) {
|
for (const result of filteredResults) {
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ export class GraphQueryManager {
|
|||||||
const allNodes: Node[] = [];
|
const allNodes: Node[] = [];
|
||||||
const kinds: Node['kind'][] = [
|
const kinds: Node['kind'][] = [
|
||||||
'class',
|
'class',
|
||||||
|
'union',
|
||||||
'function',
|
'function',
|
||||||
'method',
|
'method',
|
||||||
'interface',
|
'interface',
|
||||||
@@ -347,6 +348,7 @@ export class GraphQueryManager {
|
|||||||
'module',
|
'module',
|
||||||
'class',
|
'class',
|
||||||
'struct',
|
'struct',
|
||||||
|
'union',
|
||||||
'interface',
|
'interface',
|
||||||
'trait',
|
'trait',
|
||||||
'function',
|
'function',
|
||||||
|
|||||||
@@ -564,7 +564,7 @@ export class GraphTraverser {
|
|||||||
// into their children so that callers of contained methods appear in impact
|
// into their children so that callers of contained methods appear in impact
|
||||||
const focalNode = this.queries.getNodeById(nodeId);
|
const focalNode = this.queries.getNodeById(nodeId);
|
||||||
if (focalNode) {
|
if (focalNode) {
|
||||||
const containerKinds = new Set(['class', 'interface', 'struct', 'trait', 'protocol', 'module', 'enum']);
|
const containerKinds = new Set(['class', 'interface', 'struct', 'union', 'trait', 'protocol', 'module', 'enum']);
|
||||||
if (containerKinds.has(focalNode.kind)) {
|
if (containerKinds.has(focalNode.kind)) {
|
||||||
const containsEdges = this.queries.getOutgoingEdges(nodeId, ['contains']);
|
const containsEdges = this.queries.getOutgoingEdges(nodeId, ['contains']);
|
||||||
if (containsEdges.length > 0) {
|
if (containsEdges.length > 0) {
|
||||||
|
|||||||
+4
-4
@@ -118,7 +118,7 @@ const RUST_PATH_PREFIXES = new Set(['crate', 'super', 'self']);
|
|||||||
* multi-thousand-character wall of source that bloats the agent's context.
|
* multi-thousand-character wall of source that bloats the agent's context.
|
||||||
*/
|
*/
|
||||||
const CONTAINER_NODE_KINDS = new Set<NodeKind>([
|
const CONTAINER_NODE_KINDS = new Set<NodeKind>([
|
||||||
'class', 'struct', 'interface', 'trait', 'protocol', 'enum', 'namespace', 'module',
|
'class', 'struct', 'union', 'interface', 'trait', 'protocol', 'enum', 'namespace', 'module',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** Last `::` / `.` / `/`-separated segment of a qualified symbol. */
|
/** Last `::` / `.` / `/`-separated segment of a qualified symbol. */
|
||||||
@@ -2927,7 +2927,7 @@ export class ToolHandler {
|
|||||||
const ROOT_CAP = 5; // only the symbols the query actually targeted
|
const ROOT_CAP = 5; // only the symbols the query actually targeted
|
||||||
const FILE_CAP = 4; // caller files listed per symbol before "+N more"
|
const FILE_CAP = 4; // caller files listed per symbol before "+N more"
|
||||||
const MEANINGFUL = new Set<string>([
|
const MEANINGFUL = new Set<string>([
|
||||||
'function', 'method', 'class', 'interface', 'struct', 'trait', 'protocol',
|
'function', 'method', 'class', 'interface', 'struct', 'union', 'trait', 'protocol',
|
||||||
'enum', 'type_alias', 'component', 'constant', 'variable', 'property', 'field',
|
'enum', 'type_alias', 'component', 'constant', 'variable', 'property', 'field',
|
||||||
]);
|
]);
|
||||||
const rel = (p: string) => p.replace(/\\/g, '/');
|
const rel = (p: string) => p.replace(/\\/g, '/');
|
||||||
@@ -3452,7 +3452,7 @@ export class ToolHandler {
|
|||||||
// displaces a flow-central file. Bounded: only the few named seeds, only the
|
// displaces a flow-central file. Bounded: only the few named seeds, only the
|
||||||
// types in their signatures.
|
// types in their signatures.
|
||||||
const CALLABLE_KINDS = new Set(['method', 'function', 'component', 'constructor']);
|
const CALLABLE_KINDS = new Set(['method', 'function', 'component', 'constructor']);
|
||||||
const TYPE_KINDS = new Set(['class', 'struct', 'interface', 'trait', 'protocol', 'enum', 'type_alias']);
|
const TYPE_KINDS = new Set(['class', 'struct', 'union', 'interface', 'trait', 'protocol', 'enum', 'type_alias']);
|
||||||
const SIG_EDGE = new Set(['references', 'type_of', 'returns']);
|
const SIG_EDGE = new Set(['references', 'type_of', 'returns']);
|
||||||
const changeSurfaceCandidates: Node[] = [];
|
const changeSurfaceCandidates: Node[] = [];
|
||||||
const seenChangeSurface = new Set<string>();
|
const seenChangeSurface = new Set<string>();
|
||||||
@@ -4538,7 +4538,7 @@ export class ToolHandler {
|
|||||||
// query actually asked about (#185 follow-up — Session.swift in
|
// query actually asked about (#185 follow-up — Session.swift in
|
||||||
// Alamofire is the canonical case: the `Session` class spans ~1,400
|
// Alamofire is the canonical case: the `Session` class spans ~1,400
|
||||||
// lines). We want the granular symbols inside, not the envelope.
|
// lines). We want the granular symbols inside, not the envelope.
|
||||||
const ENVELOPE_KINDS = new Set(['file', 'module', 'class', 'struct', 'interface', 'enum', 'namespace', 'protocol', 'trait', 'component']);
|
const ENVELOPE_KINDS = new Set(['file', 'module', 'class', 'struct', 'union', 'interface', 'enum', 'namespace', 'protocol', 'trait', 'component']);
|
||||||
// Cluster from this file's gathered nodes PLUS any callable the agent NAMED that
|
// Cluster from this file's gathered nodes PLUS any callable the agent NAMED that
|
||||||
// lives here. Explore's relevance gather can miss a named method def in a huge
|
// lives here. Explore's relevance gather can miss a named method def in a huge
|
||||||
// non-sibling file — Django's query.py is 3,040 lines and `_fetch_all` (L2237)
|
// non-sibling file — Django's query.py is 3,040 lines and `_fetch_all` (L2237)
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ function resolveTypeName(name: string, objEnv: Map<string, string> | undefined):
|
|||||||
let n = name;
|
let n = name;
|
||||||
for (let i = 0; objEnv && i < 5; i++) {
|
for (let i = 0; objEnv && i < 5; i++) {
|
||||||
const v = objEnv.get(n);
|
const v = objEnv.get(n);
|
||||||
const t = v?.trim().match(/^(?:struct\s+)?(\w+)$/);
|
const t = v?.trim().match(/^(?:(?:struct|union)\s+)?(\w+)$/);
|
||||||
if (!t) break;
|
if (!t) break;
|
||||||
n = t[1]!;
|
n = t[1]!;
|
||||||
}
|
}
|
||||||
@@ -370,20 +370,20 @@ const INCLUDABLE_EXT = /\.(def|inc|h|hh|hpp|hxx|c|cc|cpp|cxx|ipp|tcc|tbl)$/i;
|
|||||||
* are excluded: `resolveTypeName` would rewrite to a dead-end token that can
|
* are excluded: `resolveTypeName` would rewrite to a dead-end token that can
|
||||||
* never name a struct, so skipping them is exact, and it drops the register
|
* never name a struct, so skipping them is exact, and it drops the register
|
||||||
* flood. */
|
* flood. */
|
||||||
const OBJ_ALIAS_RE = /^[ \t]*#[ \t]*define[ \t]+(\w+)[ \t]+(?:struct[ \t]+)*[A-Za-z_]\w*[ \t\r]*$/gm;
|
const OBJ_ALIAS_RE = /^[ \t]*#[ \t]*define[ \t]+(\w+)[ \t]+(?:(?:struct|union)[ \t]+)*[A-Za-z_]\w*[ \t\r]*$/gm;
|
||||||
|
|
||||||
/** `(?:struct )?TYPE name[opt] = {` initializers, where TYPE is a struct that
|
/** `(?:struct )?TYPE name[opt] = {` initializers, where TYPE is a struct that
|
||||||
* has ≥1 fn-pointer field. Handles both single (`= {…}`) and array
|
* has ≥1 fn-pointer field. Handles both single (`= {…}`) and array
|
||||||
* (`[] = { {…}, {…} }`) forms. Macro calls inside an element are expanded first. */
|
* (`[] = { {…}, {…} }`) forms. Macro calls inside an element are expanded first. */
|
||||||
const INIT_RE =
|
const INIT_RE =
|
||||||
/(?:^|[;{}])\s*(?:(?:static|const|extern|register|volatile)\s+)*(?:struct\s+)?(\w+)\s+(\w+)\s*(\[[^\]]*\])?\s*=\s*\{/g;
|
/(?:^|[;{}])\s*(?:(?:static|const|extern|register|volatile)\s+)*(?:(?:struct|union)\s+)?(\w+)\s+(\w+)\s*(\[[^\]]*\])?\s*=\s*\{/g;
|
||||||
/** `struct TAG { … } var[opt] [= {…}]` — the struct is defined INLINE with the
|
/** `struct TAG { … } var[opt] [= {…}]` — the struct is defined INLINE with the
|
||||||
* table (vim's `cmdname`/`nv_cmd`); its layout never became a node, so parse it
|
* table (vim's `cmdname`/`nv_cmd`); its layout never became a node, so parse it
|
||||||
* here and register it before reading the entries. No leading anchor: a
|
* here and register it before reading the entries. No leading anchor: a
|
||||||
* `struct TAG {` with a brace body is always a definition (it may be preceded
|
* `struct TAG {` with a brace body is always a definition (it may be preceded
|
||||||
* by a `#define …` line ending in a digit, as in vim), and the trailing
|
* by a `#define …` line ending in a digit, as in vim), and the trailing
|
||||||
* `var … = {` check below is what distinguishes a TABLE from a plain type. */
|
* `var … = {` check below is what distinguishes a TABLE from a plain type. */
|
||||||
const INLINE_STRUCT_RE = /\bstruct\s+(\w+)\s*\{/g;
|
const INLINE_STRUCT_RE = /\b(?:struct|union)\s+(\w+)\s*\{/g;
|
||||||
/** `(?:static …)* ELEMTYPE [*] name[…] = { … }` — a bare array of function
|
/** `(?:static …)* ELEMTYPE [*] name[…] = { … }` — a bare array of function
|
||||||
* pointers (no struct wrapper). The optional `*` covers a function-TYPE
|
* pointers (no struct wrapper). The optional `*` covers a function-TYPE
|
||||||
* typedef element (`opcode_t *opcodes[]`); a function-pointer typedef element
|
* typedef element (`opcode_t *opcodes[]`); a function-pointer typedef element
|
||||||
@@ -854,12 +854,14 @@ export async function cFnPointerDispatchEdges(
|
|||||||
if (fields.some((f) => f.isFnPtr)) structLayout.set(name, fields);
|
if (fields.some((f) => f.isFnPtr)) structLayout.set(name, fields);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const st of (ctx.iterateNodesByKind?.('struct') ?? ctx.getNodesByKind('struct'))) {
|
for (const kind of ['struct', 'union'] as const) {
|
||||||
if ((++scannedFiles & 255) === 0) await onYield();
|
for (const st of (ctx.iterateNodesByKind?.(kind) ?? ctx.getNodesByKind(kind))) {
|
||||||
if (!C_CPP_EXT.test(st.filePath)) continue;
|
if ((++scannedFiles & 255) === 0) await onYield();
|
||||||
const rawFields = rawFieldsByNode.get(st.id);
|
if (!C_CPP_EXT.test(st.filePath)) continue;
|
||||||
if (!rawFields) continue; // file unreadable or body unparsable at sweep time — the old pass skipped it too
|
const rawFields = rawFieldsByNode.get(st.id);
|
||||||
registerStructLayout(st.name, classifyFields(rawFields));
|
if (!rawFields) continue; // file unreadable or body unparsable at sweep time — the old pass skipped it too
|
||||||
|
registerStructLayout(st.name, classifyFields(rawFields));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rawFieldsByNode.clear();
|
rawFieldsByNode.clear();
|
||||||
if (prof) { prof.B = Date.now() - tPass; tPass = Date.now(); }
|
if (prof) { prof.B = Date.now() - tPass; tPass = Date.now(); }
|
||||||
@@ -1211,7 +1213,7 @@ export async function cFnPointerDispatchEdges(
|
|||||||
const recvTypeIn = (fnSrc: string, recv: string): string | null => {
|
const recvTypeIn = (fnSrc: string, recv: string): string | null => {
|
||||||
let re = recvReCache.get(recv);
|
let re = recvReCache.get(recv);
|
||||||
if (!re) {
|
if (!re) {
|
||||||
re = new RegExp(`(?:struct\\s+)?(\\w+)\\s*\\*?\\s*\\b${recv}\\b\\s*(?:[,)=;]|\\[)`, 'g');
|
re = new RegExp(`(?:(?:struct|union)\\s+)?(\\w+)\\s*\\*?\\s*\\b${recv}\\b\\s*(?:[,)=;]|\\[)`, 'g');
|
||||||
recvReCache.set(recv, re);
|
recvReCache.set(recv, re);
|
||||||
}
|
}
|
||||||
re.lastIndex = 0;
|
re.lastIndex = 0;
|
||||||
@@ -1230,7 +1232,7 @@ export async function cFnPointerDispatchEdges(
|
|||||||
const varTypeIn = (fnSrc: string, v: string): string | null => {
|
const varTypeIn = (fnSrc: string, v: string): string | null => {
|
||||||
let re = varReCache.get(v);
|
let re = varReCache.get(v);
|
||||||
if (!re) {
|
if (!re) {
|
||||||
re = new RegExp(`(?:struct\\s+)?(\\w+)\\s*\\*?\\s*\\b${escapeRe(v)}\\b\\s*(?:[,)=;]|\\[)`, 'g');
|
re = new RegExp(`(?:(?:struct|union)\\s+)?(\\w+)\\s*\\*?\\s*\\b${escapeRe(v)}\\b\\s*(?:[,)=;]|\\[)`, 'g');
|
||||||
varReCache.set(v, re);
|
varReCache.set(v, re);
|
||||||
}
|
}
|
||||||
re.lastIndex = 0;
|
re.lastIndex = 0;
|
||||||
|
|||||||
@@ -2192,7 +2192,7 @@ function findExportedSymbolWalk(
|
|||||||
|
|
||||||
/** Node kinds that own static members reachable as `Container.member`. */
|
/** Node kinds that own static members reachable as `Container.member`. */
|
||||||
const STATIC_MEMBER_CONTAINERS = new Set<Node['kind']>([
|
const STATIC_MEMBER_CONTAINERS = new Set<Node['kind']>([
|
||||||
'class', 'struct', 'interface', 'enum', 'trait', 'protocol',
|
'class', 'struct', 'union', 'interface', 'enum', 'trait', 'protocol',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user