-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement dataflow-based const validation #64470
Implement dataflow-based const validation #64470
Conversation
This comment has been minimized.
This comment has been minimized.
r? @eddyb |
This comment has been minimized.
This comment has been minimized.
17b52c5
to
797a2c2
Compare
This comment has been minimized.
This comment has been minimized.
797a2c2
to
691ca56
Compare
This comment has been minimized.
This comment has been minimized.
91ff10a
to
64afa84
Compare
This comment has been minimized.
This comment has been minimized.
64afa84
to
3400462
Compare
This comment has been minimized.
This comment has been minimized.
3400462
to
abd2034
Compare
This comment has been minimized.
This comment has been minimized.
abd2034
to
5e93494
Compare
This comment has been minimized.
This comment has been minimized.
5e93494
to
fe0d220
Compare
This comment has been minimized.
This comment has been minimized.
3b1e7e5
to
0e431ee
Compare
I've cleaned up the history a bit, and I'm happy with the current state of this. I'm removing the draft status, although there's a few decisions that need to be made before this is actually merged. This PR now allows validation to proceed in two modes, controlled by If Also, cc @oli-obk @eddyb This is designed to be reviewed commit-by-commit which should make things a bit more manageable. |
this PR cause large perf regression see perf |
This is expected since we're running old and new and comparing the result. When we remove the old check we should get perf back |
Why would this PR affect |
…=oli-obk Enable support for `IndirectlyMutableLocals` in `rustc_peek` This PR allows `rustc_peek` tests to be written for the `IndirectlyMutableLocals` analysis implemented in rust-lang#64470. See any of the tests in [`test/ui/mir-dataflow`](https://github.com/rust-lang/rust/blob/master/src/test/ui/mir-dataflow/inits-1.rs) for an example. Included in this PR is a major rewrite of the `rustc_peek` module. This was motivated by the differences between the `IndirectlyMutableLocals` analysis and the initialized places ones. To properly test `IndirectlyMutableLocals`, we must pass locals by-value to `rustc_peek`, since any local that is not `Freeze` will be marked as indirectly mutable as soon as a reference to it is taken. Unfortunately, `UnsafeCell` is not `Copy`, so we can only do one `rustc_peek` on each value with interior mutability inside a test. I'm not sure how to deal with this restriction; perhaps I need to special case borrows preceding a call to `rustc_peek` in the analysis itself? `rustc_peek` also assumed that the analysis was done on move paths and that its transfer function only needed to be applied at assignment statements. This PR removes both of those restrictions by adding a trait, `RustcPeekAt`, that controls how the peeked at `Place` maps to the current dataflow state and using a dataflow cursor to retrieve the state itself. Finally, this PR adds a test which demonstrates some unsoundness in the `IndirectlyMutableLocals` analysis by converting a reference to a `Freeze` field to a reference to a `!Freeze` field by offsetting a pointer (or in this case transmuting a pointer to a ZST field with the same address as a `!Freeze` field). This does not represent a hole in the language proper, since this analysis is only used to validate `const` bodies, in which the unsound code will only compile with `-Zunleash-the-miri-inside-of-you`. Nevertheless, this should get fixed. r? @oli-obk
I assume somebody has checked, but just in case not: does this affect the (cc @nvzqz ) |
It currently expects const eval for array size overflow to fail. But even if that changes to allow an overflow, there’d still be a type mismatch. |
Any stable behaviour won't be changed by anything we implement. The only thing that happens is that unstable behaviour or unimplemented behaviour gets stabilized. If a macro were to expand to The behaviour that the |
Once However, once I'm totally fine with breaking changes in |
They're working to make compile-time evaluation more expressive by enabling `if`, `match` and other control flow in constants. As one of their first major contributions, they implemented a dataflow analysis to validate the bodies of `const`s and `const fn`s (rust-lang/rust#64470).
Enable `if` and `match` in constants behind a feature flag This PR is an initial implementation of #49146. It introduces a `const_if_match` feature flag and does the following if it is enabled: - Allows `Downcast` projections, `SwitchInt` terminators and `FakeRead`s for matched places through the MIR const-checker. - Allows `if` and `match` expressions through the HIR const-checker. - Stops converting `&&` to `&` and `||` to `|` in `const` and `static` items. As a result, the following operations are now allowed in a const context behind the feature flag: - `if` and `match` - short circuiting logic operators (`&&` and `||`) - the `assert` and `debug_assert` macros (if the `const_panic` feature flag is also enabled) However, the following operations remain forbidden: - `while`, `loop` and `for` (see #52000) - the `?` operator (calls `From::from` on its error variant) - the `assert_eq` and `assert_ne` macros, along with their `debug` variants (calls `fmt::Debug`) This PR is possible now that we use dataflow for const qualification (see #64470 and #66385). r? @oli-obk cc @rust-lang/wg-const-eval @eddyb
…eddyb,matthewjasper Handle const-checks for `&mut` outside of `HasMutInterior` Addresses [this comment](rust-lang#64470 (comment)). Const-checking relied on `HasMutInterior` to forbid `&mut` in a const context. This was strange because all we needed to do was look for an `Rvalue::Ref` with a certain `BorrowKind`, whereas the `Qualif` traits are specifically meant to get the qualifs for a *value*. This PR removes that logic from `HasMutInterior` and moves it into `check_consts::Validator`. As a result, we can now properly handle qualifications for `static`s, which had to be ignored previously since you can e.g. borrow a static `Cell` from another `static`. We also remove the `derived_from_illegal_borrow` logic, since it is no longer necessary; we give good errors for subsequent reborrows/borrows of illegal borrows.
/// A function call where the callee is not a function definition or function pointer, e.g. a | ||
/// closure. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am very confused by this comment. Syntactically, in MIR only functions and function pointers can be called -- so "where the callee is not a function definition or function pointer" seems just impossible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bjorn3's (deleted? it's rather confusing to get a notification and then not see the comment, please edit it but don't delete) comment made me realize that maybe I got the parentheses here wrong -- I thought this was "or (function pointer, e.g. closure)", but I suppose closure is meant as an example for other callees here? But closures are not called directly, they are called via the Fn*
traits. Or does this run on a variant of MIR where things have not been lowered yet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But closures are not called directly, they are called via the Fn* traits.
Realized that immediately after I commented.
return; | ||
} | ||
_ => { | ||
self.check_op(ops::FnCallOther); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Callees must always be FnPtr
or FnDef
(Miri will ICE when it sees anything else), how is this here even reachable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This const-checking pass has structured errors everywhere the old pass had a call to span_err
. If you would like to change this, it would be nice to encode the set of types that can appear as the func
operand of a Call
terminator in an enum
somewhere so we could use exhaustive matching in more places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be nice to encode the set of types that can appear as the func operand of a Call terminator in an enum somewhere so we could use exhaustive matching in more places.
This is basically suggesting "intrinsically-typed MIR", where invalid MIR cannot even be constructed. I am not sure what the best way would be to do that in Rust (I only know how to do it with dependent types). Calls are not the only operations this affects, tons of MIR operations have invariants for which types can occur -- arithmetic primitives, casts, pointer dereferences, ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just meant an as_func_ty
combinator for Ty
whose return value is restricted to the types that are valid as a func
. It's still up to the user to call this in the correct places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just meant that there are more "subclasses" of types than "callable types", and some of them overlap. I don't think we should adopt something ad-hoc that only works for callable types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotta start somewhere. What do we do for closures btw? Do these get lowered to FnDef
s by this point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotta start somewhere.
I'd rather just remove this dead code for now, and leave improving MIR itself for later. Do you want to track this in an issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closures do not get called directly. Instead FnOnce::call_once
(or the other Fn*
traits) gets called, which is a FnDef
. The ty::Closure
is then passed as self
argument.
See for example the MIR of this function.
…=oli-obk Only run dataflow for const qualification if type-based check would fail This is the optimization discussed in rust-lang#49146 (comment). We wait for `Qualif::in_any_value_of_ty` to return `true` before running dataflow. For bodies that deal mostly with primitive types, this will avoid running dataflow at all during const qualification. This also removes the `BitSet` used to cache `in_any_value_of_ty` for each local, which was only necessary for an old version of rust-lang#64470 that also handled promotability.
…=oli-obk Only run dataflow for const qualification if type-based check would fail This is the optimization discussed in rust-lang#49146 (comment). We wait for `Qualif::in_any_value_of_ty` to return `true` before running dataflow. For bodies that deal mostly with primitive types, this will avoid running dataflow at all during const qualification. This also removes the `BitSet` used to cache `in_any_value_of_ty` for each local, which was only necessary for an old version of rust-lang#64470 that also handled promotability.
This PR adds a separate, dataflow-enabled pass that checks the bodies of
const
s,static
s andconst fn
s for const safety. This is based on my work in #63860, which tried to integrate into the existing pass inqualify_consts.rs
. However, the resulting pass was even more unwieldy than the original. Unlike its predecessor, this PR is designed to be combined with #63812 to replace the existing pass completely.The new checker lives in
librustc_mir/transform/check_consts
.qualifs.rs
contains small modifications to the existingQualif
trait and its implementors, but is mostly unchanged except for the removal ofIsNotPromotable
andIsNotImplicitlyPromotable
, which are only necessary for promotion.resolver.rs
contains the dataflow analysis used to propagate qualifs between locals.Finally,
validation.rs
contains a refactored version of the existingVisitor
inqualfy_consts.rs
. All errors have been associated with astruct
to make comparison with the existing pass simple.The existing validation logic in
qualify_consts
has been modified to allow it to run in parallel with the new validator. Ifuse_new_validator
is not set, the old validation will be responsible for actually generating the errors, but those errors can be compared with the ones from the new validator.