diff --git a/Cargo.lock b/Cargo.lock index 1e8e743d5e56..4f371c8356e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -108,9 +109,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "camino" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -146,9 +147,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.15" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", @@ -175,7 +176,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -349,6 +350,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "getopts" version = "0.2.21" @@ -375,6 +382,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "graph-cycles" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6ad932c6dd3cfaf16b66754a42f87bbeefd591530c4b6a8334270a7df3e853" +dependencies = [ + "ahash", + "petgraph", + "thiserror", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -401,9 +419,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown", @@ -510,7 +528,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -532,9 +550,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.157" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" [[package]] name = "linear-map" @@ -723,6 +741,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -928,6 +956,8 @@ name = "scanner" version = "0.0.0" dependencies = [ "csv", + "graph-cycles", + "petgraph", "serde", "strum", "strum_macros", @@ -950,29 +980,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.206" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.206" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] name = "serde_json" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", "memchr", @@ -1072,7 +1102,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1087,9 +1117,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -1126,7 +1156,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1192,7 +1222,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1311,9 +1341,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" -version = "6.0.2" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", @@ -1467,5 +1497,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] diff --git a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs index 15593549b690..eff7dd1fc486 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs @@ -203,119 +203,108 @@ impl<'a, 'tcx> Analysis<'tcx> for PointsToAnalysis<'a, 'tcx> { }; match instance.def { // Intrinsics could introduce aliasing edges we care about, so need to handle them. - InstanceKind::Intrinsic(def_id) => { - // Check if the intrinsic has a body we can analyze. - if self.tcx.is_mir_available(def_id) { - self.apply_regular_call_effect(state, instance, args, destination); - } else { - // Check all of the other intrinsics. - match Intrinsic::from_instance(&rustc_internal::stable(instance)) { - intrinsic if is_identity_aliasing_intrinsic(intrinsic.clone()) => { - // Treat the intrinsic as an aggregate, taking a union of all of the - // arguments' aliases. - let destination_set = - state.resolve_place(*destination, self.instance); - let operands_set = args - .into_iter() - .flat_map(|operand| { - self.successors_for_operand(state, operand.node.clone()) - }) - .collect(); - state.extend(&destination_set, &operands_set); - } - // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. - // This is equivalent to `destination = *dst; *dst = src`. - Intrinsic::AtomicCxchg(_) | Intrinsic::AtomicCxchgWeak(_) => { - let src_set = - self.successors_for_operand(state, args[2].node.clone()); - let dst_set = - self.successors_for_deref(state, args[0].node.clone()); - let destination_set = - state.resolve_place(*destination, self.instance); - state.extend(&destination_set, &state.successors(&dst_set)); - state.extend(&dst_set, &src_set); - } - // All `atomic_load` intrinsics take `src` as an argument. - // This is equivalent to `destination = *src`. - Intrinsic::AtomicLoad(_) => { - let src_set = - self.successors_for_deref(state, args[0].node.clone()); - let destination_set = - state.resolve_place(*destination, self.instance); - state.extend(&destination_set, &state.successors(&src_set)); - } - // All `atomic_store` intrinsics take `dst, val` as arguments. - // This is equivalent to `*dst = val`. - Intrinsic::AtomicStore(_) => { - let dst_set = - self.successors_for_deref(state, args[0].node.clone()); - let val_set = - self.successors_for_operand(state, args[1].node.clone()); - state.extend(&dst_set, &val_set); - } - // All other `atomic` intrinsics take `dst, src` as arguments. - // This is equivalent to `destination = *dst; *dst = src`. - Intrinsic::AtomicAnd(_) - | Intrinsic::AtomicMax(_) - | Intrinsic::AtomicMin(_) - | Intrinsic::AtomicNand(_) - | Intrinsic::AtomicOr(_) - | Intrinsic::AtomicUmax(_) - | Intrinsic::AtomicUmin(_) - | Intrinsic::AtomicXadd(_) - | Intrinsic::AtomicXchg(_) - | Intrinsic::AtomicXor(_) - | Intrinsic::AtomicXsub(_) => { - let src_set = - self.successors_for_operand(state, args[1].node.clone()); - let dst_set = - self.successors_for_deref(state, args[0].node.clone()); - let destination_set = - state.resolve_place(*destination, self.instance); - state.extend(&destination_set, &state.successors(&dst_set)); - state.extend(&dst_set, &src_set); - } - // Similar to `copy_nonoverlapping`, argument order is `src`, `dst`, `count`. - Intrinsic::Copy => { - self.apply_copy_effect( - state, - args[0].node.clone(), - args[1].node.clone(), - ); - } - // Similar to `copy_nonoverlapping`, argument order is `dst`, `src`, `count`. - Intrinsic::VolatileCopyMemory - | Intrinsic::VolatileCopyNonOverlappingMemory => { - self.apply_copy_effect( - state, - args[1].node.clone(), - args[0].node.clone(), - ); - } - // Semantically equivalent to dest = *a - Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { - // Destination of the return value. - let lvalue_set = state.resolve_place(*destination, self.instance); - let rvalue_set = - self.successors_for_deref(state, args[0].node.clone()); - state.extend(&lvalue_set, &state.successors(&rvalue_set)); - } - // Semantically equivalent *a = b. - Intrinsic::VolatileStore => { - let lvalue_set = - self.successors_for_deref(state, args[0].node.clone()); - let rvalue_set = - self.successors_for_operand(state, args[1].node.clone()); - state.extend(&lvalue_set, &rvalue_set); - } - Intrinsic::Unimplemented { .. } => { - // This will be taken care of at the codegen level. - } - intrinsic => { - unimplemented!( - "Kani does not support reasoning about aliasing in presence of intrinsic `{intrinsic:?}`. For more information about the state of uninitialized memory checks implementation, see: https://github.com/model-checking/kani/issues/3300." - ); - } + InstanceKind::Intrinsic(_) => { + match Intrinsic::from_instance(&rustc_internal::stable(instance)) { + intrinsic if is_identity_aliasing_intrinsic(intrinsic.clone()) => { + // Treat the intrinsic as an aggregate, taking a union of all of the + // arguments' aliases. + let destination_set = state.resolve_place(*destination, self.instance); + let operands_set = args + .into_iter() + .flat_map(|operand| { + self.successors_for_operand(state, operand.node.clone()) + }) + .collect(); + state.extend(&destination_set, &operands_set); + } + // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. + // This is equivalent to `destination = *dst; *dst = src`. + Intrinsic::AtomicCxchg(_) | Intrinsic::AtomicCxchgWeak(_) => { + let src_set = self.successors_for_operand(state, args[2].node.clone()); + let dst_set = self.successors_for_deref(state, args[0].node.clone()); + let destination_set = state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&dst_set)); + state.extend(&dst_set, &src_set); + } + // All `atomic_load` intrinsics take `src` as an argument. + // This is equivalent to `destination = *src`. + Intrinsic::AtomicLoad(_) => { + let src_set = self.successors_for_deref(state, args[0].node.clone()); + let destination_set = state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&src_set)); + } + // All `atomic_store` intrinsics take `dst, val` as arguments. + // This is equivalent to `*dst = val`. + Intrinsic::AtomicStore(_) => { + let dst_set = self.successors_for_deref(state, args[0].node.clone()); + let val_set = self.successors_for_operand(state, args[1].node.clone()); + state.extend(&dst_set, &val_set); + } + // All other `atomic` intrinsics take `dst, src` as arguments. + // This is equivalent to `destination = *dst; *dst = src`. + Intrinsic::AtomicAnd(_) + | Intrinsic::AtomicMax(_) + | Intrinsic::AtomicMin(_) + | Intrinsic::AtomicNand(_) + | Intrinsic::AtomicOr(_) + | Intrinsic::AtomicUmax(_) + | Intrinsic::AtomicUmin(_) + | Intrinsic::AtomicXadd(_) + | Intrinsic::AtomicXchg(_) + | Intrinsic::AtomicXor(_) + | Intrinsic::AtomicXsub(_) => { + let src_set = self.successors_for_operand(state, args[1].node.clone()); + let dst_set = self.successors_for_deref(state, args[0].node.clone()); + let destination_set = state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&dst_set)); + state.extend(&dst_set, &src_set); + } + // Similar to `copy_nonoverlapping`, argument order is `src`, `dst`, `count`. + Intrinsic::Copy => { + self.apply_copy_effect( + state, + args[0].node.clone(), + args[1].node.clone(), + ); + } + Intrinsic::TypedSwap => { + // Extend from x_set to y_set and vice-versa so that both x and y alias + // to a union of places each of them alias to. + let x_set = self.successors_for_deref(state, args[0].node.clone()); + let y_set = self.successors_for_deref(state, args[1].node.clone()); + state.extend(&x_set, &state.successors(&y_set)); + state.extend(&y_set, &state.successors(&x_set)); + } + // Similar to `copy_nonoverlapping`, argument order is `dst`, `src`, `count`. + Intrinsic::VolatileCopyMemory + | Intrinsic::VolatileCopyNonOverlappingMemory => { + self.apply_copy_effect( + state, + args[1].node.clone(), + args[0].node.clone(), + ); + } + // Semantically equivalent to dest = *a + Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { + // Destination of the return value. + let lvalue_set = state.resolve_place(*destination, self.instance); + let rvalue_set = self.successors_for_deref(state, args[0].node.clone()); + state.extend(&lvalue_set, &state.successors(&rvalue_set)); + } + // Semantically equivalent *a = b. + Intrinsic::VolatileStore => { + let lvalue_set = self.successors_for_deref(state, args[0].node.clone()); + let rvalue_set = + self.successors_for_operand(state, args[1].node.clone()); + state.extend(&lvalue_set, &rvalue_set); + } + Intrinsic::Unimplemented { .. } => { + // This will be taken care of at the codegen level. + } + intrinsic => { + unimplemented!( + "Kani does not support reasoning about aliasing in presence of intrinsic `{intrinsic:?}`. For more information about the state of uninitialized memory checks implementation, see: https://github.com/model-checking/kani/issues/3300." + ); } } } @@ -681,6 +670,7 @@ fn is_identity_aliasing_intrinsic(intrinsic: Intrinsic) -> bool { | Intrinsic::PtrOffsetFrom | Intrinsic::PtrOffsetFromUnsigned | Intrinsic::RawEq + | Intrinsic::RetagBoxToRaw | Intrinsic::RintF32 | Intrinsic::RintF64 | Intrinsic::RotateLeft @@ -695,6 +685,7 @@ fn is_identity_aliasing_intrinsic(intrinsic: Intrinsic) -> bool { | Intrinsic::SqrtF32 | Intrinsic::SqrtF64 | Intrinsic::SubWithOverflow + | Intrinsic::Transmute | Intrinsic::TruncF32 | Intrinsic::TruncF64 | Intrinsic::TypeId diff --git a/tests/expected/uninit/intrinsics/expected b/tests/expected/uninit/intrinsics/expected index c6d61b93add9..b5555785e8af 100644 --- a/tests/expected/uninit/intrinsics/expected +++ b/tests/expected/uninit/intrinsics/expected @@ -2,18 +2,10 @@ std::ptr::write::>.assertion.1\ - Status: FAILURE\ - Description: "Interaction between raw pointers and unions is not yet supported." -std::ptr::write::>.assertion.2\ - - Status: FAILURE\ - - Description: "Interaction between raw pointers and unions is not yet supported." - std::ptr::write::>.assertion.1\ - Status: FAILURE\ - Description: "Interaction between raw pointers and unions is not yet supported."\ -std::ptr::write::>.assertion.2\ - - Status: FAILURE\ - - Description: "Interaction between raw pointers and unions is not yet supported." - check_typed_swap.assertion.1\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*mut u8`" diff --git a/tests/perf/s2n-quic b/tests/perf/s2n-quic index ab9723a772f0..80b93a7f1d18 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit ab9723a772f03a9793c9863e73c9a48fab3c5235 +Subproject commit 80b93a7f1d187fef005c8c896ab99278b6865dbe diff --git a/tests/script-based-pre/tool-scanner/scanner-test.expected b/tests/script-based-pre/tool-scanner/scanner-test.expected index c8f9af0ef1b7..f55a883434ee 100644 --- a/tests/script-based-pre/tool-scanner/scanner-test.expected +++ b/tests/script-based-pre/tool-scanner/scanner-test.expected @@ -1,6 +1,6 @@ -2 test_scan_fn_loops.csv -16 test_scan_functions.csv +5 test_scan_fn_loops.csv +19 test_scan_functions.csv 5 test_scan_input_tys.csv -14 test_scan_overall.csv +16 test_scan_overall.csv 3 test_scan_recursion.csv 5 test_scan_unsafe_ops.csv diff --git a/tests/script-based-pre/tool-scanner/test.rs b/tests/script-based-pre/tool-scanner/test.rs index 24b346e535b5..f6a141f2a708 100644 --- a/tests/script-based-pre/tool-scanner/test.rs +++ b/tests/script-based-pre/tool-scanner/test.rs @@ -33,7 +33,34 @@ pub fn with_iterator(input: &[usize]) -> usize { .iter() .copied() .find(|e| *e == 0) - .unwrap_or_else(|| input.iter().fold(0, |acc, i| acc + 1)) + .unwrap_or_else(|| input.iter().fold(0, |acc, _| acc + 1)) +} + +pub fn with_for_loop(input: &[usize]) -> usize { + let mut res = 0; + for _ in input { + res += 1; + } + res +} + +pub fn with_while_loop(input: &[usize]) -> usize { + let mut res = 0; + while res < input.len() { + res += 1; + } + return res; +} + +pub fn with_loop_loop(input: &[usize]) -> usize { + let mut res = 0; + loop { + if res == input.len() { + break; + } + res += 1; + } + res } static mut COUNTER: Option = Some(0); diff --git a/tools/scanner/Cargo.toml b/tools/scanner/Cargo.toml index edbd330bea47..f27e9e06c72c 100644 --- a/tools/scanner/Cargo.toml +++ b/tools/scanner/Cargo.toml @@ -15,6 +15,8 @@ csv = "1.3" serde = {version = "1", features = ["derive"]} strum = "0.26" strum_macros = "0.26" +petgraph = "0.6.5" +graph-cycles = "0.1.0" [package.metadata.rust-analyzer] # This crate uses rustc crates. diff --git a/tools/scanner/src/analysis.rs b/tools/scanner/src/analysis.rs index c376af9662f8..b32627939bf5 100644 --- a/tools/scanner/src/analysis.rs +++ b/tools/scanner/src/analysis.rs @@ -5,11 +5,13 @@ use crate::info; use csv::WriterBuilder; +use graph_cycles::Cycles; +use petgraph::graph::Graph; use serde::{ser::SerializeStruct, Serialize, Serializer}; use stable_mir::mir::mono::Instance; use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef}; use stable_mir::mir::{ - Body, MirVisitor, Mutability, ProjectionElem, Safety, Terminator, TerminatorKind, + BasicBlock, Body, MirVisitor, Mutability, ProjectionElem, Safety, Terminator, TerminatorKind, }; use stable_mir::ty::{AdtDef, AdtKind, FnDef, GenericArgs, MirConst, RigidTy, Ty, TyKind}; use stable_mir::visitor::{Visitable, Visitor}; @@ -159,6 +161,7 @@ impl OverallStats { pub fn loops(&mut self, filename: PathBuf) { let all_items = stable_mir::all_local_items(); let (has_loops, no_loops) = all_items + .clone() .into_iter() .filter_map(|item| { let kind = item.ty().kind(); @@ -168,9 +171,37 @@ impl OverallStats { Some(FnLoops::new(item.name()).collect(&item.body())) }) .partition::, _>(|props| props.has_loops()); - self.counters - .extend_from_slice(&[("has_loops", has_loops.len()), ("no_loops", no_loops.len())]); - dump_csv(filename, &has_loops); + + let (has_iterators, no_iterators) = all_items + .clone() + .into_iter() + .filter_map(|item| { + let kind = item.ty().kind(); + if !kind.is_fn() { + return None; + }; + Some(FnLoops::new(item.name()).collect(&item.body())) + }) + .partition::, _>(|props| props.has_iterators()); + + let (has_either, _) = all_items + .into_iter() + .filter_map(|item| { + let kind = item.ty().kind(); + if !kind.is_fn() { + return None; + }; + Some(FnLoops::new(item.name()).collect(&item.body())) + }) + .partition::, _>(|props| props.has_iterators() || props.has_loops()); + + self.counters.extend_from_slice(&[ + ("has_loops", has_loops.len()), + ("no_loops", no_loops.len()), + ("has_iterators", has_iterators.len()), + ("no_iterators", no_iterators.len()), + ]); + dump_csv(filename, &has_either); } /// Create a callgraph for this crate and try to find recursive calls. @@ -436,21 +467,26 @@ impl<'a> MirVisitor for BodyVisitor<'a> { fn_props! { struct FnLoops { iterators, - nested_loops, - /// TODO: Collect loops. loops, + // TODO: Collect nested loops. + nested_loops, } } impl FnLoops { pub fn collect(self, body: &Body) -> FnLoops { - let mut visitor = IteratorVisitor { props: self, body }; + let mut visitor = + IteratorVisitor { props: self, body, graph: Vec::new(), current_bbidx: 0 }; visitor.visit_body(body); visitor.props } pub fn has_loops(&self) -> bool { - (self.iterators + self.loops + self.nested_loops) > 0 + (self.loops + self.nested_loops) > 0 + } + + pub fn has_iterators(&self) -> bool { + (self.iterators) > 0 } } @@ -461,12 +497,36 @@ impl FnLoops { struct IteratorVisitor<'a> { props: FnLoops, body: &'a Body, + graph: Vec<(u32, u32)>, + current_bbidx: u32, } impl<'a> MirVisitor for IteratorVisitor<'a> { + fn visit_body(&mut self, body: &Body) { + // First visit the body to build the control flow graph + self.super_body(body); + // Build the petgraph from the adj vec + let g = Graph::<(), ()>::from_edges(self.graph.clone()); + self.props.loops += g.cycles().len(); + } + + fn visit_basic_block(&mut self, bb: &BasicBlock) { + self.current_bbidx = self.body.blocks.iter().position(|b| *b == *bb).unwrap() as u32; + self.super_basic_block(bb); + } + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + // Add edges between basic block into the adj table + let successors = term.kind.successors(); + for target in successors { + self.graph.push((self.current_bbidx, target as u32)); + } + if let TerminatorKind::Call { func, .. } = &term.kind { let kind = func.ty(self.body.locals()).unwrap().kind(); + // Check if the target is a visited block. + + // Check if the call is an iterator function that contains loops. if let TyKind::RigidTy(RigidTy::FnDef(def, _)) = kind { let fullname = def.name(); let names = fullname.split("::").collect::>(); @@ -505,6 +565,7 @@ impl<'a> MirVisitor for IteratorVisitor<'a> { } } } + self.super_terminator(term, location) } }