Skip to content

Commit

Permalink
Auto merge of rust-lang#137972 - BoxyUwU:ty_const_wf_before_eval, r=<…
Browse files Browse the repository at this point in the history
…try>

Ensure constants are WF before calling into CTFE

Fixes rust-lang#127643
Fixes rust-lang#131046
Fixes rust-lang#131406
Fixes rust-lang#133066

I'll write a PR desc for this tommorow

r? `@ghost`
  • Loading branch information
bors committed Mar 4, 2025
2 parents e16a049 + f770b29 commit 4cbf2ca
Show file tree
Hide file tree
Showing 22 changed files with 544 additions and 169 deletions.
12 changes: 12 additions & 0 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2374,6 +2374,18 @@ rustc_queries! {
}
}

query check_constant_safe_to_evaluate(
key: ty::CanonicalQueryInput<
TyCtxt<'tcx>,
ty::ParamEnvAnd<'tcx, ty::UnevaluatedConst<'tcx>>,
>
) -> bool {
desc { |tcx|
"checking constant `{}` is safe to evaluate",
tcx.def_path_str(key.canonical.value.into_parts().1.def)
}
}

query method_autoderef_steps(
goal: CanonicalTyGoal<'tcx>
) -> MethodAutoderefStepsResult<'tcx> {
Expand Down
189 changes: 145 additions & 44 deletions compiler/rustc_trait_selection/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use std::ops::ControlFlow;

