1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
use super::{
Declaration, DeclarationKind, Extractor, Language, Reference, ReferenceKind, Scope, TokenKind,
};
impl Extractor<'_, '_> {
/// `impl Type`, `impl Trait for Type` and `extension Type` name what their
/// members belong to without declaring anything themselves.
///
/// The name is the last one before the brace, which is what makes
/// `impl Display for Engine` belong to `Engine` rather than to `Display`.
pub(super) fn open_named_scope(&mut self, keyword: usize) -> Option<usize> {
self.drop_waiting();
let limit = (keyword + 64).min(self.tokens.len());
let mut cursor = keyword + 1;
let mut name = None;
let mut generic = 0_i32;
while cursor < limit && !self.punct(cursor, "{") {
if self.punct(cursor, ";") {
return None;
}
// A generic argument is part of the type, not a name of its own.
if self.punct(cursor, "<") {
generic += 1;
} else if self.punct(cursor, ">") {
generic -= 1;
} else if generic == 0 && self.kind(cursor) == Some(TokenKind::Identifier) {
name = Some(self.text(cursor).to_owned());
}
cursor += 1;
}
let name = name?;
if !self.punct(cursor, "{") {
return None;
}
if self.language == Language::Swift {
self.swift_extension_heritage(keyword + 1, cursor, &name);
}
let test_only = self.test_only_at(keyword);
self.scopes.push(Scope {
name,
depth: None,
declaration: None,
type_body: true,
test_only,
});
Some(cursor)
}
/// `extension Engine: Equatable, Codable` names the protocols the members
/// satisfy. The colon is not `implements`, so the shared heritage walk
/// never sees it unless this pass records those types.
fn swift_extension_heritage(&mut self, start: usize, end: usize, owner: &str) {
let mut cursor = start;
let mut active = false;
let mut generic = 0_i32;
while cursor < end {
if self.punct(cursor, "<") {
generic += 1;
} else if self.punct(cursor, ">") {
generic -= 1;
} else if generic == 0 && self.punct(cursor, ":") {
active = true;
} else if active
&& generic == 0
&& self.kind(cursor) == Some(TokenKind::Identifier)
&& !self.punct(cursor.wrapping_sub(1), ".")
{
self.facts.references.push(Reference {
name: self.text(cursor).to_owned(),
kind: ReferenceKind::Implements,
receiver: None,
span: self.span(cursor, cursor),
owner: Some(owner.to_owned()),
string_arguments: Vec::new(),
name_arguments: Vec::new(),
});
}
cursor += 1;
}
}
/// A C or C++ function, which no keyword introduces: what marks it is a
/// return type before the name and a body after the parameter list.
///
/// Without this, `int add(int a, int b) { }` matched nothing and then fell
/// through to the call path, so every C function definition was recorded
/// as a call to itself - a self-edge in the graph, and no declaration for
/// dead-code analysis to find.
pub(super) fn typed_function(&mut self, index: usize, exported: bool) -> Option<usize> {
if !self.rules.typed_functions || !self.punct(index + 1, "(") {
return None;
}
let name = self.text(index);
// A control structure is also a name followed by a parenthesis and a
// brace, and `else if (x) {` even has an identifier before it.
if matches!(
name,
"if" | "for" | "while" | "switch" | "return" | "catch" | "sizeof" | "do"
) {
return None;
}
// What precedes the name decides: a return type, possibly through a
// `Class::` qualifier, means a definition; anything else means a call.
let (owner, type_index) = if self.punct(index - 1, ":")
&& self.punct(index.checked_sub(2)?, ":")
&& self.kind(index.checked_sub(3)?) == Some(TokenKind::Identifier)
{
(Some(self.text(index - 3).to_owned()), index.checked_sub(4)?)
} else {
(self.owner(), index.checked_sub(1)?)
};
let preceded_by_type = self.kind(type_index) == Some(TokenKind::Identifier)
&& !matches!(self.text(type_index), "return" | "else" | "case" | "goto")
|| self.punct(type_index, "*")
|| self.punct(type_index, "&");
if !preceded_by_type {
return None;
}
// Only a body proves a definition. A prototype ends at a semicolon,
// and so does `return helper(x);` - so prototypes are left alone
// rather than risk reading every call as a declaration.
let mut cursor = index + 2;
let mut depth = 1_i32;
let limit = (index + 512).min(self.tokens.len());
while cursor < limit && depth > 0 {
if self.punct(cursor, "(") {
depth += 1;
} else if self.punct(cursor, ")") {
depth -= 1;
}
cursor += 1;
}
// `const`, `noexcept` and `override` may sit between `)` and the body.
while cursor < limit && self.kind(cursor) == Some(TokenKind::Identifier) {
cursor += 1;
}
if !self.punct(cursor, "{") {
return None;
}
let name = name.to_owned();
let test_only = self.test_only_at(index);
let declaration_span = self.span(index, index);
self.record_test_only_declaration(test_only, declaration_span);
let declaration = self.facts.declarations.len();
self.facts.declarations.push(Declaration {
name: name.clone(),
kind: if owner.is_some() {
DeclarationKind::Method
} else {
DeclarationKind::Function
},
span: declaration_span,
extent: declaration_span,
owner,
// C has no visibility keyword; a static function is file-local and
// everything else is linkable.
exported: exported || !self.is_static(index),
});
self.scopes.push(Scope {
name,
depth: None,
declaration: Some(declaration),
type_body: false,
test_only,
});
Some(index + 1)
}
/// How many tokens the `<...>` group at `index` occupies, or zero when
/// this is a comparison rather than a type list.
///
/// A closing angle bracket is what tells the two apart: `a < b` never
/// reaches one before the statement ends.
///
/// This deliberately allocates nothing. Rust writes `Vec<String>` and
/// `Option<T>` everywhere, so this runs on almost every identifier in a
/// file and almost always ends in a type rather than a call - collecting
/// the names here cost a heap allocation per type argument that was then
/// thrown away, and a third of the extraction throughput with it.
pub(super) fn type_argument_span(&self, index: usize) -> usize {
if !self.punct(index, "<") {
return 0;
}
let limit = (index + 32).min(self.tokens.len());
let mut cursor = index + 1;
let mut depth = 1_i32;
while cursor < limit && depth > 0 {
if self.punct(cursor, "<") {
depth += 1;
} else if self.punct(cursor, ">") {
depth -= 1;
} else if self.punct(cursor, ";") || self.punct(cursor, "{") {
// A statement ended, so the angle bracket was an operator.
return 0;
}
cursor += 1;
}
if depth > 0 { 0 } else { cursor - index }
}
/// The type names inside a group already known to be one.
pub(super) fn type_argument_names(&self, index: usize, length: usize) -> Vec<String> {
(index..index + length)
.filter(|cursor| self.kind(*cursor) == Some(TokenKind::Identifier))
.map(|cursor| self.text(cursor).to_owned())
.collect()
}
/// Whether the declaration ending at `index` was marked `static`.
pub(super) fn is_static(&self, index: usize) -> bool {
let start = index.saturating_sub(4);
(start..index).any(|cursor| self.text(cursor) == "static")
}
/// A method written directly inside a class or struct body, in languages
/// that declare members without a keyword.
pub(super) fn braced_member(&mut self, index: usize, exported: bool) -> Option<usize> {
if !self.rules.braced_members {
return None;
}
let inside_type = self.scopes.last().is_some_and(|scope| {
scope.type_body && scope.depth.is_some_and(|depth| self.depth == depth)
});
if !inside_type {
return None;
}
// An annotation is not a member. `@GetMapping("/stock")` and
// `[HttpGet("/health")]` configure the member written beneath them,
// and reading them as declarations both invents a method and loses
// the route they carry.
if self.punct(index.wrapping_sub(1), "@") || self.punct(index.wrapping_sub(1), "[") {
return None;
}
// `Type name(` declares a method; the name is the token before `(`.
// Reaching a terminator first means there is no parameter list, so
// this is a field rather than a method - and the loop must leave the
// decision to the field path instead of giving up here.
let mut cursor = index;
let limit = (index + 16).min(self.tokens.len());
while cursor < limit && !self.punct(cursor + 1, "(") {
if self.punct(cursor, ";") || self.punct(cursor, "{") || self.punct(cursor, "=") {
return self.braced_field(index, exported);
}
cursor += 1;
}
// `private String name;` declares a field: a type, a name, and no
// parameter list. The line scanner recorded these, so losing them
// would be a regression rather than a simplification.
if cursor >= limit || !self.punct(cursor + 1, "(") {
return self.braced_field(index, exported);
}
if self.kind(cursor) != Some(TokenKind::Identifier) {
return None;
}
let name = self.text(cursor).to_owned();
if matches!(name.as_str(), "if" | "for" | "while" | "switch" | "return") {
return None;
}
let test_only = self.test_only_at(index);
let declaration_span = self.span(index, cursor);
self.record_test_only_declaration(test_only, declaration_span);
let declaration = self.facts.declarations.len();
self.facts.declarations.push(Declaration {
name: name.clone(),
kind: DeclarationKind::Method,
span: declaration_span,
extent: declaration_span,
owner: self.owner(),
exported,
});
// A member named like its enclosing type is a constructor, and its
// parameter types are dependency-injection wiring.
let constructor = self
.scopes
.last()
.is_some_and(|scope| scope.type_body && scope.name == name);
if constructor {
self.parameter_type_uses(cursor + 2);
}
self.scopes.push(Scope {
name,
depth: None,
declaration: Some(declaration),
type_body: false,
test_only,
});
Some(cursor + 1)
}
}