From bd2d8d5d5fb03ea778675ea3e65820422bb52b30 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 30 Nov 2023 11:14:45 -0800 Subject: [PATCH] Change reachability module to use StableMIR (#2894) Rewrite the reachability module to use Stable APIs wherever possible. Note that in StableMIR the instance body is already monomorphized and constants are already evaluated, which simplifies the code for most of it, except to handle stubbing issue #2589. For the stubbing issue, we still use a mix of stable and internal APIs to detect an invalid monomorphization. Co-authored-by: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> --- docs/src/dev-documentation.md | 1 + docs/src/stable_mir.md | 94 +++ kani-compiler/src/kani_compiler.rs | 10 +- kani-compiler/src/kani_middle/provide.rs | 15 +- kani-compiler/src/kani_middle/reachability.rs | 623 +++++++----------- kani-compiler/src/kani_middle/stubbing/mod.rs | 91 ++- .../harness/expected | 2 +- 7 files changed, 442 insertions(+), 394 deletions(-) create mode 100644 docs/src/stable_mir.md diff --git a/docs/src/dev-documentation.md b/docs/src/dev-documentation.md index 639b7125ae78..173588586a3a 100644 --- a/docs/src/dev-documentation.md +++ b/docs/src/dev-documentation.md @@ -14,6 +14,7 @@ developers (including external contributors): 3. [Development setup recommendations for working with `cbmc`](./cbmc-hacks.md). 4. [Development setup recommendations for working with `rustc`](./rustc-hacks.md). 5. [Guide for testing in Kani](./testing.md). + 6. [Transition to StableMIR](./stable_mir.md). > **NOTE**: The developer documentation is intended for Kani developers and not users. At present, the project is under heavy development and some items diff --git a/docs/src/stable_mir.md b/docs/src/stable_mir.md new file mode 100644 index 000000000000..79e63a98cba1 --- /dev/null +++ b/docs/src/stable_mir.md @@ -0,0 +1,94 @@ +# Transition to StableMIR + +We have partnered with the Rust compiler team in the initiative to introduce stable +APIs to the compiler that can be used by third-party tools, which is known as the +[Stable MIR Project](https://github.com/rust-lang/project-stable-mir), or just StableMIR. +This means that we are starting to use the new APIs introduced by this project as is, +despite them not being stable yet. + +### StableMIR APIs + +For now, the StableMIR APIs are exposed as a crate in the compiler named `stable_mir`. +This crate includes the definition of structures and methods to be stabilized, +which are expected to become the stable APIs in the compiler. +To reduce the migration burden, these APIs are somewhat close to the original compiler interfaces. +However, some changes have been made to make these APIs cleaner and easier to use. + +For example: +1. The usage of the compiler context (aka `TyCtxt`) is transparent to the user. + The StableMIR implementation caches this context in a thread local variable, + and retrieves it whenever necessary. + - Because of that, code that uses the StableMIR has to be invoked inside a `run` call. +2. The `DefId` has been specialized into multiple types, + making its usage less error prone. E.g.: + `FnDef` represents the definition of a function, + while `StaticDef` is the definition of a static variable. + - Note that the same `DefId` may be mapped to different definitions according to its context. + For example, an `InstanceDef` and a `FnDef` may represent the same function definition. +3. Methods that used to be exposed as part of `TyCtxt` are now part of a type. + Example, the function `TyCtxt.instance_mir` is now `Instance::body`. +4. There is no need for explicit instantiation (monomorphization) of items from an`Instance::body`. + This method already instantiates all types and resolves all constants before converting + it to stable APIs. + + +### Performance + +Since the new APIs require converting internal data to a stable representation, +the APIs were also designed to avoid needless conversions, +and to allow extra information to be retrieved on demand. + +For example, `Ty` is just an identifier, while `TyKind` is a structure that can be retrieved via `Ty::kind` method. +The `TyKind` is a more structured object, thus, +it is only generated when the `kind` method is invoked. +Since this translation is not cached, +many of the functions that the rust compiler used to expose in `Ty`, +is now only part of `TyKind`. +The reason being that there is no cache for the `TyKind`, +and users should do the caching themselves to avoid needless translations. + +From our initial experiments with the transition of the reachability algorithm to use StableMIR, +there is a small penalty of using StableMIR over internal rust compiler APIs. +However, they are still fairly efficient and it did not impact the overall compilation time. + +### Interface with internal APIs + +To reduce the burden of migrating to StableMIR, +and to allow StableMIR to be used together with internal APIs, +there are two helpful methods to convert StableMIR constructs to internal rustc and back: + - `rustc_internal::internal()`: Convert a Stable item into an internal one. + - `rustc_internal::stable()`: Convert an internal item into a Stable one. + +Both of these methods are inside `rustc_smir` crate in the `rustc_internal` +module inside the compiler. +Note that there is no plan to stabilize any of these methods, +and there's also no guarantee on its support and coverage. + +The conversion is not implemented for all items, and some conversions may be incomplete. +Please proceed with caution when using these methods. + +Besides that, do not invoke any other `rustc_smir` methods, except for `run`. +This crate's methods are not meant to be invoked externally. +Note that, the method `run` will also eventually be replaced by a Stable driver. + +### Creating and modifying StableMIR items + +For now, StableMIR should only be used to get information from the compiler. +Do not try to create or modify items directly, as it may not work. +This may result in incorrect behavior or an internal compiler error (ICE). + +## Naming conventions in Kani + +As we adopt StableMIR, we would like to introduce a few conventions to make it easier to maintain the code. +Whenever there is a name conflict, for example, `Ty` or `codegen_ty`, +use a suffix to indicate which API you are using. +`Stable` for StableMIR and `Internal` for `rustc` internal APIs. + +A module should either default its naming to Stable APIs or Internal APIs. +I.e.: Modules that have been migrated to StableMIR don't need to add the `Stable` suffix to stable items. +While those that haven't been migrated, should add `Stable`, but no `Internal` is needed. + +For example, the `codegen::typ` module will likely include methods: + +`codegen_ty(&mut self, Ty)` and `codegen_ty_stable(&mut, TyStable)` to handle +internal and stable APIs. diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index 56cd02ce542d..fc04b68ec747 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -34,6 +34,7 @@ use rustc_hir::definitions::DefPathHash; use rustc_interface::Config; use rustc_middle::ty::TyCtxt; use rustc_session::config::{ErrorOutputType, OutputType}; +use rustc_smir::rustc_internal; use rustc_span::ErrorGuaranteed; use std::collections::{BTreeMap, HashMap}; use std::fs::File; @@ -400,9 +401,12 @@ impl Callbacks for KaniCompiler { ) -> Compilation { if self.stage.is_init() { self.stage = rustc_queries.global_ctxt().unwrap().enter(|tcx| { - check_crate_items(tcx, self.queries.lock().unwrap().args().ignore_global_asm); - self.process_harnesses(tcx) - }); + rustc_internal::run(tcx, || { + check_crate_items(tcx, self.queries.lock().unwrap().args().ignore_global_asm); + self.process_harnesses(tcx) + }) + .unwrap() + }) } self.prepare_codegen() diff --git a/kani-compiler/src/kani_middle/provide.rs b/kani-compiler/src/kani_middle/provide.rs index 033eae3b460f..3c7664d076d6 100644 --- a/kani-compiler/src/kani_middle/provide.rs +++ b/kani-compiler/src/kani_middle/provide.rs @@ -76,11 +76,14 @@ fn collect_and_partition_mono_items( tcx: TyCtxt, key: (), ) -> queries::collect_and_partition_mono_items::ProvidedValue { - let entry_fn = tcx.entry_fn(()).map(|(id, _)| id); - let local_reachable = filter_crate_items(tcx, |_, def_id| { - tcx.is_reachable_non_generic(def_id) || entry_fn == Some(def_id) - }); - // We do not actually need the value returned here. - collect_reachable_items(tcx, &local_reachable); + rustc_smir::rustc_internal::run(tcx, || { + let entry_fn = tcx.entry_fn(()).map(|(id, _)| id); + let local_reachable = filter_crate_items(tcx, |_, def_id| { + tcx.is_reachable_non_generic(def_id) || entry_fn == Some(def_id) + }); + // We do not actually need the value returned here. + collect_reachable_items(tcx, &local_reachable); + }) + .unwrap(); (rustc_interface::DEFAULT_QUERY_PROVIDERS.collect_and_partition_mono_items)(tcx, key) } diff --git a/kani-compiler/src/kani_middle/reachability.rs b/kani-compiler/src/kani_middle/reachability.rs index 35ba3d6229f4..d56b7b5db65d 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -13,42 +13,43 @@ //! - For every static, collect initializer and drop functions. //! //! We have kept this module agnostic of any Kani code in case we can contribute this back to rustc. -use rustc_span::ErrorGuaranteed; -use tracing::{debug, debug_span, trace, warn}; +//! +//! Note that this is a copy of `reachability.rs` that uses StableMIR but the public APIs are still +//! kept with internal APIs. +use tracing::{debug, debug_span, trace}; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; -use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; -use rustc_hir::ItemId; -use rustc_middle::mir::interpret::{AllocId, ErrorHandled, GlobalAlloc, Scalar}; -use rustc_middle::mir::mono::MonoItem; -use rustc_middle::mir::visit::Visitor as MirVisitor; -use rustc_middle::mir::{ - Body, CastKind, Const, ConstOperand, ConstValue, Location, Rvalue, Terminator, TerminatorKind, - UnevaluatedConst, -}; -use rustc_middle::span_bug; -use rustc_middle::ty::adjustment::PointerCoercion; -use rustc_middle::ty::{ - Closure, ClosureKind, ConstKind, EarlyBinder, Instance, InstanceDef, ParamEnv, Ty, TyCtxt, - TyKind, TypeFoldable, VtblEntry, +use rustc_middle::mir::mono::MonoItem as InternalMonoItem; +use rustc_middle::ty::{TyCtxt, VtblEntry}; +use rustc_smir::rustc_internal; +use stable_mir::mir::alloc::{AllocId, GlobalAlloc}; +use stable_mir::mir::mono::{Instance, InstanceKind, MonoItem, StaticDef}; +use stable_mir::mir::pretty::pretty_ty; +use stable_mir::mir::{ + visit::Location, Body, CastKind, Constant, MirVisitor, PointerCoercion, Rvalue, Terminator, + TerminatorKind, }; +use stable_mir::ty::{Allocation, ClosureKind, ConstantKind, RigidTy, Ty, TyKind}; +use stable_mir::CrateDef; +use stable_mir::{self, CrateItem}; use crate::kani_middle::coercion; -use crate::kani_middle::stubbing::get_stub; +use crate::kani_middle::coercion::CoercionBase; +use crate::kani_middle::stubbing::{get_stub, validate_instance}; /// Collect all reachable items starting from the given starting points. pub fn collect_reachable_items<'tcx>( tcx: TyCtxt<'tcx>, - starting_points: &[MonoItem<'tcx>], -) -> Vec> { + starting_points: &[InternalMonoItem<'tcx>], +) -> Vec> { // For each harness, collect items using the same collector. // I.e.: This will return any item that is reachable from one or more of the starting points. let mut collector = MonoItemsCollector::new(tcx); for item in starting_points { - collector.collect(*item); + collector.collect(rustc_internal::stable(item)); } #[cfg(debug_assertions)] @@ -58,99 +59,76 @@ pub fn collect_reachable_items<'tcx>( .unwrap_or_else(|e| tracing::error!("Failed to dump call graph: {e}")); tcx.sess.abort_if_errors(); - // Sort the result so code generation follows deterministic order. // This helps us to debug the code, but it also provides the user a good experience since the // order of the errors and warnings is stable. - let mut sorted_items: Vec<_> = collector.collected.into_iter().collect(); + let mut sorted_items: Vec<_> = + collector.collected.iter().map(rustc_internal::internal).collect(); sorted_items.sort_by_cached_key(|item| to_fingerprint(tcx, item)); sorted_items } /// Collect all (top-level) items in the crate that matches the given predicate. /// An item can only be a root if they are: non-generic Fn / Static / GlobalASM -pub fn filter_crate_items(tcx: TyCtxt, predicate: F) -> Vec +pub fn filter_crate_items(tcx: TyCtxt, predicate: F) -> Vec where F: Fn(TyCtxt, DefId) -> bool, { - let crate_items = tcx.hir_crate_items(()); + let crate_items = stable_mir::all_local_items(); // Filter regular items. - let root_items = crate_items.items().filter_map(|item| { - let def_id = item.owner_id.def_id.to_def_id(); - if !is_generic(tcx, def_id) && predicate(tcx, def_id) { - to_mono_root(tcx, item, def_id) - } else { - None - } - }); - - // Filter items from implementation blocks. - let impl_items = crate_items.impl_items().filter_map(|impl_item| { - let def_id = impl_item.owner_id.def_id.to_def_id(); - if matches!(tcx.def_kind(def_id), DefKind::AssocFn) - && !is_generic(tcx, def_id) - && predicate(tcx, def_id) - { - Some(MonoItem::Fn(Instance::mono(tcx, def_id))) - } else { - None - } - }); - root_items.chain(impl_items).collect() + crate_items + .iter() + .filter_map(|item| { + // Only collect monomorphic items. + Instance::try_from(*item).ok().and_then(|instance| { + let def_id = rustc_internal::internal(item); + predicate(tcx, def_id) + .then_some(InternalMonoItem::Fn(rustc_internal::internal(&instance))) + }) + }) + .collect::>() } /// Use a predicate to find `const` declarations, then extract all items reachable from them. /// /// Probably only specifically useful with a predicate to find `TestDescAndFn` const declarations from /// tests and extract the closures from them. -pub fn filter_const_crate_items(tcx: TyCtxt, mut predicate: F) -> Vec +pub fn filter_const_crate_items(tcx: TyCtxt, mut predicate: F) -> Vec where F: FnMut(TyCtxt, DefId) -> bool, { + let crate_items = stable_mir::all_local_items(); let mut roots = Vec::new(); - for hir_id in tcx.hir_crate_items(()).items() { - let def_id = hir_id.owner_id.def_id.to_def_id(); - let def_kind = tcx.def_kind(def_id); - if matches!(def_kind, DefKind::Const) && predicate(tcx, def_id) { - let instance = Instance::mono(tcx, def_id); - let body = tcx.instance_mir(InstanceDef::Item(def_id)); - let mut collector = - MonoItemsFnCollector { tcx, body, instance, collected: FxHashSet::default() }; - collector.visit_body(body); - - roots.extend(collector.collected); + // Filter regular items. + for item in crate_items { + // Only collect monomorphic items. + if let Ok(instance) = Instance::try_from(item) { + let def_id = rustc_internal::internal(&item); + if predicate(tcx, def_id) { + let body = instance.body().unwrap(); + let mut collector = MonoItemsFnCollector { + tcx, + body: &body, + collected: FxHashSet::default(), + instance: &instance, + }; + collector.visit_body(&body); + roots.extend(collector.collected.iter().map(rustc_internal::internal)); + } } } roots } -fn is_generic(tcx: TyCtxt, def_id: DefId) -> bool { - let generics = tcx.generics_of(def_id); - generics.requires_monomorphization(tcx) -} - -fn to_mono_root(tcx: TyCtxt, item_id: ItemId, def_id: DefId) -> Option { - let kind = tcx.def_kind(def_id); - match kind { - DefKind::Static(..) => Some(MonoItem::Static(def_id)), - DefKind::Fn => Some(MonoItem::Fn(Instance::mono(tcx, def_id))), - DefKind::GlobalAsm => Some(MonoItem::GlobalAsm(item_id)), - _ => { - debug!(?def_id, ?kind, "Ignored item. Not a root type."); - None - } - } -} - struct MonoItemsCollector<'tcx> { /// The compiler context. tcx: TyCtxt<'tcx>, /// Set of collected items used to avoid entering recursion loops. - collected: FxHashSet>, + collected: FxHashSet, /// Items enqueued for visiting. - queue: Vec>, + queue: Vec, #[cfg(debug_assertions)] - call_graph: debug::CallGraph<'tcx>, + call_graph: debug::CallGraph, } impl<'tcx> MonoItemsCollector<'tcx> { @@ -163,8 +141,9 @@ impl<'tcx> MonoItemsCollector<'tcx> { call_graph: debug::CallGraph::default(), } } + /// Collects all reachable items starting from the given root. - pub fn collect(&mut self, root: MonoItem<'tcx>) { + pub fn collect(&mut self, root: MonoItem) { debug!(?root, "collect"); self.queue.push(root); self.reachable_items(); @@ -175,12 +154,12 @@ impl<'tcx> MonoItemsCollector<'tcx> { fn reachable_items(&mut self) { while let Some(to_visit) = self.queue.pop() { if !self.collected.contains(&to_visit) { - self.collected.insert(to_visit); - let next_items = match to_visit { - MonoItem::Fn(instance) => self.visit_fn(instance), - MonoItem::Static(def_id) => self.visit_static(def_id), + self.collected.insert(to_visit.clone()); + let next_items = match &to_visit { + MonoItem::Fn(instance) => self.visit_fn(*instance), + MonoItem::Static(static_def) => self.visit_static(*static_def), MonoItem::GlobalAsm(_) => { - self.visit_asm(to_visit); + self.visit_asm(&to_visit); vec![] } }; @@ -194,146 +173,117 @@ impl<'tcx> MonoItemsCollector<'tcx> { } /// Visit a function and collect all mono-items reachable from its instructions. - fn visit_fn(&mut self, instance: Instance<'tcx>) -> Vec> { + fn visit_fn(&mut self, instance: Instance) -> Vec { let _guard = debug_span!("visit_fn", function=?instance).entered(); - let body = self.tcx.instance_mir(instance.def); - let mut collector = - MonoItemsFnCollector { tcx: self.tcx, collected: FxHashSet::default(), instance, body }; - collector.visit_body(body); - collector.collected.into_iter().collect() + if validate_instance(self.tcx, instance) { + let body = instance.body().unwrap(); + let mut collector = MonoItemsFnCollector { + tcx: self.tcx, + collected: FxHashSet::default(), + body: &body, + instance: &instance, + }; + collector.visit_body(&body); + collector.collected.into_iter().collect() + } else { + vec![] + } } /// Visit a static object and collect drop / initialization functions. - fn visit_static(&mut self, def_id: DefId) -> Vec> { - let _guard = debug_span!("visit_static", ?def_id).entered(); - let instance = Instance::mono(self.tcx, def_id); + fn visit_static(&mut self, def: StaticDef) -> Vec { + let _guard = debug_span!("visit_static", ?def).entered(); let mut next_items = vec![]; // Collect drop function. - let static_ty = instance.ty(self.tcx, ParamEnv::reveal_all()); - let instance = Instance::resolve_drop_in_place(self.tcx, static_ty); - next_items.push(MonoItem::Fn(instance.polymorphize(self.tcx))); + let static_ty = def.ty(); + let instance = Instance::resolve_drop_in_place(static_ty); + next_items.push(instance.into()); // Collect initialization. - let alloc = self.tcx.eval_static_initializer(def_id).unwrap(); - for id in alloc.inner().provenance().provenances() { - next_items.extend(collect_alloc_items(self.tcx, id).iter()); + let alloc = def.eval_initializer().unwrap(); + for (_, prov) in alloc.provenance.ptrs { + next_items.extend(collect_alloc_items(prov.0).into_iter()); } next_items } /// Visit global assembly and collect its item. - fn visit_asm(&mut self, item: MonoItem<'tcx>) { + fn visit_asm(&mut self, item: &MonoItem) { debug!(?item, "visit_asm"); } } struct MonoItemsFnCollector<'a, 'tcx> { tcx: TyCtxt<'tcx>, - collected: FxHashSet>, - instance: Instance<'tcx>, - body: &'a Body<'tcx>, + collected: FxHashSet, + body: &'a Body, + instance: &'a Instance, } impl<'a, 'tcx> MonoItemsFnCollector<'a, 'tcx> { - fn monomorphize(&self, value: T) -> T - where - T: TypeFoldable>, - { - trace!(instance=?self.instance, ?value, "monomorphize"); - self.instance.instantiate_mir_and_normalize_erasing_regions( - self.tcx, - ParamEnv::reveal_all(), - EarlyBinder::bind(value), - ) - } - /// Collect the implementation of all trait methods and its supertrait methods for the given /// concrete type. - fn collect_vtable_methods(&mut self, concrete_ty: Ty<'tcx>, trait_ty: Ty<'tcx>) { + fn collect_vtable_methods(&mut self, concrete_ty: Ty, trait_ty: Ty) { trace!(?concrete_ty, ?trait_ty, "collect_vtable_methods"); - assert!(!concrete_ty.is_trait(), "Expected a concrete type, but found: {concrete_ty:?}"); - assert!(trait_ty.is_trait(), "Expected a trait: {trait_ty:?}"); - if let TyKind::Dynamic(trait_list, ..) = trait_ty.kind() { + let concrete_kind = concrete_ty.kind(); + let trait_kind = trait_ty.kind(); + + assert!(!concrete_kind.is_trait(), "expected a concrete type, but found `{concrete_ty:?}`"); + assert!(trait_kind.is_trait(), "expected a trait `{trait_ty:?}`"); + if let Some(principal) = trait_kind.trait_principal() { // A trait object type can have multiple trait bounds but up to one non-auto-trait // bound. This non-auto-trait, named principal, is the only one that can have methods. // https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits - if let Some(principal) = trait_list.principal() { - let poly_trait_ref = principal.with_self_ty(self.tcx, concrete_ty); - - // Walk all methods of the trait, including those of its supertraits - let entries = self.tcx.vtable_entries(poly_trait_ref); - let methods = entries.iter().filter_map(|entry| match entry { - VtblEntry::MetadataAlign - | VtblEntry::MetadataDropInPlace - | VtblEntry::MetadataSize - | VtblEntry::Vacant => None, - VtblEntry::TraitVPtr(_) => { - // all super trait items already covered, so skip them. - None - } - VtblEntry::Method(instance) if should_codegen_locally(self.tcx, instance) => { - Some(MonoItem::Fn(instance.polymorphize(self.tcx))) - } - VtblEntry::Method(..) => None, - }); - trace!(methods=?methods.clone().collect::>(), "collect_vtable_methods"); - self.collected.extend(methods); - } + let poly_trait_ref = principal.with_self_ty(concrete_ty); + + // Walk all methods of the trait, including those of its supertraits + let entries = self.tcx.vtable_entries(rustc_internal::internal(&poly_trait_ref)); + let methods = entries.iter().filter_map(|entry| match entry { + VtblEntry::MetadataAlign + | VtblEntry::MetadataDropInPlace + | VtblEntry::MetadataSize + | VtblEntry::Vacant => None, + VtblEntry::TraitVPtr(_) => { + // all super trait items already covered, so skip them. + None + } + VtblEntry::Method(instance) => { + let instance = rustc_internal::stable(instance); + should_codegen_locally(&instance).then_some(MonoItem::Fn(instance)) + } + }); + trace!(methods=?methods.clone().collect::>(), "collect_vtable_methods"); + self.collected.extend(methods); } // Add the destructor for the concrete type. - let instance = Instance::resolve_drop_in_place(self.tcx, concrete_ty); + let instance = Instance::resolve_drop_in_place(concrete_ty); self.collect_instance(instance, false); } /// Collect an instance depending on how it is used (invoked directly or via fn_ptr). - fn collect_instance(&mut self, instance: Instance<'tcx>, is_direct_call: bool) { - let should_collect = match instance.def { - InstanceDef::Virtual(..) | InstanceDef::Intrinsic(_) => { + fn collect_instance(&mut self, instance: Instance, is_direct_call: bool) { + let should_collect = match instance.kind { + InstanceKind::Virtual | InstanceKind::Intrinsic => { // Instance definition has no body. assert!(is_direct_call, "Expected direct call {instance:?}"); false } - InstanceDef::DropGlue(_, None) => { - // Only need the glue if we are not calling it directly. - !is_direct_call - } - InstanceDef::CloneShim(..) - | InstanceDef::ClosureOnceShim { .. } - | InstanceDef::DropGlue(_, Some(_)) - | InstanceDef::FnPtrShim(..) - | InstanceDef::Item(..) - | InstanceDef::ReifyShim(..) - | InstanceDef::VTableShim(..) => true, - InstanceDef::ThreadLocalShim(_) | InstanceDef::FnPtrAddrShim(_, _) => true, + InstanceKind::Shim | InstanceKind::Item => true, }; - if should_collect && should_codegen_locally(self.tcx, &instance) { + if should_collect && should_codegen_locally(&instance) { trace!(?instance, "collect_instance"); - self.collected.insert(MonoItem::Fn(instance.polymorphize(self.tcx))); + self.collected.insert(instance.into()); } } /// Collect constant values represented by static variables. - fn collect_const_value(&mut self, value: ConstValue<'tcx>) { - debug!(?value, "collect_const_value"); - match value { - ConstValue::Scalar(Scalar::Ptr(ptr, _size)) => { - self.collected.extend(collect_alloc_items(self.tcx, ptr.provenance).iter()); - } - ConstValue::Slice { data: alloc, .. } => { - for id in alloc.inner().provenance().provenances() { - self.collected.extend(collect_alloc_items(self.tcx, id).iter()) - } - } - ConstValue::Indirect { alloc_id, .. } => { - let alloc = self.tcx.global_alloc(alloc_id).unwrap_memory(); - for id in alloc.inner().provenance().provenances() { - self.collected.extend(collect_alloc_items(self.tcx, id).iter()) - } - } - _ => {} + fn collect_allocation(&mut self, alloc: &Allocation) { + debug!(?alloc, "collect_allocation"); + for (_, id) in &alloc.provenance.ptrs { + self.collected.extend(collect_alloc_items(id.0).into_iter()) } } } @@ -353,12 +303,12 @@ impl<'a, 'tcx> MonoItemsFnCollector<'a, 'tcx> { /// 5. Drop glue. /// 6. Static Initialization /// This code has been mostly taken from `rustc_monomorphize::collector::MirNeighborCollector`. -impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { +impl<'a, 'tcx> MirVisitor for MonoItemsFnCollector<'a, 'tcx> { /// Collect the following: /// - Trait implementations when casting from concrete to dyn Trait. /// - Functions / Closures that have their address taken. /// - Thread Local. - fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { trace!(rvalue=?*rvalue, "visit_rvalue"); match *rvalue { @@ -369,13 +319,12 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { ) => { // Check if the conversion include casting a concrete type to a trait type. // If so, collect items from the impl `Trait for Concrete {}`. - let target_ty = self.monomorphize(target); - let source_ty = self.monomorphize(operand.ty(self.body, self.tcx)); - let base_coercion = - coercion::extract_unsize_casting(self.tcx, source_ty, target_ty); - if !base_coercion.src_ty.is_trait() && base_coercion.dst_ty.is_trait() { - debug!(?base_coercion, "collect_vtable_methods"); - self.collect_vtable_methods(base_coercion.src_ty, base_coercion.dst_ty); + let target_ty = target; + let source_ty = operand.ty(self.body.locals()).unwrap(); + let (src_ty, dst_ty) = extract_unsize_coercion(self.tcx, source_ty, target_ty); + if !src_ty.kind().is_trait() && dst_ty.kind().is_trait() { + debug!(?src_ty, ?dst_ty, "collect_vtable_methods"); + self.collect_vtable_methods(src_ty, dst_ty); } } Rvalue::Cast( @@ -383,19 +332,12 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { ref operand, _, ) => { - let fn_ty = operand.ty(self.body, self.tcx); - let fn_ty = self.monomorphize(fn_ty); - if let TyKind::FnDef(def_id, substs) = *fn_ty.kind() { - let instance = Instance::resolve_for_fn_ptr( - self.tcx, - ParamEnv::reveal_all(), - def_id, - substs, - ) - .unwrap(); + let fn_kind = operand.ty(self.body.locals()).unwrap().kind(); + if let RigidTy::FnDef(fn_def, args) = fn_kind.rigid().unwrap() { + let instance = Instance::resolve_for_fn_ptr(*fn_def, args).unwrap(); self.collect_instance(instance, false); } else { - unreachable!("Expected FnDef type, but got: {:?}", fn_ty); + unreachable!("Expected FnDef type, but got: {:?}", fn_kind); } } Rvalue::Cast( @@ -403,30 +345,20 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { ref operand, _, ) => { - let source_ty = operand.ty(self.body, self.tcx); - let source_ty = self.monomorphize(source_ty); - match *source_ty.kind() { - Closure(def_id, substs) => { - let instance = Instance::resolve_closure( - self.tcx, - def_id, - substs, - ClosureKind::FnOnce, - ) - .expect("failed to normalize and resolve closure during codegen"); + let source_ty = operand.ty(self.body.locals()).unwrap(); + match source_ty.kind().rigid().unwrap() { + RigidTy::Closure(def_id, args) => { + let instance = + Instance::resolve_closure(*def_id, args, ClosureKind::FnOnce) + .expect("failed to normalize and resolve closure during codegen"); self.collect_instance(instance, false); } _ => unreachable!("Unexpected type: {:?}", source_ty), } } - Rvalue::ThreadLocalRef(def_id) => { - assert!(self.tcx.is_thread_local_static(def_id)); - trace!(?def_id, "visit_rvalue thread_local"); - let instance = Instance::mono(self.tcx, def_id); - if should_codegen_locally(self.tcx, &instance) { - trace!("collecting thread-local static {:?}", def_id); - self.collected.insert(MonoItem::Static(def_id)); - } + Rvalue::ThreadLocalRef(item) => { + trace!(?item, "visit_rvalue thread_local"); + self.collected.insert(MonoItem::Static(StaticDef::try_from(item).unwrap())); } _ => { /* not interesting */ } } @@ -435,78 +367,39 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { } /// Collect constants that are represented as static variables. - fn visit_constant(&mut self, constant: &ConstOperand<'tcx>, location: Location) { - let const_ = self.monomorphize(constant.const_); - debug!(?constant, ?location, ?const_, "visit_constant"); - let val = match const_ { - Const::Val(const_val, _) => const_val, - Const::Ty(ct) => match ct.kind() { - ConstKind::Value(v) => self.tcx.valtree_to_const_val((ct.ty(), v)), - ConstKind::Unevaluated(_) => unreachable!(), - // Nothing to do - ConstKind::Param(..) - | ConstKind::Infer(..) - | ConstKind::Error(..) - | ConstKind::Expr(..) => return, - - // Shouldn't happen - ConstKind::Placeholder(..) | ConstKind::Bound(..) => { - unreachable!("Unexpected constant type {:?} ({:?})", ct, ct.kind()) - } - }, - Const::Unevaluated(un_eval, _) => { - // Thread local fall into this category. - match self.tcx.const_eval_resolve(ParamEnv::reveal_all(), un_eval, None) { - // The `monomorphize` call should have evaluated that constant already. - Ok(const_val) => const_val, - Err(ErrorHandled::TooGeneric(span)) => { - if graceful_const_resolution_err( - self.tcx, - &un_eval, - span, - self.instance.def_id(), - ) - .is_some() - { - return; - } else { - span_bug!( - span, - "Unexpected polymorphic constant: {:?} {:?}", - const_, - constant.const_ - ) - } - } - Err(error) => { - warn!(?error, "Error already reported"); - return; - } - } + fn visit_constant(&mut self, constant: &Constant, location: Location) { + debug!(?constant, ?location, literal=?constant.literal, "visit_constant"); + let allocation = match constant.literal.kind() { + ConstantKind::Allocated(allocation) => allocation, + ConstantKind::Unevaluated(_) => { + unreachable!("Instance with polymorphic constant: `{constant:?}`") + } + ConstantKind::Param(_) => unreachable!("Unexpected parameter constant: {constant:?}"), + ConstantKind::ZeroSized => { + // Nothing to do here. + return; } }; - self.collect_const_value(val); + self.collect_allocation(&allocation); } /// Collect function calls. - fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + fn visit_terminator(&mut self, terminator: &Terminator, location: Location) { trace!(?terminator, ?location, "visit_terminator"); - let tcx = self.tcx; match terminator.kind { - TerminatorKind::Call { ref func, args: ref outer_args, .. } => { - let callee_ty = func.ty(self.body, tcx); - let fn_ty = self.monomorphize(callee_ty); - if let TyKind::FnDef(def_id, substs) = *fn_ty.kind() { - let instance_opt = - Instance::resolve(self.tcx, ParamEnv::reveal_all(), def_id, substs) - .unwrap(); + TerminatorKind::Call { ref func, .. } => { + let fn_ty = func.ty(self.body.locals()).unwrap(); + if let TyKind::RigidTy(RigidTy::FnDef(fn_def, args)) = fn_ty.kind() { + let instance_opt = Instance::resolve(fn_def, &args).ok(); match instance_opt { None => { - let caller = tcx.def_path_str(self.instance.def_id()); - let callee = tcx.def_path_str(def_id); + let caller = CrateItem::try_from(*self.instance).unwrap().name(); + let callee = fn_def.name(); // Check if the current function has been stubbed. - if let Some(stub) = get_stub(tcx, self.instance.def_id()) { + if let Some(stub) = + get_stub(self.tcx, rustc_internal::internal(self.instance).def_id()) + { // During the MIR stubbing transformation, we do not // force type variables in the stub's signature to // implement the same traits as those in the @@ -518,22 +411,21 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { // necessarily true. It could be any argument or // even the return type, for instance for a // trait like `FromIterator`. - let generic_ty = outer_args[0].ty(self.body, tcx).peel_refs(); - let receiver_ty = tcx.instantiate_and_normalize_erasing_regions( - substs, - ParamEnv::reveal_all(), - EarlyBinder::bind(generic_ty), - ); + let receiver_ty = args.0[0].expect_ty(); let sep = callee.rfind("::").unwrap(); let trait_ = &callee[..sep]; - tcx.sess.span_err( - terminator.source_info.span, + self.tcx.sess.span_err( + rustc_internal::internal(terminator.span), format!( - "`{receiver_ty}` doesn't implement \ - `{trait_}`. The function `{caller}` \ + "`{}` doesn't implement \ + `{}`. The function `{}` \ cannot be stubbed by `{}` due to \ - generic bounds not being met. Callee: {callee}", - tcx.def_path_str(stub) + generic bounds not being met. Callee: {}", + pretty_ty(receiver_ty.kind()), + trait_, + caller, + self.tcx.def_path_str(stub), + callee, ), ); } else { @@ -544,74 +436,47 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> { }; } else { assert!( - matches!(fn_ty.kind(), TyKind::FnPtr(..)), + matches!(fn_ty.kind().rigid(), Some(RigidTy::FnPtr(..))), "Unexpected type: {fn_ty:?}" ); } } TerminatorKind::Drop { ref place, .. } => { - let place_ty = place.ty(self.body, self.tcx).ty; - let place_mono_ty = self.monomorphize(place_ty); - let instance = Instance::resolve_drop_in_place(self.tcx, place_mono_ty); + let place_ty = place.ty(self.body.locals()).unwrap(); + let instance = Instance::resolve_drop_in_place(place_ty); self.collect_instance(instance, true); } TerminatorKind::InlineAsm { .. } => { // We don't support inline assembly. This shall be replaced by an unsupported // construct during codegen. } - TerminatorKind::UnwindTerminate { .. } | TerminatorKind::Assert { .. } => { + TerminatorKind::Abort { .. } | TerminatorKind::Assert { .. } => { // We generate code for this without invoking any lang item. } - TerminatorKind::Goto { .. } + TerminatorKind::CoroutineDrop { .. } + | TerminatorKind::Goto { .. } | TerminatorKind::SwitchInt { .. } - | TerminatorKind::UnwindResume + | TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::Unreachable => {} - TerminatorKind::CoroutineDrop - | TerminatorKind::Yield { .. } - | TerminatorKind::FalseEdge { .. } - | TerminatorKind::FalseUnwind { .. } => { - unreachable!("Unexpected at this MIR level") - } } self.super_terminator(terminator, location); } } -/// Try to construct a nice error message when const evaluation fails. -/// -/// This function handles the `Trt::CNST` case where there is one trait (`Trt`) -/// which defined a constant `CNST` that we failed to resolve. As such we expect -/// that the trait can be resolved from the constant and that only one generic -/// parameter, the instantiation of `Trt`, is present. -/// -/// If these expectations are not met we return `None`. We do not know in what -/// situation that would be the case and if they are even possible. -fn graceful_const_resolution_err<'tcx>( - tcx: TyCtxt<'tcx>, - mono_const: &UnevaluatedConst<'tcx>, - span: rustc_span::Span, - parent_fn: DefId, -) -> Option { - let implementor = match mono_const.args.as_slice() { - [one] => one.as_type(), - _ => None, - }?; - let trait_ = tcx.trait_of_item(mono_const.def)?; - let msg = format!( - "Type `{implementor}` does not implement trait `{}`. \ - This is likely because `{}` is used as a stub but its \ - generic bounds are not being met.", - tcx.def_path_str(trait_), - tcx.def_path_str(parent_fn) +fn extract_unsize_coercion(tcx: TyCtxt, orig_ty: Ty, dst_trait: Ty) -> (Ty, Ty) { + let CoercionBase { src_ty, dst_ty } = coercion::extract_unsize_casting( + tcx, + rustc_internal::internal(orig_ty), + rustc_internal::internal(dst_trait), ); - Some(tcx.sess.span_err(span, msg)) + (rustc_internal::stable(src_ty), rustc_internal::stable(dst_ty)) } /// Convert a `MonoItem` into a stable `Fingerprint` which can be used as a stable hash across /// compilation sessions. This allow us to provide a stable deterministic order to codegen. -fn to_fingerprint(tcx: TyCtxt, item: &MonoItem) -> Fingerprint { +fn to_fingerprint(tcx: TyCtxt, item: &InternalMonoItem) -> Fingerprint { tcx.with_stable_hashing_context(|mut hcx| { let mut hasher = StableHasher::new(); item.hash_stable(&mut hcx, &mut hasher); @@ -620,52 +485,31 @@ fn to_fingerprint(tcx: TyCtxt, item: &MonoItem) -> Fingerprint { } /// Return whether we should include the item into codegen. -/// - We only skip foreign items. -/// -/// Note: Ideally, we should be able to assert that the MIR for non-foreign items are available via -/// call to `tcx.is_mir_available (def_id)`. -/// However, we found an issue where this function was returning `false` for a mutable static -/// item with constant initializer from an upstream crate. -/// See for an example. -fn should_codegen_locally<'tcx>(tcx: TyCtxt<'tcx>, instance: &Instance<'tcx>) -> bool { - if let Some(def_id) = instance.def.def_id_if_not_guaranteed_local_codegen() { - // We cannot codegen foreign items. - !tcx.is_foreign_item(def_id) - } else { - // This will include things like VTableShim and other stuff. See the method - // def_id_if_not_guaranteed_local_codegen for the full list. - true - } +fn should_codegen_locally(instance: &Instance) -> bool { + !instance.is_foreign_item() } -/// Scans the allocation type and collect static objects. -fn collect_alloc_items(tcx: TyCtxt, alloc_id: AllocId) -> Vec { - trace!(alloc=?tcx.global_alloc(alloc_id), ?alloc_id, "collect_alloc_items"); +fn collect_alloc_items(alloc_id: AllocId) -> Vec { + trace!(?alloc_id, "collect_alloc_items"); let mut items = vec![]; - match tcx.global_alloc(alloc_id) { - GlobalAlloc::Static(def_id) => { + match GlobalAlloc::from(alloc_id) { + GlobalAlloc::Static(def) => { // This differ from rustc's collector since rustc does not include static from // upstream crates. - assert!(!tcx.is_thread_local_static(def_id)); - let instance = Instance::mono(tcx, def_id); - should_codegen_locally(tcx, &instance).then(|| items.push(MonoItem::Static(def_id))); + let instance = Instance::try_from(CrateItem::from(def)).unwrap(); + should_codegen_locally(&instance).then(|| items.push(MonoItem::from(def))); } GlobalAlloc::Function(instance) => { - should_codegen_locally(tcx, &instance) - .then(|| items.push(MonoItem::Fn(instance.polymorphize(tcx)))); + should_codegen_locally(&instance).then(|| items.push(MonoItem::from(instance))); } GlobalAlloc::Memory(alloc) => { items.extend( - alloc - .inner() - .provenance() - .provenances() - .flat_map(|id| collect_alloc_items(tcx, id)), + alloc.provenance.ptrs.iter().flat_map(|(_, prov)| collect_alloc_items(prov.0)), ); } - GlobalAlloc::VTable(ty, trait_ref) => { - let vtable_id = tcx.vtable_allocation((ty, trait_ref)); - items.append(&mut collect_alloc_items(tcx, vtable_id)); + vtable_alloc @ GlobalAlloc::VTable(..) => { + let vtable_id = vtable_alloc.vtable_allocation().unwrap(); + items = collect_alloc_items(vtable_id); } }; items @@ -675,6 +519,7 @@ fn collect_alloc_items(tcx: TyCtxt, alloc_id: AllocId) -> Vec { mod debug { #![allow(dead_code)] + use std::fmt::{Display, Formatter}; use std::{ collections::{HashMap, HashSet}, fs::File, @@ -686,35 +531,39 @@ mod debug { use super::*; #[derive(Debug, Default)] - pub struct CallGraph<'tcx> { + pub struct CallGraph { // Nodes of the graph. - nodes: HashSet>, - edges: HashMap, Vec>>, - back_edges: HashMap, Vec>>, + nodes: HashSet, + edges: HashMap>, + back_edges: HashMap>, } - type Node<'tcx> = MonoItem<'tcx>; + #[derive(Clone, Debug, Eq, PartialEq, Hash)] + struct Node(pub MonoItem); - impl<'tcx> CallGraph<'tcx> { - pub fn add_node(&mut self, item: Node<'tcx>) { - self.nodes.insert(item); - self.edges.entry(item).or_default(); - self.back_edges.entry(item).or_default(); + impl CallGraph { + pub fn add_node(&mut self, item: MonoItem) { + let node = Node(item); + self.nodes.insert(node.clone()); + self.edges.entry(node.clone()).or_default(); + self.back_edges.entry(node).or_default(); } /// Add a new edge "from" -> "to". - pub fn add_edge(&mut self, from: Node<'tcx>, to: Node<'tcx>) { + pub fn add_edge(&mut self, from: MonoItem, to: MonoItem) { + let from_node = Node(from.clone()); + let to_node = Node(to.clone()); self.add_node(from); self.add_node(to); - self.edges.get_mut(&from).unwrap().push(to); - self.back_edges.get_mut(&to).unwrap().push(from); + self.edges.get_mut(&from_node).unwrap().push(to_node.clone()); + self.back_edges.get_mut(&to_node).unwrap().push(from_node); } /// Add multiple new edges for the "from" node. - pub fn add_edges(&mut self, from: Node<'tcx>, to: &[Node<'tcx>]) { - self.add_node(from); + pub fn add_edges(&mut self, from: MonoItem, to: &[MonoItem]) { + self.add_node(from.clone()); for item in to { - self.add_edge(from, *item); + self.add_edge(from.clone(), item.clone()); } } @@ -759,7 +608,7 @@ mod debug { .iter() .filter(|item| item.to_string().contains(target)) .collect::>(); - let mut visited: HashSet<&MonoItem> = HashSet::default(); + let mut visited: HashSet<&Node> = HashSet::default(); tracing::info!(target=?queue, nodes=?self.nodes.len(), edges=?self.edges.len(), "dump_reason"); while let Some(to_visit) = queue.pop() { if !visited.contains(to_visit) { @@ -779,4 +628,14 @@ mod debug { Ok(()) } } + + impl Display for Node { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.0 { + MonoItem::Fn(instance) => write!(f, "{}", instance.mangled_name()), + MonoItem::Static(def) => write!(f, "{}", CrateItem::from(*def).name()), + MonoItem::GlobalAsm(asm) => write!(f, "{asm:?}"), + } + } + } } diff --git a/kani-compiler/src/kani_middle/stubbing/mod.rs b/kani-compiler/src/kani_middle/stubbing/mod.rs index d4a00f44cc01..03298456c522 100644 --- a/kani-compiler/src/kani_middle/stubbing/mod.rs +++ b/kani-compiler/src/kani_middle/stubbing/mod.rs @@ -6,12 +6,19 @@ mod annotations; mod transform; use std::collections::BTreeMap; +use tracing::{debug, trace}; +pub use self::transform::*; use kani_metadata::HarnessMetadata; use rustc_hir::def_id::DefId; use rustc_hir::definitions::DefPathHash; -use rustc_middle::ty::TyCtxt; -pub use transform::*; +use rustc_middle::mir::Const; +use rustc_middle::ty::{self, EarlyBinder, ParamEnv, TyCtxt, TypeFoldable}; +use rustc_smir::rustc_internal; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::visit::{Location, MirVisitor}; +use stable_mir::mir::Constant; +use stable_mir::{CrateDef, CrateItem}; use self::annotations::update_stub_mapping; @@ -28,3 +35,83 @@ pub fn harness_stub_map( } stub_pairs } + +/// Validate that an instance body can be instantiated. +/// +/// Stubbing may cause an instance to not be correctly instantiated since we delay checking its +/// generic bounds. +/// +/// In stable MIR, trying to retrieve an `Instance::body()` will ICE if we cannot evaluate a +/// constant as expected. For now, use internal APIs to anticipate this issue. +pub fn validate_instance(tcx: TyCtxt, instance: Instance) -> bool { + let internal_instance = rustc_internal::internal(instance); + if get_stub(tcx, internal_instance.def_id()).is_some() { + let item = CrateItem::try_from(instance).unwrap(); + let mut checker = StubConstChecker::new(tcx, internal_instance, item); + checker.visit_body(&item.body()); + checker.is_valid() + } else { + true + } +} + +struct StubConstChecker<'tcx> { + tcx: TyCtxt<'tcx>, + instance: ty::Instance<'tcx>, + source: CrateItem, + is_valid: bool, +} + +impl<'tcx> StubConstChecker<'tcx> { + fn new(tcx: TyCtxt<'tcx>, instance: ty::Instance<'tcx>, source: CrateItem) -> Self { + StubConstChecker { tcx, instance, is_valid: true, source } + } + fn monomorphize(&self, value: T) -> T + where + T: TypeFoldable>, + { + trace!(instance=?self.instance, ?value, "monomorphize"); + self.instance.instantiate_mir_and_normalize_erasing_regions( + self.tcx, + ParamEnv::reveal_all(), + EarlyBinder::bind(value), + ) + } + + fn is_valid(&self) -> bool { + self.is_valid + } +} + +impl<'tcx> MirVisitor for StubConstChecker<'tcx> { + /// Collect constants that are represented as static variables. + fn visit_constant(&mut self, constant: &Constant, location: Location) { + let const_ = self.monomorphize(rustc_internal::internal(&constant.literal)); + debug!(?constant, ?location, ?const_, "visit_constant"); + match const_ { + Const::Val(..) | Const::Ty(..) => {} + Const::Unevaluated(un_eval, _) => { + // Thread local fall into this category. + if self.tcx.const_eval_resolve(ParamEnv::reveal_all(), un_eval, None).is_err() { + // The `monomorphize` call should have evaluated that constant already. + let tcx = self.tcx; + let mono_const = &un_eval; + let implementor = match mono_const.args.as_slice() { + [one] => one.as_type().unwrap(), + _ => unreachable!(), + }; + let trait_ = tcx.trait_of_item(mono_const.def).unwrap(); + let msg = format!( + "Type `{implementor}` does not implement trait `{}`. \ + This is likely because `{}` is used as a stub but its \ + generic bounds are not being met.", + tcx.def_path_str(trait_), + self.source.name() + ); + tcx.sess.span_err(rustc_internal::internal(location.span()), msg); + self.is_valid = false; + } + } + }; + } +} diff --git a/tests/cargo-kani/stubbing-double-extern-path/harness/expected b/tests/cargo-kani/stubbing-double-extern-path/harness/expected index 178d9ab00302..adbbf31d49bd 100644 --- a/tests/cargo-kani/stubbing-double-extern-path/harness/expected +++ b/tests/cargo-kani/stubbing-double-extern-path/harness/expected @@ -1 +1 @@ -error[E0391]: cycle detected when optimizing MIR for `crate_a::assert_true` +error[E0391]: cycle detected when optimizing MIR for `crate_b::assert_false`