use rustc_errors::ErrorGuaranteed;
use rustc_hir::def::DefKind;
use rustc_infer::infer::canonical::OriginalQueryValues;
pub use rustc_infer::traits::*;
use rustc_middle::query::Providers;
use rustc_middle::span_bug;
Expand Down Expand Up @@ -545,75 +546,130 @@ pub fn try_evaluate_const<'tcx>(
| ty::ConstKind::Expr(_) => Err(EvaluateConstErr::HasGenericsOrInfers),
ty::ConstKind::Unevaluated(uv) => {
// Postpone evaluation of constants that depend on generic parameters or
// inference variables.
// inference variables. Also ensure that constants are wf before passing
// them onto CTFE.
//
// We use `TypingMode::PostAnalysis` here which is not *technically* correct
// We use `TypingMode::PostAnalysis` here which is not *technically* correct
// to be revealing opaque types here as borrowcheck has not run yet. However,
// CTFE itself uses `TypingMode::PostAnalysis` unconditionally even during
// typeck and not doing so has a lot of (undesirable) fallout (#101478, #119821).
// As a result we always use a revealed env when resolving the instance to evaluate.
//
// FIXME: `const_eval_resolve_for_typeck` should probably just modify the env itself
// instead of having this logic here
let (args, typing_env) = if tcx.features().generic_const_exprs()
&& uv.has_non_region_infer()
{
// `feature(generic_const_exprs)` causes anon consts to inherit all parent generics. This can cause
// inference variables and generic parameters to show up in `ty::Const` even though the anon const
// does not actually make use of them. We handle this case specially and attempt to evaluate anyway.
match tcx.thir_abstract_const(uv.def) {
Ok(Some(ct)) => {
let ct = tcx.expand_abstract_consts(ct.instantiate(tcx, uv.args));
if let Err(e) = ct.error_reported() {
return Err(EvaluateConstErr::EvaluationFailure(e));
} else if ct.has_non_region_infer() || ct.has_non_region_param() {
// If the anon const *does* actually use generic parameters or inference variables from
// the generic arguments provided for it, then we should *not* attempt to evaluate it.
return Err(EvaluateConstErr::HasGenericsOrInfers);
} else {
let args = replace_param_and_infer_args_with_placeholder(tcx, uv.args);
let typing_env = infcx
.typing_env(tcx.erase_regions(param_env))
.with_post_analysis_normalized(tcx);
let (args, typing_env) = if tcx.features().generic_const_exprs() {
// We handle `generic_const_exprs` separately as reasonable ways of handling constants in the type system
// completely fall apart under `generic_const_exprs` and makes this whole function Really hard to reason
// about if you have to consider gce whatsoever.
//
// We don't bother trying to ensure GCE constants are WF before passing them to CTFE as it would cause
// query cycles on almost every single call to this function.

if uv.has_non_region_infer() || uv.has_non_region_param() {
// `feature(generic_const_exprs)` causes anon consts to inherit all parent generics. This can cause
// inference variables and generic parameters to show up in `ty::Const` even though the anon const
// does not actually make use of them. We handle this case specially and attempt to evaluate anyway.
match tcx.thir_abstract_const(uv.def) {
Ok(Some(ct)) => {
let ct = tcx.expand_abstract_consts(ct.instantiate(tcx, uv.args));
if let Err(e) = ct.error_reported() {
return Err(EvaluateConstErr::EvaluationFailure(e));
} else if ct.has_non_region_infer() || ct.has_non_region_param() {
// If the anon const *does* actually use generic parameters or inference variables from
// the generic arguments provided for it, then we should *not* attempt to evaluate it.
return Err(EvaluateConstErr::HasGenericsOrInfers);
} else {
let args =
replace_param_and_infer_args_with_placeholder(tcx, uv.args);
let typing_env = infcx
.typing_env(tcx.erase_regions(param_env))
.with_post_analysis_normalized(tcx);
(args, typing_env)
}
}
Err(_) | Ok(None) => {
let args = GenericArgs::identity_for_item(tcx, uv.def);
let typing_env = ty::TypingEnv::post_analysis(tcx, uv.def);
(args, typing_env)
}
}
Err(_) | Ok(None) => {
let args = GenericArgs::identity_for_item(tcx, uv.def);
let typing_env = ty::TypingEnv::post_analysis(tcx, uv.def);
(args, typing_env)
}
} else {
let typing_env = infcx
.typing_env(tcx.erase_regions(param_env))
.with_post_analysis_normalized(tcx);
(uv.args, typing_env)
}
} else if tcx.def_kind(uv.def) == DefKind::AnonConst && uv.has_non_region_infer() {
} else if !tcx.features().min_generic_const_args()
&& !tcx.features().associated_const_equality()
// We check for anon consts so that when `associated_const_equality` bounds are
// lowered on stable we still handle them correctly to avoid ICEs in CTFE.
&& tcx.def_kind(uv.def) == DefKind::AnonConst
{
// FIXME: remove this when `const_evaluatable_unchecked` is a hard error.
//
// Diagnostics will sometimes replace the identity args of anon consts in
// array repeat expr counts with inference variables so we have to handle this
// even though it is not something we should ever actually encounter.
//
// Array repeat expr counts are allowed to syntactically use generic parameters
// but must not actually depend on them in order to evalaute successfully. This means
// that it is actually fine to evalaute them in their own environment rather than with
// the actually provided generic arguments.
tcx.dcx().delayed_bug(
"Encountered anon const with inference variable args but no error reported",
);
// Under `min_const_generics` the only place we can encounter generics in type system
// consts is for the `const_evaluatable_unchecked` future compat lint. We don't care
// to handle them in important ways (e.g. deferring evaluation) so we handle it separately.

if uv.has_non_region_infer() {
// Diagnostics will sometimes replace the identity args of anon consts in
// array repeat expr counts with inference variables so we have to handle this
// even though it is not something we should ever actually encounter.
//
// Array repeat expr counts are allowed to syntactically use generic parameters
// but must not actually depend on them in order to evalaute successfully. This means
// that it is actually fine to evalaute them in their own environment rather than with
// the actually provided generic arguments.
tcx.dcx().delayed_bug(
"Encountered anon const with inference variable args but no error reported",
);
}

// Generic arguments to anon consts in the type system are never meaningful under mcg,
// there either are no arguments or its a repeat count and the arguments must not be
// depended on for evaluation.
let args = GenericArgs::identity_for_item(tcx, uv.def);
let typing_env = ty::TypingEnv::post_analysis(tcx, uv.def);
(args, typing_env)
} else {
// FIXME: This codepath is reachable under `associated_const_equality` and in the
// future will be reachable by `min_generic_const_args`. We should handle inference
// variables and generic parameters properly instead of doing nothing.
// We are only dealing with "truly" generic/uninferred constants here:
// - `generic_const_exprs` has been handled separately in the first if
// - `min_const_generics` repeat expr count hacks have been handled in the previous if
//
// So we are free to simply defer evaluation here. This does assume that `uv.args` has
// already been normalized.
if uv.args.has_non_region_param() || uv.args.has_non_region_infer() {
return Err(EvaluateConstErr::HasGenericsOrInfers);
}

// If we are dealing with a fully monomorphic constant then we should ensure that
// it is well formed as otherwise CTFE will ICE. For the same reasons as with
// deferring evaluation of generic/uninferred constants, we do not have to worry
// about `generic_const_expr`
if tcx.def_kind(uv.def) == DefKind::AnonConst
&& tcx.predicates_of(uv.def).predicates.is_empty()
{
// ... skip doing any work
} else {
let input = infcx
.canonicalize_query(param_env.and(uv), &mut OriginalQueryValues::default());
if !tcx.check_constant_safe_to_evaluate(input) {
let e = tcx.dcx().delayed_bug(
"Attempted to evaluate illformed constant but no error emitted",
);
return Err(EvaluateConstErr::EvaluationFailure(e));
}
}

let typing_env = infcx
.typing_env(tcx.erase_regions(param_env))
.with_post_analysis_normalized(tcx);
(uv.args, typing_env)
};
let uv = ty::UnevaluatedConst::new(uv.def, args);

let uv = ty::UnevaluatedConst::new(uv.def, args);
let erased_uv = tcx.erase_regions(uv);

use rustc_middle::mir::interpret::ErrorHandled;
match tcx.const_eval_resolve_for_typeck(typing_env, erased_uv, DUMMY_SP) {
Ok(Ok(val)) => Ok(ty::Const::new_value(
Expand Down Expand Up @@ -697,7 +753,51 @@ fn replace_param_and_infer_args_with_placeholder<'tcx>(
args.fold_with(&mut ReplaceParamAndInferWithPlaceholder { tcx, idx: 0 })
}

/// Normalizes the predicates and checks whether they hold in an empty environment. If this
#[instrument(level = "debug", skip(tcx), ret)]
fn check_constant_safe_to_evaluate<'tcx>(
tcx: TyCtxt<'tcx>,
input: ty::CanonicalQueryInput<TyCtxt<'tcx>, ty::ParamEnvAnd<'tcx, ty::UnevaluatedConst<'tcx>>>,
) -> bool {
let (infcx, param_env_and, _) = tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &input);
let (param_env, uv) = param_env_and.into_parts();

// We can't just register a wf goal for the constant as that would require evaluating the constant
// which would result in a query cycle.
let mut predicates = tcx.predicates_of(uv.def).instantiate(tcx, uv.args).predicates;

// Specifically check trait fulfillment to avoid an error when trying to resolve
// associated items.
if let Some(trait_def_id) = tcx.trait_of_item(uv.def) {
let trait_ref = ty::TraitRef::from_method(tcx, trait_def_id, uv.args);
predicates.push(trait_ref.upcast(tcx));
}

let ocx = ObligationCtxt::new(&infcx);
let predicates = ocx.normalize(&ObligationCause::dummy(), param_env, predicates);
for predicate in predicates {
let obligation = Obligation::new(tcx, ObligationCause::dummy(), param_env, predicate);
ocx.register_obligation(obligation);
}
let errors = ocx.select_all_or_error();

if !errors.is_empty() {
return false;
}

// Leak check for any higher-ranked trait mismatches.
// We only need to do this in the old solver, since the new solver already
// leak-checks.
if !infcx.next_trait_solver() && infcx.leak_check(ty::UniverseIndex::ROOT, None).is_err() {
return false;
}

// We don't check non-higher-ranked region constraints, but we don't need to as region
// constraints cant affect coherence and therefore result in overlapping impls in codegen/ctfe
// so it shouldn't matter to speculatively evaluate constants with failing region constraints.
true
}

/// Normalizes the predicates and checks whether they hold in a given empty. If this
/// returns true, then either normalize encountered an error or one of the predicates did not
/// hold. Used when creating vtables to check for unsatisfiable methods. This should not be
/// used during analysis.
Expand Down Expand Up @@ -840,6 +940,7 @@ pub fn provide(providers: &mut Providers) {
specialization_enabled_in: specialize::specialization_enabled_in,
instantiate_and_check_impossible_predicates,
is_impossible_associated_item,
check_constant_safe_to_evaluate,
..*providers
};
}
18 changes: 0 additions & 18 deletions tests/crashes/127643.rs

This file was deleted.

15 changes: 0 additions & 15 deletions tests/crashes/131046.rs

This file was deleted.

12 changes: 0 additions & 12 deletions tests/crashes/131406.rs

This file was deleted.

12 changes: 0 additions & 12 deletions tests/crashes/133066.rs

This file was deleted.

11 changes: 0 additions & 11 deletions tests/crashes/133199.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#![feature(associated_const_equality)]

trait Owner<K> {
const C: K;
}
impl<K: ConstDefault> Owner<K> for () {
const C: K = K::DEFAULT;
}

trait ConstDefault {
const DEFAULT: Self;
}

fn user() -> impl Owner<dyn Sized, C = 0> {}
//~^ ERROR: the trait bound `(dyn Sized + 'static): ConstDefault` is not satisfied
//~| ERROR: the size for values of type `(dyn Sized + 'static)` cannot be known at compilation time
//~| ERROR: the trait `Sized` is not dyn compatible
//~| ERROR: mismatched types

fn main() {}
Loading

0 comments on commit 4cbf2ca

Please sign in to comment.