1use crate::analysis::Analysis;
2use rustc_hir::{
3 Attribute, BodyId, FnDecl, ItemKind,
4 def_id::{DefId, LocalDefId},
5 intravisit::{FnKind, Visitor},
6};
7use rustc_middle::{
8 hir::nested_filter,
9 ty::{TyCtxt, TyKind},
10};
11use rustc_span::{Span, Symbol};
12use std::collections::HashMap;
13use syn::Expr;
14
15use super::{
16 assets_parser::*,
17 attr_parser::parse_rapx_attr,
18 contract::Property,
19 helpers::{collect_unsafe_callsites, get_unsafe_callees},
20 path::{PathExtractor, PathStart},
21};
22
23pub type FnContracts<'tcx> = Vec<Property<'tcx>>;
25
26pub type StructInvariants<'tcx> = Vec<Property<'tcx>>;
28
29#[derive(Clone)]
31pub struct FunctionTarget<'tcx> {
32 pub def_id: DefId,
34 pub owner_struct_def_id: Option<DefId>,
36 pub callee_requires: HashMap<DefId, FnContracts<'tcx>>,
38}
39
40pub struct StructTarget<'tcx> {
42 pub def_id: DefId,
44 pub invariants: StructInvariants<'tcx>,
46 pub function_targets: Vec<FunctionTarget<'tcx>>,
48}
49
50pub struct VerifyTargetCollector<'tcx> {
52 tcx: TyCtxt<'tcx>,
53 pub function_targets: Vec<FunctionTarget<'tcx>>,
55 pub struct_targets: HashMap<DefId, StructTarget<'tcx>>,
57 fn_contract_cache: HashMap<DefId, FnContracts<'tcx>>,
59}
60
61impl<'tcx> VerifyTargetCollector<'tcx> {
62 pub fn new(tcx: TyCtxt<'tcx>) -> Self {
64 VerifyTargetCollector {
65 tcx,
66 function_targets: Vec::new(),
67 struct_targets: HashMap::new(),
68 fn_contract_cache: HashMap::new(),
69 }
70 }
71
72 fn is_std_crate_def_id(&self, def_id: DefId) -> bool {
74 matches!(
75 self.tcx.crate_name(def_id.krate).as_str(),
76 "core" | "std" | "alloc"
77 )
78 }
79
80 fn get_fn_contracts(&mut self, callee_def_id: DefId) -> FnContracts<'tcx> {
89 let is_std = self.is_std_crate_def_id(callee_def_id);
90 self.fn_contract_cache
91 .entry(callee_def_id)
92 .or_insert_with(|| {
93 let mut requires = get_contract_from_annotation(self.tcx, callee_def_id);
95
96 if requires.is_empty() && is_std {
99 requires = get_contract_from_entry(
100 self.tcx,
101 callee_def_id,
102 get_std_contracts_from_assets(self.tcx, callee_def_id),
103 );
104 }
105
106 requires
107 })
108 .clone()
110 }
111
112 fn has_rapx_verify_attr(&self, def_id: LocalDefId) -> bool {
114 let hir_id = self.tcx.local_def_id_to_hir_id(def_id);
115
116 let rapx = Symbol::intern("rapx");
117 let verify = Symbol::intern("verify");
118
119 let attrs = self.tcx.hir_attrs(hir_id);
120
121 attrs.iter().any(|attr| {
122 if attr.is_doc_comment().is_some() {
123 return false;
124 }
125
126 let path = attr.path();
127
128 path.len() == 2 && path[0] == rapx && path[1] == verify
129 })
130 }
131
132 fn build_function_target(&mut self, def_id: DefId) -> FunctionTarget<'tcx> {
134 let unsafe_callees = get_unsafe_callees(self.tcx, def_id);
135 let callee_requires = unsafe_callees
136 .iter()
137 .map(|callee_def_id| (*callee_def_id, self.get_fn_contracts(*callee_def_id)))
138 .collect();
139
140 FunctionTarget {
141 def_id,
142 owner_struct_def_id: self.get_owner_struct_def_id(def_id),
143 callee_requires,
144 }
145 }
146
147 fn get_owner_struct_def_id(&self, def_id: DefId) -> Option<DefId> {
149 let assoc_item = self.tcx.opt_associated_item(def_id)?;
150 let impl_id = assoc_item.impl_container(self.tcx)?;
151 let self_ty = self.tcx.type_of(impl_id).skip_binder();
152
153 match self_ty.kind() {
154 TyKind::Adt(adt_def, _) => Some(adt_def.did()),
155 _ => None,
156 }
157 }
158
159 fn push_function_target(&mut self, function_target: FunctionTarget<'tcx>) {
161 self.function_targets.push(function_target.clone());
162
163 if let Some(struct_def_id) = function_target.owner_struct_def_id {
164 self.struct_targets
165 .entry(struct_def_id)
166 .or_insert_with(|| StructTarget {
167 def_id: struct_def_id,
168 invariants: get_struct_invariants_from_annotation(
169 self.tcx,
170 struct_def_id,
171 function_target.def_id,
172 ),
173 function_targets: Vec::new(),
174 })
175 .function_targets
176 .push(function_target);
177 }
178 }
179}
180
181impl<'tcx> Visitor<'tcx> for VerifyTargetCollector<'tcx> {
182 type NestedFilter = nested_filter::OnlyBodies;
183
184 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
185 self.tcx
186 }
187
188 fn visit_fn(
193 &mut self,
194 _fk: FnKind<'tcx>,
195 _fd: &'tcx FnDecl<'tcx>,
196 _b: BodyId,
197 _span: Span,
198 id: LocalDefId,
199 ) -> Self::Result {
200 if self.has_rapx_verify_attr(id) {
201 let def_id = id.to_def_id();
202 let function_target = self.build_function_target(def_id);
203 self.push_function_target(function_target);
204 }
205 }
206}
207
208pub struct PrepareTargets<'tcx> {
210 tcx: TyCtxt<'tcx>,
211}
212
213impl<'tcx> Analysis for PrepareTargets<'tcx> {
214 fn name(&self) -> &'static str {
215 "Verify Identify Targets Analysis"
216 }
217
218 fn run(&mut self) {
220 rap_info!("======== #[rapx::verify] identify targets ========");
221 let mut collector = VerifyTargetCollector::new(self.tcx);
222 self.tcx.hir_visit_all_item_likes_in_crate(&mut collector);
223
224 for function_target in collector
225 .function_targets
226 .iter()
227 .filter(|target| target.owner_struct_def_id.is_none())
228 {
229 self.log_function_target(function_target, false);
230 }
231
232 let mut struct_ids: Vec<_> = collector.struct_targets.keys().copied().collect();
233 struct_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
234
235 for struct_def_id in struct_ids {
236 let Some(struct_target) = collector.struct_targets.get(&struct_def_id) else {
237 continue;
238 };
239 let struct_path = self.tcx.def_path_str(struct_target.def_id);
240 rap_info!(
241 "[rapx::verify::identify-targets] struct target: {} (DefId: {:?})",
242 struct_path,
243 struct_target.def_id
244 );
245
246 if struct_target.invariants.is_empty() {
247 rap_info!(" struct invariants: <none>");
248 } else {
249 for property in &struct_target.invariants {
250 rap_info!(
251 " struct invariant: kind={:?}, args={:?}",
252 property.kind,
253 property.args
254 );
255 }
256 }
257
258 for function_target in &struct_target.function_targets {
259 self.log_function_target(function_target, true);
260 }
261 }
262
263 let total_free_function_targets = collector
264 .function_targets
265 .iter()
266 .filter(|target| target.owner_struct_def_id.is_none())
267 .count();
268 let total_method_targets = collector
269 .function_targets
270 .iter()
271 .filter(|target| target.owner_struct_def_id.is_some())
272 .count();
273 let total_struct_targets = collector.struct_targets.len();
274
275 rap_info!(
276 "total: {} free function target(s) to verify, {} method target(s) to verify, {} struct target(s) to verify",
277 total_free_function_targets,
278 total_method_targets,
279 total_struct_targets
280 );
281 rap_info!("=======================================");
282 }
283
284 fn reset(&mut self) {}
285}
286
287impl<'tcx> PrepareTargets<'tcx> {
288 pub fn new(tcx: TyCtxt<'tcx>) -> Self {
290 PrepareTargets { tcx }
291 }
292
293 fn log_function_target(&self, target: &FunctionTarget<'tcx>, nested_under_struct: bool) {
295 let target_path = self.tcx.def_path_str(target.def_id);
296 let prefix = if nested_under_struct {
297 " method target"
298 } else {
299 "[rapx::verify::identify-targets] function target"
300 };
301 rap_info!("{}: {} (DefId: {:?})", prefix, target_path, target.def_id);
302
303 if let Some(struct_def_id) = target.owner_struct_def_id {
304 let struct_path = self.tcx.def_path_str(struct_def_id);
305 rap_info!(
306 " owner struct: {} (DefId: {:?})",
307 struct_path,
308 struct_def_id
309 );
310 }
311
312 if target.callee_requires.is_empty() {
313 rap_info!(" unsafe callees: <none>");
314 return;
315 }
316
317 let mut unsafe_callee_ids: Vec<_> = target.callee_requires.keys().copied().collect();
318 unsafe_callee_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
319
320 for unsafe_callee_def_id in unsafe_callee_ids {
321 let unsafe_callee_path = self.tcx.def_path_str(unsafe_callee_def_id);
322 rap_info!(
323 " unsafe callee: {} (DefId: {:?})",
324 unsafe_callee_path,
325 unsafe_callee_def_id
326 );
327
328 match target.callee_requires.get(&unsafe_callee_def_id) {
329 Some(requires) if !requires.is_empty() => {
330 for property in requires {
331 rap_info!(
332 " safety contract: kind={:?}, args={:?}",
333 property.kind,
334 property.args
335 );
336 }
337 }
338 _ => {
339 rap_info!(" safety contract: <none>");
340 }
341 }
342 }
343
344 self.log_function_paths(target);
345 }
346
347 fn log_function_paths(&self, target: &FunctionTarget<'tcx>) {
349 let callsites = collect_unsafe_callsites(self.tcx, target.def_id);
350 if callsites.is_empty() {
351 rap_info!(" unsafe callsites: <none>");
352 return;
353 }
354
355 let result = PathExtractor::new(self.tcx, target.def_id, callsites).run();
356
357 rap_info!(" detected loop(s): {}", result.loops().len());
358 for loop_info in result.loops() {
359 let body: Vec<_> = loop_info
360 .blocks
361 .iter()
362 .map(|bb| format!("bb{}", bb.as_usize()))
363 .collect();
364 let exits: Vec<_> = loop_info
365 .exits
366 .iter()
367 .map(|exit| format!("bb{}->bb{}", exit.from.as_usize(), exit.to.as_usize()))
368 .collect();
369 rap_info!(
370 " loop #{}: header=bb{}, body={:?}, exits={:?}",
371 loop_info.id.index(),
372 loop_info.header.as_usize(),
373 body,
374 exits
375 );
376 }
377
378 for (display_index, callsite) in result.callsites().iter().enumerate() {
379 rap_info!(
380 " unsafe callsite #{}: {} at bb{} ({} arg(s))",
381 display_index,
382 callsite.callee_name(self.tcx),
383 callsite.block.as_usize(),
384 callsite.args.len()
385 );
386
387 let mut callsite_paths: Vec<_> = result.paths_for(callsite.location()).iter().collect();
388 callsite_paths.sort_by_key(|path| path.describe());
389
390 if callsite_paths.is_empty() {
391 rap_info!(" paths: <none>");
392 continue;
393 }
394
395 for (path_idx, path) in callsite_paths.iter().enumerate() {
396 let kind = match path.start {
397 PathStart::FunctionEntry => "entry",
398 PathStart::LoopHeader { loop_id } => {
399 rap_info!(
400 " path {} kind: loop-header(loop #{})",
401 path_idx,
402 loop_id.index()
403 );
404 rap_info!(" path {}: {}", path_idx, path.describe());
405 continue;
406 }
407 };
408 rap_info!(" path {} kind: {}", path_idx, kind);
409 rap_info!(" path {}: {}", path_idx, path.describe());
410 }
411 }
412 }
413}
414
415fn get_contract_from_entry<'tcx>(
421 tcx: TyCtxt<'tcx>,
422 def_id: DefId,
423 contract_entries: &[PropertyEntry],
424) -> FnContracts<'tcx> {
425 let mut results = Vec::new();
426 for entry in contract_entries {
427 if entry.args.is_empty() {
428 continue;
429 }
430
431 let mut exprs: Vec<Expr> = Vec::new();
432 for arg_str in &entry.args {
433 match syn::parse_str::<Expr>(arg_str) {
434 Ok(expr) => exprs.push(expr),
435 Err(_) => {
436 rap_error!(
437 "JSON Contract Error: Failed to parse arg '{}' as Rust Expr for tag {}",
438 arg_str,
439 entry.tag
440 );
441 }
442 }
443 }
444
445 if exprs.len() != entry.args.len() {
446 rap_error!(
447 "Parse std API args error: Failed to parse arg '{:?}'",
448 entry.args
449 );
450 continue;
451 }
452
453 let property = Property::new(tcx, def_id, entry.tag.as_str(), &exprs);
454 results.push(property);
455 }
456 results
457}
458
459fn is_rapx_requires_attr(attr: &Attribute) -> bool {
461 matches!(
462 attr,
463 Attribute::Unparsed(tool_attr)
464 if tool_attr.path.segments.len() == 2
465 && tool_attr.path.segments[0].as_str() == "rapx"
466 && tool_attr.path.segments[1].as_str() == "requires"
467 )
468}
469
470fn collect_properties_from_requires_attrs<'tcx>(
472 tcx: TyCtxt<'tcx>,
473 attrs: impl IntoIterator<Item = &'tcx Attribute>,
474 property_def_id: DefId,
475 parse_error_label: &str,
476) -> Vec<Property<'tcx>> {
477 let mut results = Vec::new();
478
479 for attr in attrs {
480 if !is_rapx_requires_attr(attr) {
481 continue;
482 }
483
484 let attr_str = rustc_hir_pretty::attribute_to_string(&tcx, attr);
485 let parsed = match parse_rapx_attr(attr_str.as_str(), "requires") {
486 Ok(parsed) => parsed,
487 Err(err) => {
488 rap_error!(
489 "Failed to parse RAPx {} attr '{}': {}",
490 parse_error_label,
491 attr_str,
492 err
493 );
494 continue;
495 }
496 };
497
498 results.extend(parsed.properties.into_iter().map(|property| {
499 Property::new(tcx, property_def_id, property.tag.as_str(), &property.args)
500 }));
501 }
502
503 results
504}
505
506fn get_contract_from_annotation<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> FnContracts<'tcx> {
508 collect_properties_from_requires_attrs(tcx, tcx.get_all_attrs(def_id), def_id, "requires")
509}
510
511fn get_struct_invariants_from_annotation<'tcx>(
513 tcx: TyCtxt<'tcx>,
514 struct_def_id: DefId,
515 context_def_id: DefId,
516) -> StructInvariants<'tcx> {
517 let Some(local_def_id) = struct_def_id.as_local() else {
518 return Vec::new();
519 };
520
521 let item = tcx.hir_expect_item(local_def_id);
522 if !matches!(item.kind, ItemKind::Struct(..)) {
523 return Vec::new();
524 }
525
526 collect_properties_from_requires_attrs(
527 tcx,
528 tcx.get_all_attrs(struct_def_id),
529 context_def_id,
530 "invariant",
531 )
532}