Skip to content

Commit 0e419ef

Browse files
committed
check for object safety violations in constants
1 parent 9dadcf8 commit 0e419ef

9 files changed

+237
-47
lines changed

compiler/rustc_trait_selection/src/traits/const_evaluatable.rs

+37-23
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,10 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
8585
} else if leaf.has_param_types_or_consts() {
8686
failure_kind = cmp::min(failure_kind, FailureKind::MentionsParam);
8787
}
88+
89+
false
8890
}
89-
Node::Binop(_, _, _) | Node::UnaryOp(_, _) | Node::FunctionCall(_, _) => (),
91+
Node::Binop(_, _, _) | Node::UnaryOp(_, _) | Node::FunctionCall(_, _) => false,
9092
});
9193

9294
match failure_kind {
@@ -194,12 +196,12 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
194196
///
195197
/// This is only able to represent a subset of `MIR`,
196198
/// and should not leak any information about desugarings.
197-
#[derive(Clone, Copy)]
199+
#[derive(Debug, Clone, Copy)]
198200
pub struct AbstractConst<'tcx> {
199201
// FIXME: Consider adding something like `IndexSlice`
200202
// and use this here.
201-
inner: &'tcx [Node<'tcx>],
202-
substs: SubstsRef<'tcx>,
203+
pub inner: &'tcx [Node<'tcx>],
204+
pub substs: SubstsRef<'tcx>,
203205
}
204206

205207
impl AbstractConst<'tcx> {
@@ -212,6 +214,17 @@ impl AbstractConst<'tcx> {
212214
Ok(inner.map(|inner| AbstractConst { inner, substs }))
213215
}
214216

217+
pub fn from_const(
218+
tcx: TyCtxt<'tcx>,
219+
ct: &ty::Const<'tcx>,
220+
) -> Result<Option<AbstractConst<'tcx>>, ErrorReported> {
221+
match ct.val {
222+
ty::ConstKind::Unevaluated(def, substs, None) => AbstractConst::new(tcx, def, substs),
223+
ty::ConstKind::Error(_) => Err(ErrorReported),
224+
_ => Ok(None),
225+
}
226+
}
227+
215228
#[inline]
216229
pub fn subtree(self, node: NodeId) -> AbstractConst<'tcx> {
217230
AbstractConst { inner: &self.inner[..=node.index()], substs: self.substs }
@@ -550,31 +563,32 @@ pub(super) fn try_unify_abstract_consts<'tcx>(
550563
// on `ErrorReported`.
551564
}
552565

