Rollup merge of #76807 - ecstatic-morse:const-checking-staged-api, r=oli-obk

Use const-checking to forbid use of unstable features in const-stable functions

First step towards #76618.

Currently this code isn't ever hit because `qualify_min_const_fn` runs first and catches pretty much everything. One exception is `const_precise_live_drops`, which does not use the newly added code since it runs as part of a separate pass.

Also contains some unrelated refactoring, which is split into separate commits.

r? @oli-obk
This commit is contained in:
ecstatic-morse 2020-09-21 20:40:53 -07:00 committed by GitHub
commit 60b99015e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 172 additions and 71 deletions

View File

@ -4,10 +4,12 @@
//! has interior mutability or needs to be dropped, as well as the visitor that emits errors when
//! it finds operations that are invalid in a certain context.
use rustc_attr as attr;
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_middle::mir;
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::Symbol;
pub use self::qualifs::Qualif;
@ -55,3 +57,9 @@ impl ConstCx<'mir, 'tcx> {
pub fn is_lang_panic_fn(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool {
Some(def_id) == tcx.lang_items().panic_fn() || Some(def_id) == tcx.lang_items().begin_panic_fn()
}
pub fn allow_internal_unstable(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool {
let attrs = tcx.get_attrs(def_id);
attr::allow_internal_unstable(&tcx.sess, attrs)
.map_or(false, |mut features| features.any(|name| name == feature_gate))
}

View File

@ -1,6 +1,6 @@
//! Concrete error types for all operations which may be invalid in a certain const context.
use rustc_errors::struct_span_err;
use rustc_errors::{struct_span_err, Applicability};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_session::config::nightly_options;
@ -14,35 +14,54 @@ use super::ConstCx;
pub fn non_const<O: NonConstOp>(ccx: &ConstCx<'_, '_>, op: O, span: Span) {
debug!("illegal_op: op={:?}", op);
if op.is_allowed_in_item(ccx) {
return;
}
let gate = match op.status_in_item(ccx) {
Status::Allowed => return,
Status::Unstable(gate) if ccx.tcx.features().enabled(gate) => {
let unstable_in_stable = ccx.const_kind() == hir::ConstContext::ConstFn
&& ccx.tcx.features().enabled(sym::staged_api)
&& !ccx.tcx.has_attr(ccx.def_id.to_def_id(), sym::rustc_const_unstable)
&& !super::allow_internal_unstable(ccx.tcx, ccx.def_id.to_def_id(), gate);
if unstable_in_stable {
ccx.tcx.sess
.struct_span_err(span, &format!("`#[feature({})]` cannot be depended on in a const-stable function", gate.as_str()))
.span_suggestion(
ccx.body.span,
"if it is not part of the public API, make this function unstably const",
concat!(r#"#[rustc_const_unstable(feature = "...", issue = "...")]"#, '\n').to_owned(),
Applicability::HasPlaceholders,
)
.help("otherwise `#[allow_internal_unstable]` can be used to bypass stability checks")
.emit();
}
return;
}
Status::Unstable(gate) => Some(gate),
Status::Forbidden => None,
};
if ccx.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
ccx.tcx.sess.miri_unleashed_feature(span, O::feature_gate());
ccx.tcx.sess.miri_unleashed_feature(span, gate);
return;
}
op.emit_error(ccx, span);
}
pub enum Status {
Allowed,
Unstable(Symbol),
Forbidden,
}
/// An operation that is not *always* allowed in a const context.
pub trait NonConstOp: std::fmt::Debug {
/// Returns the `Symbol` corresponding to the feature gate that would enable this operation,
/// or `None` if such a feature gate does not exist.
fn feature_gate() -> Option<Symbol> {
None
}
/// Returns `true` if this operation is allowed in the given item.
///
/// This check should assume that we are not in a non-const `fn`, where all operations are
/// legal.
///
/// By default, it returns `true` if and only if this operation has a corresponding feature
/// gate and that gate is enabled.
fn is_allowed_in_item(&self, ccx: &ConstCx<'_, '_>) -> bool {
Self::feature_gate().map_or(false, |gate| ccx.tcx.features().enabled(gate))
/// Returns an enum indicating whether this operation is allowed within the given item.
fn status_in_item(&self, _ccx: &ConstCx<'_, '_>) -> Status {
Status::Forbidden
}
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@ -53,9 +72,13 @@ pub trait NonConstOp: std::fmt::Debug {
"{} contains unimplemented expression type",
ccx.const_kind()
);
if let Some(feat) = Self::feature_gate() {
err.help(&format!("add `#![feature({})]` to the crate attributes to enable", feat));
if let Status::Unstable(gate) = self.status_in_item(ccx) {
if !ccx.tcx.features().enabled(gate) && nightly_options::is_nightly_build() {
err.help(&format!("add `#![feature({})]` to the crate attributes to enable", gate));
}
}
if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
err.note(
"A function call isn't allowed in the const's initialization expression \
@ -147,7 +170,9 @@ pub struct InlineAsm;
impl NonConstOp for InlineAsm {}
#[derive(Debug)]
pub struct LiveDrop(pub Option<Span>);
pub struct LiveDrop {
pub dropped_at: Option<Span>,
}
impl NonConstOp for LiveDrop {
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
let mut diagnostic = struct_span_err!(
@ -157,7 +182,7 @@ impl NonConstOp for LiveDrop {
"destructors cannot be evaluated at compile-time"
);
diagnostic.span_label(span, format!("{}s cannot evaluate destructors", ccx.const_kind()));
if let Some(span) = self.0 {
if let Some(span) = self.dropped_at {
diagnostic.span_label(span, "value is dropped here");
}
diagnostic.emit();
@ -182,14 +207,13 @@ impl NonConstOp for CellBorrow {
#[derive(Debug)]
pub struct MutBorrow;
impl NonConstOp for MutBorrow {
fn is_allowed_in_item(&self, ccx: &ConstCx<'_, '_>) -> bool {
// Forbid everywhere except in const fn
ccx.const_kind() == hir::ConstContext::ConstFn
&& ccx.tcx.features().enabled(Self::feature_gate().unwrap())
}
fn feature_gate() -> Option<Symbol> {
Some(sym::const_mut_refs)
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
// Forbid everywhere except in const fn with a feature gate
if ccx.const_kind() == hir::ConstContext::ConstFn {
Status::Unstable(sym::const_mut_refs)
} else {
Status::Forbidden
}
}
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@ -201,15 +225,16 @@ impl NonConstOp for MutBorrow {
&format!("mutable references are not allowed in {}s", ccx.const_kind()),
)
} else {
struct_span_err!(
let mut err = struct_span_err!(
ccx.tcx.sess,
span,
E0764,
"mutable references are not allowed in {}s",
ccx.const_kind(),
)
);
err.span_label(span, format!("`&mut` is only allowed in `const fn`"));
err
};
err.span_label(span, "`&mut` is only allowed in `const fn`".to_string());
if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
err.note(
"References in statics and constants may only refer \
@ -226,11 +251,17 @@ impl NonConstOp for MutBorrow {
}
}
// FIXME(ecstaticmorse): Unify this with `MutBorrow`. It has basically the same issues.
#[derive(Debug)]
pub struct MutAddressOf;
impl NonConstOp for MutAddressOf {
fn feature_gate() -> Option<Symbol> {
Some(sym::const_mut_refs)
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
// Forbid everywhere except in const fn with a feature gate
if ccx.const_kind() == hir::ConstContext::ConstFn {
Status::Unstable(sym::const_mut_refs)
} else {
Status::Forbidden
}
}
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@ -247,16 +278,16 @@ impl NonConstOp for MutAddressOf {
#[derive(Debug)]
pub struct MutDeref;
impl NonConstOp for MutDeref {
fn feature_gate() -> Option<Symbol> {
Some(sym::const_mut_refs)
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
Status::Unstable(sym::const_mut_refs)
}
}
#[derive(Debug)]
pub struct Panic;
impl NonConstOp for Panic {
fn feature_gate() -> Option<Symbol> {
Some(sym::const_panic)
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
Status::Unstable(sym::const_panic)
}
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@ -289,8 +320,8 @@ impl NonConstOp for RawPtrComparison {
#[derive(Debug)]
pub struct RawPtrDeref;
impl NonConstOp for RawPtrDeref {
fn feature_gate() -> Option<Symbol> {
Some(sym::const_raw_ptr_deref)
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
Status::Unstable(sym::const_raw_ptr_deref)
}
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@ -307,8 +338,8 @@ impl NonConstOp for RawPtrDeref {
#[derive(Debug)]
pub struct RawPtrToIntCast;
impl NonConstOp for RawPtrToIntCast {
fn feature_gate() -> Option<Symbol> {
Some(sym::const_raw_ptr_to_usize_cast)
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
Status::Unstable(sym::const_raw_ptr_to_usize_cast)
}
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@ -326,8 +357,12 @@ impl NonConstOp for RawPtrToIntCast {
#[derive(Debug)]
pub struct StaticAccess;
impl NonConstOp for StaticAccess {
fn is_allowed_in_item(&self, ccx: &ConstCx<'_, '_>) -> bool {
matches!(ccx.const_kind(), hir::ConstContext::Static(_))
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
if let hir::ConstContext::Static(_) = ccx.const_kind() {
Status::Allowed
} else {
Status::Forbidden
}
}
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@ -371,14 +406,13 @@ impl NonConstOp for ThreadLocalAccess {
#[derive(Debug)]
pub struct UnionAccess;
impl NonConstOp for UnionAccess {
fn is_allowed_in_item(&self, ccx: &ConstCx<'_, '_>) -> bool {
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
// Union accesses are stable in all contexts except `const fn`.
ccx.const_kind() != hir::ConstContext::ConstFn
|| ccx.tcx.features().enabled(Self::feature_gate().unwrap())
}
fn feature_gate() -> Option<Symbol> {
Some(sym::const_fn_union)
if ccx.const_kind() != hir::ConstContext::ConstFn {
Status::Allowed
} else {
Status::Unstable(sym::const_fn_union)
}
}
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {

View File

@ -2,7 +2,7 @@ use rustc_hir::def_id::LocalDefId;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::{self, BasicBlock, Location};
use rustc_middle::ty::TyCtxt;
use rustc_span::Span;
use rustc_span::{sym, Span};
use super::ops;
use super::qualifs::{NeedsDrop, Qualif};
@ -11,7 +11,12 @@ use super::ConstCx;
/// Returns `true` if we should use the more precise live drop checker that runs after drop
/// elaboration.
pub fn checking_enabled(tcx: TyCtxt<'tcx>) -> bool {
pub fn checking_enabled(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> bool {
// Const-stable functions must always use the stable live drop checker.
if tcx.features().staged_api && !tcx.has_attr(def_id.to_def_id(), sym::rustc_const_unstable) {
return false;
}
tcx.features().const_precise_live_drops
}
@ -25,7 +30,7 @@ pub fn check_live_drops(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &mir::Body<
return;
}
if !checking_enabled(tcx) {
if !checking_enabled(tcx, def_id) {
return;
}
@ -52,7 +57,7 @@ impl std::ops::Deref for CheckLiveDrops<'mir, 'tcx> {
impl CheckLiveDrops<'mir, 'tcx> {
fn check_live_drop(&self, span: Span) {
ops::non_const(self.ccx, ops::LiveDrop(None), span);
ops::non_const(self.ccx, ops::LiveDrop { dropped_at: None }, span);
}
}

View File

@ -551,7 +551,7 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> {
| TerminatorKind::DropAndReplace { place: dropped_place, .. } => {
// If we are checking live drops after drop-elaboration, don't emit duplicate
// errors here.
if super::post_drop_elaboration::checking_enabled(self.tcx) {
if super::post_drop_elaboration::checking_enabled(self.tcx, self.def_id) {
return;
}
@ -576,7 +576,7 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> {
if needs_drop {
self.check_op_spanned(
ops::LiveDrop(Some(terminator.source_info.span)),
ops::LiveDrop { dropped_at: Some(terminator.source_info.span) },
err_span,
);
}

View File

@ -1,4 +1,3 @@
use rustc_attr as attr;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::*;
@ -344,8 +343,7 @@ fn feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bo
// However, we cannot allow stable `const fn`s to use unstable features without an explicit
// opt-in via `allow_internal_unstable`.
attr::allow_internal_unstable(&tcx.sess, &tcx.get_attrs(def_id))
.map_or(false, |mut features| features.any(|name| name == feature_gate))
super::check_consts::allow_internal_unstable(tcx, def_id, feature_gate)
}
/// Returns `true` if the given library feature gate is allowed within the function with the given `DefId`.
@ -364,8 +362,7 @@ pub fn lib_feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbo
// However, we cannot allow stable `const fn`s to use unstable features without an explicit
// opt-in via `allow_internal_unstable`.
attr::allow_internal_unstable(&tcx.sess, &tcx.get_attrs(def_id))
.map_or(false, |mut features| features.any(|name| name == feature_gate))
super::check_consts::allow_internal_unstable(tcx, def_id, feature_gate)
}
fn check_terminator(

View File

@ -21,7 +21,7 @@ help: skipping check for `const_mut_refs` feature
|
LL | &mut *(box 0)
| ^^^^^^^^^^^^^
help: skipping check for `const_mut_refs` feature
help: skipping check that does not even have a feature gate
--> $DIR/box.rs:10:5
|
LL | &mut *(box 0)

View File

@ -6,17 +6,17 @@ LL | *OH_YES = 99;
warning: skipping const checks
|
help: skipping check for `const_mut_refs` feature
help: skipping check that does not even have a feature gate
--> $DIR/mutable_references.rs:9:26
|
LL | static FOO: &&mut u32 = &&mut 42;
| ^^^^^^^
help: skipping check for `const_mut_refs` feature
help: skipping check that does not even have a feature gate
--> $DIR/mutable_references.rs:13:23
|
LL | static BAR: &mut () = &mut ();
| ^^^^^^^
help: skipping check for `const_mut_refs` feature
help: skipping check that does not even have a feature gate
--> $DIR/mutable_references.rs:18:28
|
LL | static BOO: &mut Foo<()> = &mut Foo(());
@ -26,7 +26,7 @@ help: skipping check that does not even have a feature gate
|
LL | x: &UnsafeCell::new(42),
| ^^^^^^^^^^^^^^^^^^^^
help: skipping check for `const_mut_refs` feature
help: skipping check that does not even have a feature gate
--> $DIR/mutable_references.rs:30:27
|
LL | static OH_YES: &mut i32 = &mut 42;

View File

@ -30,7 +30,7 @@ help: skipping check that does not even have a feature gate
|
LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: skipping check for `const_mut_refs` feature
help: skipping check that does not even have a feature gate
--> $DIR/mutable_references_err.rs:30:25
|
LL | const BLUNT: &mut i32 = &mut 42;

View File

@ -0,0 +1,22 @@
#![stable(feature = "core", since = "1.6.0")]
#![feature(staged_api)]
#![feature(const_precise_live_drops, const_fn)]
enum Either<T, S> {
Left(T),
Right(S),
}
impl<T> Either<T, T> {
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "foo", since = "1.0.0")]
pub const fn unwrap(self) -> T {
//~^ ERROR destructors cannot be evaluated at compile-time
match self {
Self::Left(t) => t,
Self::Right(t) => t,
}
}
}
fn main() {}

View File

@ -0,0 +1,12 @@
error[E0493]: destructors cannot be evaluated at compile-time
--> $DIR/stable-precise-live-drops-in-libcore.rs:13:25
|
LL | pub const fn unwrap(self) -> T {
| ^^^^ constant functions cannot evaluate destructors
...
LL | }
| - value is dropped here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0493`.

View File

@ -0,0 +1,23 @@
// check-pass
#![stable(feature = "core", since = "1.6.0")]
#![feature(staged_api)]
#![feature(const_precise_live_drops)]
enum Either<T, S> {
Left(T),
Right(S),
}
impl<T> Either<T, T> {
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "foo", issue = "none")]
pub const fn unwrap(self) -> T {
match self {
Self::Left(t) => t,
Self::Right(t) => t,
}
}
}
fn main() {}