1use super::{
2 BTreeSet, DirectPropagation, FallibleKind, Language, OperationAttributes, OperationEdge,
3 OperationEdgeKind, OperationKind, OperationNode, SemanticGraphError, SemanticOperationGraph,
4 SemanticRuleMatcher, SemanticRuleScope, TypeTag, cross_language_api_correspondence,
5 match_same_variant_rule, registered_rules,
6};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct OperationObservation {
11 pub source_offset: u64,
13 pub api_name: String,
15 pub type_tag: Option<TypeTag>,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct ConstructObservation {
25 pub source_offset: u64,
27 pub kind: OperationKind,
29 pub fallible_kind: Option<FallibleKind>,
34 pub direct_propagation: Option<DirectPropagation>,
36 pub resource_kind: Option<String>,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
47pub struct SemanticSourceRange {
48 pub start: u64,
50 pub end: u64,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct SemanticGraphWindow {
58 pub graph: SemanticOperationGraph,
60 pub source_range: SemanticSourceRange,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct ApiNormalization {
67 pub graph: Option<SemanticOperationGraph>,
69 pub node_source_ranges: Vec<SemanticSourceRange>,
74 pub excluded_observations: usize,
76}
77
78pub fn normalize_registered_apis(
89 language: Language,
90 build_variant_fingerprint: [u8; 32],
91 observations: Vec<OperationObservation>,
92) -> Result<ApiNormalization, SemanticGraphError> {
93 normalize_registered_observations(
94 language,
95 build_variant_fingerprint,
96 observations,
97 Vec::new(),
98 )
99}
100
101pub fn normalize_registered_observations(
112 language: Language,
113 build_variant_fingerprint: [u8; 32],
114 observations: Vec<OperationObservation>,
115 constructs: Vec<ConstructObservation>,
116) -> Result<ApiNormalization, SemanticGraphError> {
117 let api_with_ranges = observations.into_iter().map(|observation| {
118 let range = SemanticSourceRange {
119 start: observation.source_offset,
120 end: observation.source_offset,
121 };
122 (observation, range)
123 });
124 let constructs_with_ranges = constructs.into_iter().map(|construct| {
125 let range = SemanticSourceRange {
126 start: construct.source_offset,
127 end: construct.source_offset,
128 };
129 (construct, range)
130 });
131 normalize_registered_observations_with_ranges(
132 language,
133 build_variant_fingerprint,
134 api_with_ranges.collect(),
135 constructs_with_ranges.collect(),
136 )
137}
138
139pub fn normalize_registered_observations_with_ranges(
150 language: Language,
151 build_variant_fingerprint: [u8; 32],
152 observations: Vec<(OperationObservation, SemanticSourceRange)>,
153 constructs: Vec<(ConstructObservation, SemanticSourceRange)>,
154) -> Result<ApiNormalization, SemanticGraphError> {
155 if observations
156 .iter()
157 .map(|(_, range)| range)
158 .chain(constructs.iter().map(|(_, range)| range))
159 .any(|range| range.end < range.start)
160 {
161 return Err(SemanticGraphError::InvalidSourceRange);
162 }
163 let observation_count = observations.len();
164 let mut nodes: Vec<_> = observations
165 .into_iter()
166 .enumerate()
167 .filter_map(|(source_index, (observation, source_range))| {
168 let kind = registered_api_kind(language, &observation.api_name)?;
169 let order = observation.api_name.clone();
170 Some((
171 observation.source_offset,
172 source_index,
173 order,
174 source_range,
175 OperationNode {
176 kind,
177 attributes: OperationAttributes {
178 type_tag: observation.type_tag,
179 api_names: BTreeSet::from([observation.api_name]),
180 resource_kind: None,
181 fallible_kind: None,
182 direct_propagation: None,
183 structure_fingerprint: None,
184 },
185 },
186 ObservationSource::Api,
187 ))
188 })
189 .collect();
190 let recognized_api_count = nodes.len();
191 nodes.extend(constructs.into_iter().enumerate().map(
192 |(source_index, (construct, source_range))| {
193 (
194 construct.source_offset,
195 source_index,
196 construct.kind.name().to_owned(),
197 source_range,
198 OperationNode {
199 kind: construct.kind,
200 attributes: OperationAttributes {
201 fallible_kind: construct.fallible_kind,
202 direct_propagation: construct.direct_propagation,
203 resource_kind: construct.resource_kind,
204 ..OperationAttributes::default()
205 },
206 },
207 ObservationSource::Construct,
208 )
209 },
210 ));
211 nodes.sort_by(|left, right| {
212 left.0
213 .cmp(&right.0)
214 .then_with(|| left.1.cmp(&right.1))
215 .then_with(|| left.2.cmp(&right.2))
216 });
217 nodes.dedup_by(coincident_operation);
218 let node_source_ranges = nodes.iter().map(|(_, _, _, range, _, _)| *range).collect();
219 let nodes: Vec<_> = nodes
220 .into_iter()
221 .map(|(_, _, _, _, node, _)| node)
222 .collect();
223 let excluded_observations = observation_count.saturating_sub(recognized_api_count);
226 if nodes.is_empty() {
227 return Ok(ApiNormalization {
228 graph: None,
229 node_source_ranges,
230 excluded_observations,
231 });
232 }
233 let edges = operation_edges(&nodes)?;
234 Ok(ApiNormalization {
235 graph: Some(SemanticOperationGraph::new(
236 language,
237 build_variant_fingerprint,
238 nodes,
239 edges,
240 )?),
241 node_source_ranges,
242 excluded_observations,
243 })
244}
245
246fn operation_edges(nodes: &[OperationNode]) -> Result<Vec<OperationEdge>, SemanticGraphError> {
248 let mut edges = (1..nodes.len())
249 .map(|index| {
250 Ok(OperationEdge {
251 from: u32::try_from(index - 1).map_err(|_| SemanticGraphError::GraphTooLarge)?,
252 to: u32::try_from(index).map_err(|_| SemanticGraphError::GraphTooLarge)?,
253 kind: OperationEdgeKind::Data,
254 })
255 })
256 .collect::<Result<Vec<_>, SemanticGraphError>>()?;
257 for (index, pair) in nodes.windows(2).enumerate() {
258 let [acquire, release] = pair else {
259 continue;
260 };
261 if acquire.kind == OperationKind::AcquireResource
262 && release.kind == OperationKind::ReleaseResource
263 && acquire.attributes.resource_kind == release.attributes.resource_kind
264 {
265 edges.push(OperationEdge {
266 from: u32::try_from(index).map_err(|_| SemanticGraphError::GraphTooLarge)?,
267 to: u32::try_from(index + 1).map_err(|_| SemanticGraphError::GraphTooLarge)?,
268 kind: OperationEdgeKind::ResourceLifetime,
269 });
270 }
271 }
272 Ok(edges)
273}
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
277enum ObservationSource {
278 Api,
279 Construct,
280}
281
282fn coincident_operation(
283 left: &mut (
284 u64,
285 usize,
286 String,
287 SemanticSourceRange,
288 OperationNode,
289 ObservationSource,
290 ),
291 right: &mut (
292 u64,
293 usize,
294 String,
295 SemanticSourceRange,
296 OperationNode,
297 ObservationSource,
298 ),
299) -> bool {
300 left.0 == right.0
301 && left.3 == right.3
302 && left.4.kind == right.4.kind
303 && (left.5 != right.5 || left.1 == right.1)
304}
305
306pub fn registered_semantic_windows(
320 normalization: &ApiNormalization,
321) -> Result<Vec<SemanticGraphWindow>, SemanticGraphError> {
322 let Some(graph) = &normalization.graph else {
323 return Ok(Vec::new());
324 };
325 if graph.nodes.len() != normalization.node_source_ranges.len() {
326 return Err(SemanticGraphError::SourceRangeCountMismatch);
327 }
328 let mut windows = Vec::new();
329 for rule in registered_rules()
330 .iter()
331 .copied()
332 .filter(|rule| rule.scope == SemanticRuleScope::SameBuildVariant)
333 {
334 match rule.matcher {
335 SemanticRuleMatcher::EquivalentSequence => {
336 let mut start = 0;
337 while start < graph.nodes.len() {
338 while start < graph.nodes.len()
339 && !rule
340 .pattern
341 .permitted_kinds
342 .contains(&graph.nodes[start].kind)
343 {
344 start += 1;
345 }
346 let end = graph.nodes[start..]
347 .iter()
348 .position(|node| !rule.pattern.permitted_kinds.contains(&node.kind))
349 .map_or(graph.nodes.len(), |length| start + length);
350 if start < end {
351 let window = semantic_graph_window(
352 graph,
353 &normalization.node_source_ranges,
354 start,
355 end,
356 )?;
357 if match_same_variant_rule(rule, &window.graph, &window.graph).is_some() {
358 windows.push(window);
359 }
360 }
361 start = end.saturating_add(1);
362 }
363 }
364 SemanticRuleMatcher::ExactApiSequence { api_names } => {
365 if !api_names.is_empty() && api_names.len() <= graph.nodes.len() {
369 for start in 0..=graph.nodes.len() - api_names.len() {
370 let window = semantic_graph_window(
371 graph,
372 &normalization.node_source_ranges,
373 start,
374 start + api_names.len(),
375 )?;
376 if match_same_variant_rule(rule, &window.graph, &window.graph).is_some() {
377 windows.push(window);
378 }
379 }
380 }
381 }
382 SemanticRuleMatcher::DirectConstruct { .. } => {
383 for index in 0..graph.nodes.len() {
384 let window = semantic_graph_window(
385 graph,
386 &normalization.node_source_ranges,
387 index,
388 index + 1,
389 )?;
390 if match_same_variant_rule(rule, &window.graph, &window.graph).is_some() {
391 windows.push(window);
392 }
393 }
394 }
395 SemanticRuleMatcher::ResourceLifecycle => {
396 for index in 0..graph.nodes.len().saturating_sub(1) {
397 let window = semantic_graph_window(
398 graph,
399 &normalization.node_source_ranges,
400 index,
401 index + 2,
402 )?;
403 if match_same_variant_rule(rule, &window.graph, &window.graph).is_some() {
404 windows.push(window);
405 }
406 }
407 }
408 }
409 }
410 windows.sort_by_key(|window| window.source_range);
411 windows.dedup_by(|left, right| {
412 left.source_range == right.source_range && left.graph == right.graph
413 });
414 Ok(windows)
415}
416
417fn semantic_graph_window(
418 graph: &SemanticOperationGraph,
419 ranges: &[SemanticSourceRange],
420 start: usize,
421 end: usize,
422) -> Result<SemanticGraphWindow, SemanticGraphError> {
423 let source_range = SemanticSourceRange {
424 start: ranges[start].start,
425 end: ranges[end - 1].end,
426 };
427 let offset = u32::try_from(start).map_err(|_| SemanticGraphError::GraphTooLarge)?;
428 let limit = u32::try_from(end).map_err(|_| SemanticGraphError::GraphTooLarge)?;
429 let edges = graph
430 .edges
431 .iter()
432 .filter(|edge| {
433 edge.from >= offset && edge.from < limit && edge.to >= offset && edge.to < limit
434 })
435 .map(|edge| OperationEdge {
436 from: edge.from - offset,
437 to: edge.to - offset,
438 kind: edge.kind,
439 })
440 .collect();
441 Ok(SemanticGraphWindow {
442 graph: SemanticOperationGraph::new(
443 graph.language,
444 graph.build_variant_fingerprint,
445 graph.nodes[start..end].to_vec(),
446 edges,
447 )?,
448 source_range,
449 })
450}
451
452fn registered_api_kind(language: Language, api_name: &str) -> Option<OperationKind> {
453 cross_language_api_correspondence(language, api_name)
454 .map(|entry| entry.operation)
455 .or_else(|| {
456 matches!(
457 (language, api_name),
458 (
459 Language::Rust,
460 "rust::ToString::to_string" | "rust::str::parse"
461 ) | (Language::Cpp, "std::to_string" | "std::stoull")
462 )
463 .then_some(OperationKind::Map)
464 })
465}