553-
fn walk_abstract_const<'tcx, F>(tcx: TyCtxt<'tcx>, ct: AbstractConst<'tcx>, mut f: F)
566+
// FIXME: Use `std::ops::ControlFlow` instead of `bool` here.
567+
pub fn walk_abstract_const<'tcx, F>(tcx: TyCtxt<'tcx>, ct: AbstractConst<'tcx>, mut f: F) -> bool
554568
where
555-
F: FnMut(Node<'tcx>),
569+
F: FnMut(Node<'tcx>) -> bool,
556570
{
557-
recurse(tcx, ct, &mut f);
558-
fn recurse<'tcx>(tcx: TyCtxt<'tcx>, ct: AbstractConst<'tcx>, f: &mut dyn FnMut(Node<'tcx>)) {
571+
fn recurse<'tcx>(
572+
tcx: TyCtxt<'tcx>,
573+
ct: AbstractConst<'tcx>,
574+
f: &mut dyn FnMut(Node<'tcx>) -> bool,
575+
) -> bool {
559576
let root = ct.root();
560-
f(root);
561-
match root {
562-
Node::Leaf(_) => (),
563-
Node::Binop(_, l, r) => {
564-
recurse(tcx, ct.subtree(l), f);
565-
recurse(tcx, ct.subtree(r), f);
566-
}
567-
Node::UnaryOp(_, v) => {
568-
recurse(tcx, ct.subtree(v), f);
569-
}
570-
Node::FunctionCall(func, args) => {
571-
recurse(tcx, ct.subtree(func), f);
572-
for &arg in args {
573-
recurse(tcx, ct.subtree(arg), f);
577+
f(root)
578+
|| match root {
579+
Node::Leaf(_) => false,
580+
Node::Binop(_, l, r) => {
581+
recurse(tcx, ct.subtree(l), f) || recurse(tcx, ct.subtree(r), f)
582+
}
583+
Node::UnaryOp(_, v) => recurse(tcx, ct.subtree(v), f),
584+
Node::FunctionCall(func, args) => {
585+
recurse(tcx, ct.subtree(func), f)
586+
|| args.iter().any(|&arg| recurse(tcx, ct.subtree(arg), f))
574587
}
575588
}
576-
}
577589
}
590+
591+
recurse(tcx, ct, &mut f)
578592
}
579593

580594
/// Tries to unify two abstract constants using structural equality.

compiler/rustc_trait_selection/src/traits/object_safety.rs

+61-24
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use super::elaborate_predicates;
1212

1313
use crate::infer::TyCtxtInferExt;
14+
use crate::traits::const_evaluatable::{self, AbstractConst};
1415
use crate::traits::query::evaluate_obligation::InferCtxtExt;
1516
use crate::traits::{self, Obligation, ObligationCause};
1617
use rustc_errors::FatalError;
@@ -249,7 +250,7 @@ fn predicates_reference_self(
249250
predicates
250251
.predicates
251252
.iter()
252-
.map(|(predicate, sp)| (predicate.subst_supertrait(tcx, &trait_ref), *sp))
253+
.map(|&(predicate, sp)| (predicate.subst_supertrait(tcx, &trait_ref), sp))
253254
.filter_map(|predicate| predicate_references_self(tcx, predicate))
254255
.collect()
255256
}
@@ -260,7 +261,7 @@ fn bounds_reference_self(tcx: TyCtxt<'_>, trait_def_id: DefId) -> SmallVec<[Span
260261
.in_definition_order()
261262
.filter(|item| item.kind == ty::AssocKind::Type)
262263
.flat_map(|item| tcx.explicit_item_bounds(item.def_id))
263-
.map(|(predicate, sp)| (predicate.subst_supertrait(tcx, &trait_ref), *sp))
264+
.map(|&(predicate, sp)| (predicate.subst_supertrait(tcx, &trait_ref), sp))
264265
.filter_map(|predicate| predicate_references_self(tcx, predicate))
265266
.collect()
266267
}
@@ -415,7 +416,7 @@ fn virtual_call_violation_for_method<'tcx>(
415416
));
416417
}
417418

418-
for (i, input_ty) in sig.skip_binder().inputs()[1..].iter().enumerate() {
419+
for (i, &input_ty) in sig.skip_binder().inputs()[1..].iter().enumerate() {
419420
if contains_illegal_self_type_reference(tcx, trait_def_id, input_ty) {
420421
return Some(MethodViolationCode::ReferencesSelfInput(i));
421422
}
@@ -438,10 +439,7 @@ fn virtual_call_violation_for_method<'tcx>(
438439
// so outlives predicates will always hold.
439440
.cloned()
440441
.filter(|(p, _)| p.to_opt_type_outlives().is_none())
441-
.collect::<Vec<_>>()
442-
// Do a shallow visit so that `contains_illegal_self_type_reference`
443-
// may apply it's custom visiting.
444-
.visit_tys_shallow(|t| contains_illegal_self_type_reference(tcx, trait_def_id, t))
442+
.any(|pred| contains_illegal_self_type_reference(tcx, trait_def_id, pred))
445443
{
446444
return Some(MethodViolationCode::WhereClauseReferencesSelf);
447445
}
@@ -715,10 +713,10 @@ fn receiver_is_dispatchable<'tcx>(
715713
})
716714
}
717715

718-
fn contains_illegal_self_type_reference<'tcx>(
716+
fn contains_illegal_self_type_reference<'tcx, T: TypeFoldable<'tcx>>(
719717
tcx: TyCtxt<'tcx>,
720718
trait_def_id: DefId,
721-
ty: Ty<'tcx>,
719+
value: T,
722720
) -> bool {
723721
// This is somewhat subtle. In general, we want to forbid
724722
// references to `Self` in the argument and return types,
@@ -761,15 +759,14 @@ fn contains_illegal_self_type_reference<'tcx>(
761759

762760
struct IllegalSelfTypeVisitor<'tcx> {
763761
tcx: TyCtxt<'tcx>,
764-
self_ty: Ty<'tcx>,
765762
trait_def_id: DefId,
766763
supertraits: Option<Vec<ty::PolyTraitRef<'tcx>>>,
767764
}
768765

