1use std::collections::{HashSet, VecDeque};
2
3use rustc_hir::def_id::DefId;
4use rustc_middle::{
5 mir::{BasicBlock, BinOp, Local, Operand, ProjectionElem, Rvalue, StatementKind, TerminatorKind},
6 ty::TyCtxt,
7};
8
9use crate::analysis::dataflow::{DataflowAnalysis, default::DataflowAnalyzer};
10use crate::analysis::path::graph::{PathEnumerator, PathGraph};
11use crate::helpers::mir_utils as helpers;
12
13use super::CallEffect;
14
15fn trace_to_callee_arg<'tcx>(
18 tcx: TyCtxt<'tcx>,
19 body: &rustc_middle::mir::Body<'tcx>,
20 operand: &Operand<'_>,
21) -> Option<usize> {
22 let local = match operand {
23 Operand::Copy(place) | Operand::Move(place) => place.local,
24 _ => return None,
25 };
26 let idx = local.as_usize();
27 if idx >= 1 && idx <= body.arg_count {
28 return Some(idx - 1);
29 }
30 let mut queue = VecDeque::from([local]);
31 let mut seen = HashSet::from([local]);
32 while let Some(current) = queue.pop_front() {
33 let cidx = current.as_usize();
34 if cidx >= 1 && cidx <= body.arg_count {
35 return Some(cidx - 1);
36 }
37 for bb in body.basic_blocks.iter() {
38 for stmt in &bb.statements {
39 let StatementKind::Assign(assign) = &stmt.kind else {
40 continue;
41 };
42 let dest = assign.0.local;
43 if dest != current {
44 continue;
45 }
46 let source = match &assign.1 {
47 Rvalue::Use(Operand::Copy(place), ..)
48 | Rvalue::Use(Operand::Move(place), ..)
49 | Rvalue::Cast(_, Operand::Copy(place), _)
50 | Rvalue::Cast(_, Operand::Move(place), _)
51 | Rvalue::Ref(_, _, place)
52 | Rvalue::RawPtr(_, place)
53 | Rvalue::CopyForDeref(place) => place.local,
54 _ => continue,
55 };
56 if !seen.contains(&source) {
57 seen.insert(source);
58 queue.push_back(source);
59 }
60 }
61 let Some(terminator) = &bb.terminator else {
62 continue;
63 };
64 let TerminatorKind::Call {
65 func,
66 args,
67 destination,
68 ..
69 } = &terminator.kind
70 else {
71 continue;
72 };
73 if destination.local != current {
74 continue;
75 }
76 let name = helpers::call_name(tcx, func);
77 if !crate::helpers::api_classify::is_as_ptr(&name) {
78 continue;
79 }
80 let Some(source) = args.first().and_then(|arg| match &arg.node {
81 Operand::Copy(place) | Operand::Move(place) => Some(place.local),
82 Operand::Constant(_) => None,
83 #[cfg(rapx_ge_99)]
84 Operand::RuntimeChecks(_) => None,
85 }) else {
86 continue;
87 };
88 if !seen.contains(&source) {
89 seen.insert(source);
90 queue.push_back(source);
91 }
92 }
93 }
94 None
95}
96
97pub(super) fn try_pointer_arith_wrapper_effect<'tcx>(
100 tcx: TyCtxt<'tcx>,
101 callee: DefId,
102 _destination: Option<Local>,
103) -> Option<CallEffect> {
104 if !tcx.is_mir_available(callee) {
105 return None;
106 }
107
108 let body = tcx.optimized_mir(callee);
109 if body.basic_blocks.len() > 16 {
110 return None;
111 }
112 let ret = Local::from_usize(0);
113
114 for bb in body.basic_blocks.iter() {
115 let Some(terminator) = &bb.terminator else {
116 continue;
117 };
118 let TerminatorKind::Call {
119 func,
120 args,
121 destination: call_dest,
122 ..
123 } = &terminator.kind
124 else {
125 continue;
126 };
127
128 let name = helpers::call_name(tcx, func);
129 let is_add = crate::helpers::api_classify::is_pointer_add(&name);
130 let is_sub = crate::helpers::api_classify::is_pointer_sub(&name);
131
132 let inner_effect = if !is_add && !is_sub {
133 helpers::dep_callee_def_id(func).and_then(|inner_callee| {
134 let inner_name = helpers::call_name(tcx, func);
135 if inner_name.contains("::intrinsics::")
136 || inner_name.starts_with("intrinsics::")
137 || inner_name.ends_with("::drop_in_place")
138 {
139 return None;
140 }
141 try_pointer_arith_wrapper_effect(tcx, inner_callee, Some(call_dest.local))
142 })
143 } else {
144 None
145 };
146
147 if !is_add && !is_sub && inner_effect.is_none() {
148 continue;
149 }
150
151 let mut queue = VecDeque::from([call_dest.local]);
152 let mut seen = HashSet::from([call_dest.local]);
153 let mut reaches_ret = false;
154 while let Some(current) = queue.pop_front() {
155 if current == ret {
156 reaches_ret = true;
157 break;
158 }
159 for bb2 in body.basic_blocks.iter() {
160 for stmt in &bb2.statements {
161 let StatementKind::Assign(assign) = &stmt.kind else {
162 continue;
163 };
164 let dest = assign.0.local;
165 if seen.contains(&dest) {
166 continue;
167 }
168 match &assign.1 {
169 Rvalue::Use(Operand::Copy(place), ..)
170 | Rvalue::Use(Operand::Move(place), ..) => {
171 if place.local == current {
172 queue.push_back(dest);
173 seen.insert(dest);
174 }
175 }
176 Rvalue::Cast(_, Operand::Copy(place), _)
177 | Rvalue::Cast(_, Operand::Move(place), _) => {
178 if place.local == current {
179 queue.push_back(dest);
180 seen.insert(dest);
181 }
182 }
183 _ => {}
184 }
185 }
186 }
187 }
188 if !reaches_ret {
189 continue;
190 }
191
192 if let Some(effect) = inner_effect {
193 match effect {
194 CallEffect::ReturnPointerAdd {
195 base_arg: inner_base,
196 offset_arg: inner_offset,
197 stride,
198 }
199 | CallEffect::ReturnPointerSub {
200 base_arg: inner_base,
201 offset_arg: inner_offset,
202 stride,
203 } => {
204 let base_arg = trace_to_callee_arg(tcx, body, &args.get(inner_base)?.node)?;
205 let offset_arg = trace_to_callee_arg(tcx, body, &args.get(inner_offset)?.node)?;
206 return Some(match effect {
207 CallEffect::ReturnPointerSub { .. } => CallEffect::ReturnPointerSub {
208 base_arg,
209 offset_arg,
210 stride,
211 },
212 _ => CallEffect::ReturnPointerAdd {
213 base_arg,
214 offset_arg,
215 stride,
216 },
217 });
218 }
219 _ => {}
220 }
221 continue;
222 }
223
224 let base_arg = trace_to_callee_arg(tcx, body, &args[0].node)?;
225 let offset_arg = trace_to_callee_arg(tcx, body, &args[1].node)?;
226 let stride = if crate::helpers::api_classify::is_byte_ptr_arith(&name) {
227 Some(1)
228 } else {
229 helpers::destination_stride(tcx, callee, Some(call_dest.local))
230 };
231
232 return if is_sub {
233 Some(CallEffect::ReturnPointerSub {
234 base_arg,
235 offset_arg,
236 stride,
237 })
238 } else {
239 Some(CallEffect::ReturnPointerAdd {
240 base_arg,
241 offset_arg,
242 stride,
243 })
244 };
245 }
246
247 None
248}
249
250pub(super) fn callee_contains_pointer_arithmetic(tcx: TyCtxt<'_>, callee: DefId) -> bool {
252 let Some(_) = callee.as_local() else { return false };
253 if !tcx.is_mir_available(callee) { return false; }
254 let body = tcx.optimized_mir(callee);
255 for bb in body.basic_blocks.iter() {
256 let Some(terminator) = &bb.terminator else { continue };
257 let TerminatorKind::Call { func, .. } = &terminator.kind else { continue };
258 let name = helpers::call_name(tcx, func);
259 if crate::helpers::api_classify::is_pointer_add(&name) || crate::helpers::api_classify::is_pointer_sub(&name) {
260 return true;
261 }
262 }
263 false
264}
265
266pub(super) fn local_return_dependencies(tcx: TyCtxt<'_>, callee: DefId) -> Option<Vec<usize>> {
268 callee.as_local()?;
269 if !tcx.is_mir_available(callee) {
270 return None;
271 }
272 helpers::catch_panic(|| {
273 let mut analyzer = DataflowAnalyzer::new(tcx, false);
274 analyzer.build_graph(callee);
275 let deps = analyzer.get_fn_arg2ret(callee);
276 deps.iter_enumerated()
277 .filter_map(|(local, depends)| {
278 if *depends && local.as_usize() > 0 {
279 Some(local.as_usize() - 1)
280 } else {
281 None
282 }
283 })
284 .collect()
285 })
286 .ok()
287}
288
289pub(super) fn try_from_raw_parts_wrapper_effect<'tcx>(
292 tcx: TyCtxt<'tcx>,
293 callee: DefId,
294 _destination: Option<Local>,
295) -> Option<CallEffect> {
296 if !tcx.is_mir_available(callee) {
297 return None;
298 }
299 let body = tcx.optimized_mir(callee);
300 if body.basic_blocks.len() > 8 {
301 return None;
302 }
303 let ret = Local::from_usize(0);
304
305 for bb in body.basic_blocks.iter() {
306 let Some(terminator) = &bb.terminator else { continue };
307 let TerminatorKind::Call {
308 func, args, destination: call_dest, ..
309 } = &terminator.kind else { continue };
310
311 let name = helpers::call_name(tcx, func);
312 if !crate::helpers::api_classify::is_from_raw_parts(&name) {
313 continue;
314 }
315
316 let mut queue = VecDeque::from([call_dest.local]);
318 let mut seen = HashSet::from([call_dest.local]);
319 let mut reaches_ret = false;
320 while let Some(current) = queue.pop_front() {
321 if current == ret { reaches_ret = true; break; }
322 for bb2 in body.basic_blocks.iter() {
323 for stmt in &bb2.statements {
324 let StatementKind::Assign(assign) = &stmt.kind else { continue };
325 if seen.contains(&assign.0.local) { continue; }
326 match &assign.1 {
327 Rvalue::Use(Operand::Copy(place), ..)
328 | Rvalue::Use(Operand::Move(place), ..)
329 | Rvalue::Cast(_, Operand::Copy(place), _)
330 | Rvalue::Cast(_, Operand::Move(place), _) => {
331 if place.local == current {
332 queue.push_back(assign.0.local);
333 seen.insert(assign.0.local);
334 }
335 }
336 _ => {}
337 }
338 }
339 }
340 }
341 if !reaches_ret { continue; }
342
343 let pointer_arg = trace_to_callee_arg(tcx, body, &args[0].node)?;
345 let size_arg = trace_to_callee_arg(tcx, body, &args[1].node)?;
346
347 let ret_ty = body.local_decls[ret].ty;
349 let elem_size = match ret_ty.kind() {
350 rustc_middle::ty::TyKind::Ref(_, inner, _) => match inner.kind() {
351 rustc_middle::ty::TyKind::Slice(elem) => {
352 let typing_env = rustc_middle::ty::TypingEnv::post_analysis(tcx, callee);
353 let input = rustc_middle::ty::PseudoCanonicalInput { typing_env, value: *elem };
354 crate::helpers::mir_utils::catch_panic(|| {
355 tcx.layout_of(input)
356 }).ok().and_then(|r| r.ok())
357 .map(|l| l.size.bytes())
358 .unwrap_or(1)
359 }
360 _ => 1,
361 },
362 _ => 1,
363 };
364
365 return Some(CallEffect::ReturnFreshAllocation {
366 pointer_arg,
367 size_arg,
368 elem_size,
369 });
370 }
371 None
372}
373
374pub(super) fn local_must_write_args(tcx: TyCtxt<'_>, callee: DefId) -> Option<Vec<usize>> {
377 callee.as_local()?;
378 if !tcx.is_mir_available(callee) {
379 return None;
380 }
381
382 helpers::catch_panic(|| {
383 let body = tcx.optimized_mir(callee);
384 let mut graph = PathGraph::new(tcx, callee);
385 graph.find_scc();
386 let mut enumerator = PathEnumerator::new(&graph);
387 let paths = enumerator.enumerate_paths_repeat(0);
388
389 let mut must_write: Option<HashSet<usize>> = None;
390 for path in paths.iter() {
391 if !path_ends_in_return(body, &path) {
392 continue;
393 }
394 let writes = write_args_on_path(tcx, body, &path);
395 must_write = Some(match must_write {
396 Some(current) => current.intersection(&writes).copied().collect(),
397 None => writes,
398 });
399 }
400
401 must_write
402 .unwrap_or_default()
403 .into_iter()
404 .collect::<Vec<_>>()
405 })
406 .ok()
407}
408
409pub(super) fn named_index_disjoint_validator(name: &str) -> Option<(usize, usize)> {
412 let base = name
413 .split('<')
414 .next()
415 .unwrap_or(name)
416 .trim_end_matches("::");
417 if base.ends_with("get_disjoint_check_valid")
418 || base.ends_with("get_disjoint_check_valid_ext") {
419 Some((0, 1))
420 } else {
421 None
422 }
423}
424
425pub(super) fn detect_index_disjoint_validator(tcx: TyCtxt<'_>, callee: DefId) -> Option<(usize, usize)> {
430 callee.as_local()?;
431 if !tcx.is_mir_available(callee) {
432 return None;
433 }
434 helpers::catch_panic(|| {
435 let body = tcx.optimized_mir(callee);
436 let arg_count = body.arg_count;
437 let mut elem_load_arg: HashSet<(Local, usize)> = HashSet::new();
438 let mut copy_of_arg: HashSet<(Local, usize)> = HashSet::new();
439
440 for bb in body.basic_blocks.iter() {
441 for stmt in &bb.statements {
442 let StatementKind::Assign(assign) = &stmt.kind else {
443 continue;
444 };
445 let (dest, rvalue) = &**assign;
446 if !dest.projection.is_empty() {
447 continue;
448 }
449 let Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..) = rvalue else {
450 continue;
451 };
452 let Some(arg) = helpers::arg_of_local(place.local, arg_count) else {
453 continue;
454 };
455 if place
456 .projection
457 .iter()
458 .any(|p| matches!(p, ProjectionElem::Index(_)))
459 {
460 elem_load_arg.insert((dest.local, arg));
461 } else if place.projection.is_empty() {
462 copy_of_arg.insert((dest.local, arg));
463 }
464 }
465 }
466
467 let elem_arg = |op: &Operand<'_>| -> Option<usize> {
468 let (Operand::Copy(p) | Operand::Move(p)) = op else {
469 return None;
470 };
471 if !p.projection.is_empty() {
472 return None;
473 }
474 elem_load_arg
475 .iter()
476 .find(|(l, _)| *l == p.local)
477 .map(|(_, a)| *a)
478 };
479 let scalar_arg = |op: &Operand<'_>| -> Option<usize> {
480 let (Operand::Copy(p) | Operand::Move(p)) = op else {
481 return None;
482 };
483 if !p.projection.is_empty() {
484 return None;
485 }
486 helpers::arg_of_local(p.local, arg_count).or_else(|| {
487 copy_of_arg
488 .iter()
489 .find(|(l, _)| *l == p.local)
490 .map(|(_, a)| *a)
491 })
492 };
493
494 let mut bounds: Option<(usize, usize)> = None;
495 let mut disjoint_arg: Option<usize> = None;
496 for bb in body.basic_blocks.iter() {
497 for stmt in &bb.statements {
498 let StatementKind::Assign(assign) = &stmt.kind else {
499 continue;
500 };
501 let (_, Rvalue::BinaryOp(op, pair)) = &**assign else {
502 continue;
503 };
504 let (a, b) = &**pair;
505 match op {
506 BinOp::Ge | BinOp::Gt | BinOp::Le | BinOp::Lt => {
507 if let (Some(idx), Some(len)) = (elem_arg(a), scalar_arg(b)) {
508 bounds = Some((idx, len));
509 } else if let (Some(idx), Some(len)) = (elem_arg(b), scalar_arg(a)) {
510 bounds = Some((idx, len));
511 }
512 }
513 BinOp::Eq | BinOp::Ne => {
514 if let (Some(x), Some(y)) = (elem_arg(a), elem_arg(b))
515 && x == y
516 {
517 disjoint_arg = Some(x);
518 }
519 }
520 _ => {}
521 }
522 }
523 }
524
525 match (bounds, disjoint_arg) {
526 (Some((idx, len)), Some(dj)) if dj == idx && idx != len => Some((idx, len)),
527 _ => None,
528 }
529 })
530 .ok()
531 .flatten()
532}
533fn path_ends_in_return(body: &rustc_middle::mir::Body<'_>, path: &[usize]) -> bool {
534 path.last().is_some_and(|block| {
535 body.basic_blocks
536 .get(BasicBlock::from_usize(*block))
537 .and_then(|data| data.terminator.as_ref())
538 .is_some_and(|terminator| matches!(terminator.kind, TerminatorKind::Return))
539 })
540}
541
542fn write_args_on_path<'tcx>(
543 tcx: TyCtxt<'tcx>,
544 body: &rustc_middle::mir::Body<'tcx>,
545 path: &[usize],
546) -> HashSet<usize> {
547 let mut writes = HashSet::new();
548 for block in path {
549 let Some(data) = body.basic_blocks.get(BasicBlock::from_usize(*block)) else {
550 continue;
551 };
552 let Some(terminator) = data.terminator.as_ref() else {
553 continue;
554 };
555 let TerminatorKind::Call { func, args, .. } = &terminator.kind else {
556 continue;
557 };
558 let name = helpers::call_name(tcx, func);
559 if !crate::helpers::api_classify::is_ptr_write(&name) {
560 continue;
561 }
562 if let Some(pointer_arg) = args
563 .first()
564 .and_then(|arg| trace_to_callee_arg(tcx, body, &arg.node))
565 {
566 writes.insert(pointer_arg);
567 }
568 }
569 writes
570}
571
572pub(super) fn callee_calls_other_local(tcx: TyCtxt<'_>, callee: DefId) -> bool {
575 let body = tcx.optimized_mir(callee);
576 for bb in body.basic_blocks.iter() {
577 if matches!(bb.terminator().kind,
578 rustc_middle::mir::TerminatorKind::Call { .. }
579 ) {
580 return true;
581 }
582 }
583 false
584}