769766
impl<'tcx> TypeVisitor<'tcx> for IllegalSelfTypeVisitor<'tcx> {
770767
fn visit_ty(&mut self, t: Ty<'tcx>) -> bool {
771768
match t.kind() {
772-
ty::Param(_) => t == self.self_ty,
769+
ty::Param(_) => t == self.tcx.types.self_param,
773770
ty::Projection(ref data) => {
774771
// This is a projected type `<Foo as SomeTrait>::X`.
775772

@@ -802,22 +799,62 @@ fn contains_illegal_self_type_reference<'tcx>(
802799
}
803800
}
804801

805-
fn visit_const(&mut self, _c: &ty::Const<'tcx>) -> bool {
806-
// FIXME(#72219) Look into the unevaluated constants for object safety violations.
807-
// Do not walk substitutions of unevaluated consts, as they contain `Self`, even
808-
// though the const expression doesn't necessary use it. Currently type variables
809-
// inside array length expressions are forbidden, so they can't break the above
810-
// rules.
811-
false
802+
fn visit_const(&mut self, ct: &ty::Const<'tcx>) -> bool {
803+
// First check if the type of this constant references `Self`.
804+
if self.visit_ty(ct.ty) {
805+
return true;
806+
}
807+
808+
// Constants can only influence object safety if they reference `Self`.
809+
// This is only possible for unevaluated constants, so we walk these here.
810+
//
811+
// If `AbstractConst::new` returned an error we already failed compilation
812+
// so we don't have to emit an additional error here.
813+
//
814+
// We currently recurse into abstract consts here but do not recurse in
815+
// `is_const_evaluatable`. This means that the object safety check is more
816+
// liberal than the const eval check.
817+
//
818+
// This shouldn't really matter though as we can't really use any
819+
// constants which are not considered const evaluatable.
820+
use rustc_middle::mir::abstract_const::Node;
821+
if let Ok(Some(ct)) = AbstractConst::from_const(self.tcx, ct) {
822+
const_evaluatable::walk_abstract_const(self.tcx, ct, |node| match node {
823+
Node::Leaf(leaf) => {
824+
let leaf = leaf.subst(self.tcx, ct.substs);
825+
self.visit_const(leaf)
826+
}
827+
Node::Binop(..) | Node::UnaryOp(..) | Node::FunctionCall(_, _) => false,
828+
})
829+
} else {
830+
false
831+
}
832+
}
833+
834+
fn visit_predicate(&mut self, pred: ty::Predicate<'tcx>) -> bool {
835+
if let ty::PredicateAtom::ConstEvaluatable(def, substs) = pred.skip_binders() {
836+
// FIXME(const_evaluatable_checked): We should probably deduplicate the logic for
837+
// `AbstractConst`s here, it might make sense to change `ConstEvaluatable` to
838+
// take a `ty::Const` instead.
839+
use rustc_middle::mir::abstract_const::Node;
840+
if let Ok(Some(ct)) = AbstractConst::new(self.tcx, def, substs) {
841+
const_evaluatable::walk_abstract_const(self.tcx, ct, |node| match node {
842+
Node::Leaf(leaf) => {
843+
let leaf = leaf.subst(self.tcx, ct.substs);
844+
self.visit_const(leaf)
845+
}
846+
Node::Binop(..) | Node::UnaryOp(..) | Node::FunctionCall(_, _) => false,
847+
})
848+
} else {
849+
false
850+
}
851+
} else {
852+
pred.super_visit_with(self)
853+
}
812854
}
813855
}
814856

815-
ty.visit_with(&mut IllegalSelfTypeVisitor {
816-
tcx,
817-
self_ty: tcx.types.self_param,
818-
trait_def_id,
819-
supertraits: None,
820-
})
857+
value.visit_with(&mut IllegalSelfTypeVisitor { tcx, trait_def_id, supertraits: None })
821858
}
822859

823860
pub fn provide(providers: &mut ty::query::Providers) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![feature(const_generics, const_evaluatable_checked)]
2+
#![allow(incomplete_features)]
3+
4+
5+
const fn bar<T: ?Sized>() -> usize { 7 }
6+
7+
trait Foo {
8+
fn test(&self) -> [u8; bar::<Self>()];
9+
}
10+
11+
impl Foo for () {
12+
fn test(&self) -> [u8; bar::<Self>()] {
13+
[0; bar::<Self>()]
14+
}
15+
}
16+
17+
fn use_dyn(v: &dyn Foo) { //~ERROR the trait `Foo` cannot be made into an object
18+
v.test();
19+
}
20+
21+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error[E0038]: the trait `Foo` cannot be made into an object
2+
--> $DIR/object-safety-err-ret.rs:17:15
3+
|
4+
LL | fn use_dyn(v: &dyn Foo) {
5+
| ^^^^^^^^ `Foo` cannot be made into an object
6+
|
7+
= help: consider moving `test` to another trait
8+
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
9+
--> $DIR/object-safety-err-ret.rs:8:23
10+
|
11+
LL | trait Foo {
12+
| --- this trait cannot be made into an object...
13+
LL | fn test(&self) -> [u8; bar::<Self>()];
14+
| ^^^^^^^^^^^^^^^^^^^ ...because method `test` references the `Self` type in its return type
15+
16+
error: aborting due to previous error
17+
18+
For more information about this error, try `rustc --explain E0038`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![feature(const_generics, const_evaluatable_checked)]
2+
#![allow(incomplete_features)]
3+
#![deny(where_clauses_object_safety)]
4+
5+
6+
const fn bar<T: ?Sized>() -> usize { 7 }
7+
8+
trait Foo {
9+
fn test(&self) where [u8; bar::<Self>()]: Sized;
10+
//~^ ERROR the trait `Foo` cannot be made into an object
11+
//~| WARN this was previously accepted by the compiler but is being phased out
12+
}
13+
14+
impl Foo for () {
15+
fn test(&self) where [u8; bar::<Self>()]: Sized {}
16+
}
17+
18+
fn use_dyn(v: &dyn Foo) {
19+
v.test();
20+
}
21+
22+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error: the trait `Foo` cannot be made into an object
2+
--> $DIR/object-safety-err-where-bounds.rs:9:8
3+
|
4+
LL | fn test(&self) where [u8; bar::<Self>()]: Sized;
5+
| ^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/object-safety-err-where-bounds.rs:3:9
9+
|
10+
LL | #![deny(where_clauses_object_safety)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
13+
= note: for more information, see issue #51443 <https://github.com/rust-lang/rust/issues/51443>
14+
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
15+
--> $DIR/object-safety-err-where-bounds.rs:9:8
16+
|
17+
LL | trait Foo {
18+
| --- this trait cannot be made into an object...
19+
LL | fn test(&self) where [u8; bar::<Self>()]: Sized;
20+
| ^^^^ ...because method `test` references the `Self` type in its `where` clause
21+
= help: consider moving `test` to another trait
22+
23+
error: aborting due to previous error
24+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![feature(const_generics, const_evaluatable_checked)]
2+
#![allow(incomplete_features)]
3+
4+
trait Foo<const N: usize> {
5+
fn test(&self) -> [u8; N + 1];
6+
}
7+
8+
impl<const N: usize> Foo<N> for () {
9+
fn test(&self) -> [u8; N + 1] {
10+
[0; N + 1]
11+
}
12+
}
13+
14+
fn use_dyn<const N: usize>(v: &dyn Foo<N>) where [u8; N + 1]: Sized {
15+
assert_eq!(v.test(), [0; N + 1]);
16+
}
17+
18+
fn main() {
19+
use_dyn(&());
20+
//~^ ERROR type annotations needed
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0284]: type annotations needed: cannot satisfy `the constant `use_dyn::<{_: usize}>::{constant#0}` can be evaluated`
2+
--> $DIR/object-safety-ok-infer-err.rs:19:5
3+
|
4+
LL | fn use_dyn<const N: usize>(v: &dyn Foo<N>) where [u8; N + 1]: Sized {
5+
| ----- required by this bound in `use_dyn`
6+
...
7+
LL | use_dyn(&());
8+
| ^^^^^^^ cannot satisfy `the constant `use_dyn::<{_: usize}>::{constant#0}` can be evaluated`
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0284`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// run-pass
2+
#![feature(const_generics, const_evaluatable_checked)]
3+
#![allow(incomplete_features)]
4+
5+
trait Foo<const N: usize> {
6+
fn test(&self) -> [u8; N + 1];
7+
}
8+
9+
impl<const N: usize> Foo<N> for () {
10+
fn test(&self) -> [u8; N + 1] {
11+
[0; N + 1]
12+
}
13+
}
14+
15+
fn use_dyn<const N: usize>(v: &dyn Foo<N>) where [u8; N + 1]: Sized {
16+
assert_eq!(v.test(), [0; N + 1]);
17+
}
18+
19+
fn main() {
20+
use_dyn::<3>(&());
21+
}

0 commit comments

Comments
 (0)