Skip to content
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

Loosened rules involving statics mentioning other statics #51110

Merged
merged 8 commits into from
Jul 2, 2018

Conversation

alexreg
Copy link
Contributor

@alexreg alexreg commented May 27, 2018

Before this PR, trying to mention a static in any way other than taking a reference to it caused a compile-time error. So, while

static A: u32 = 42;
static B: &u32 = &A;

compiles successfully,

static A: u32 = 42;
static B: u32 = A; // error

and

static A: u32 = 42;
static B: u32 = *&A; // error

are not possible to express in Rust. On the other hand, introducing an intermediate const fn can presently allow one to do just that:

static A: u32 = 42;
static B: u32 = foo(&A); // success!

const fn foo(a: &u32) -> u32 {
    *a
}

Preventing const fn from allowing to work around the ban on reading from statics would cripple const fn almost into uselessness.
Additionally, the limitation for reading from statics comes from the old const evaluator(s) and is not shared by miri.

This PR loosens the rules around use of statics to allow statics to evaluate other statics by value, allowing all of the above examples to compile and run successfully.
Reads from extern (foreign) statics are however still disallowed by miri, because there is no compile-time value to be read.

extern static A: u32;

static B: u32 = A; // error

This opens up a new avenue of potential issues, as a static can now not just refer to other statics or read from other statics, but even contain references that point into itself.
While it might seem like this could cause subtle bugs like allowing a static to be initialized by its own value, this is inherently impossible in miri.
Reading from a static causes the const_eval query for that static to be invoked. Calling the const_eval query for a static while already inside the const_eval query of said static will cause cycle errors.
It is not possible to accidentally create a bug in miri that would enable initializing a static with itself, because the memory of the static does not exist while being initialized.
The memory is not uninitialized, it is not there. Thus any change that would accidentally allow reading from a not yet initialized static would cause ICEs.

Tests have been modified according to the new rules, and new tests have been added for writing to static muts within definitions of statics (which needs to fail), and incremental compilation with complex/interlinking static definitions.
Note that incremental compilation did not need to be adjusted, because all of this was already possible before with workarounds (like intermediate const fns) and the encoding/decoding already supports all the possible cases.

r? @eddyb

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 27, 2018
@@ -228,38 +222,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {

/// Check if a Local with the current qualifications is promotable.
fn can_promote(&self, qualif: Qualif) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you inline can_promote now?

@@ -492,7 +455,9 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
match *place {
Place::Local(ref local) => self.visit_local(local, context, location),
Place::Static(ref global) => {
self.add(Qualif::STATIC);
if !(self.mode == Mode::Static || self.mode == Mode::StaticMut) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment above this if saying that only statics can refer to other statics?

@@ -677,16 +618,11 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
Rvalue::Len(_) => {
// Static places in consts would have errored already,
// don't treat length checks as reads from statics.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also remove the comment here.

@@ -677,16 +618,11 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
Rvalue::Len(_) => {
// Static places in consts would have errored already,
// don't treat length checks as reads from statics.
self.qualif = self.qualif - Qualif::STATIC;
}

Rvalue::Ref(_, kind, ref place) => {
// Static places in consts would have errored already,
// only keep track of references to them here.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also remove the comment here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, yep. Should have paid more attention to the comments!

@eddyb
Copy link
Member

eddyb commented May 27, 2018

cc @oli-obk @nikomatsakis This PR allows statics to read from other statics (although miri may still error as before, when the operation is actually attempted), which was sort of already possible:

const fn read<T: Copy>(r: &T) -> T { *r }
static FOO: u32 = read(&BAR);
static BAR: u32: 10;

That is, the analysis being removed here was an unsound approximation.
However, const and const fn still cannot even mention a static, neither for borrowing or reading.

In terms of motivation, simplifying the analysis qualify_consts performs will allow us to move it to the dataflow framework to support arbitrary CFG shapes, not just linear control-flow.
Does this PR require going through FCP or even an RFC?

@rust-highfive

This comment has been minimized.

@alexreg

This comment has been minimized.

} else {
"cannot refer to statics by value, use a constant instead"
};
struct_span_err!(self.tcx.sess, self.span, E0394, "{}", msg)

This comment was marked as resolved.

This comment was marked as resolved.

@@ -566,12 +518,6 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {

ProjectionElem::Field(..) |
ProjectionElem::Index(_) => {
if this.mode != Mode::Fn &&
this.qualif.intersects(Qualif::STATIC) {
span_err!(this.tcx.sess, this.span, E0494,

This comment was marked as resolved.

@rust-highfive

This comment has been minimized.

@rust-highfive

This comment has been minimized.

@@ -305,7 +263,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
}) if self.mir.local_kind(index) == LocalKind::Temp
&& self.mir.local_decls[index].ty.is_box()
&& self.local_qualif[index].map_or(false, |qualif| {
qualif.intersects(Qualif::NOT_CONST)
qualif.contains(Qualif::NOT_CONST)

This comment was marked as resolved.

@@ -416,7 +374,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {

// Account for errors in consts by using the
// conservative type qualification instead.
if self.qualif.intersects(Qualif::CONST_ERROR) {
if self.qualif.contains(Qualif::CONST_ERROR) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I just checked, and using contains is fine except for CONST_ERROR because it's a union of several other flags, and any of the flags in that union being set are relevant.

@@ -1026,7 +951,7 @@ This does not pose a problem by itself because they can't be accessed directly."

if let Some((ref dest, _)) = *destination {
// Avoid propagating irrelevant callee/argument qualifications.
if self.qualif.intersects(Qualif::CONST_ERROR) {
if self.qualif.contains(Qualif::CONST_ERROR) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, needs intersects.

@alexreg

This comment has been minimized.

@rust-highfive

This comment has been minimized.

@rust-highfive

This comment has been minimized.

@rust-highfive

This comment has been minimized.

@alexreg

This comment has been minimized.

@rust-highfive

This comment has been minimized.

self.add(Qualif::NOT_CONST);
return;
}
let static_kind = if self.tcx.is_foreign_item(global.def_id) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/static_kind/runtime_static

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure static_kind makes the most sense here... it's not a boolean, it's an Option<&'static str>.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is irrelevant anyway now since we can't actually deny anything here.

@@ -29,5 +29,6 @@ extern "C" {
}

static B: &'static c_int = unsafe { &test_static };
//~^ ERROR foreign statics cannot be accessed at compile-time
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, this is correct... I think we have to allow it, and instead make miri disallow reads from them. Sorry about the detour, I didn't realize borrowing extern static was already allowed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, no worries.

@kennytm kennytm added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 25, 2018
@bors
Copy link
Contributor

bors commented Jun 29, 2018

☔ The latest upstream changes (presumably #51884) made this pull request unmergeable. Please resolve the merge conflicts.

@alexreg
Copy link
Contributor Author

alexreg commented Jun 30, 2018

@eddyb What caused the latest Travis failures? Did something change in master when I rebased? (Because everything was passing before the clean-up, i.e. last commit.) I'm wary of simply hanging the expected error messages.

@oli-obk
Copy link
Contributor

oli-obk commented Jun 30, 2018

@alexreg due to conflicts you need to rebase anyway. Rerun the test suite after you do, it looks like master changed indeed. I did recently change something around error reporting, so I think you can just adjust the error message comments to fit reality

@alexreg
Copy link
Contributor Author

alexreg commented Jun 30, 2018

@oli-obk Okay, will do.

@alexreg
Copy link
Contributor Author

alexreg commented Jul 1, 2018

@oli-obk Rebase didn't help. I fixed the errors now though. If you see the last commit, you'll notice the need to duplicate the error messages though. I wonder if this is a bug.

@@ -13,6 +13,10 @@
extern {
pub static symbol: ();
}
static CRASH: () = symbol; //~ cannot refer to other statics by value
static CRASH: () = symbol;
//~^ ERROR could not evaluate static initializer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open an issue about it. this feels like a regression by my diagnostics changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Or do you want to, since it's your changes? Either way.

@oli-obk
Copy link
Contributor

oli-obk commented Jul 1, 2018

@bors r=eddyb

@bors
Copy link
Contributor

bors commented Jul 1, 2018

📌 Commit 1c34227 has been approved by eddyb

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jul 1, 2018
@bors
Copy link
Contributor

bors commented Jul 1, 2018

⌛ Testing commit 1c34227 with merge c697a56...

bors added a commit that referenced this pull request Jul 1, 2018
Loosened rules involving statics mentioning other statics

Before this PR, trying to mention a static in any way other than taking a reference to it caused a compile-time error. So, while

```rust
static A: u32 = 42;
static B: &u32 = &A;
```

compiles successfully,

```rust
static A: u32 = 42;
static B: u32 = A; // error
```

and

```rust
static A: u32 = 42;
static B: u32 = *&A; // error
```

are not possible to express in Rust. On the other hand, introducing an intermediate `const fn` can presently allow one to do just that:

```rust
static A: u32 = 42;
static B: u32 = foo(&A); // success!

const fn foo(a: &u32) -> u32 {
    *a
}
```

Preventing `const fn` from allowing to work around the ban on reading from statics would cripple `const fn` almost into uselessness.
Additionally, the limitation for reading from statics comes from the old const evaluator(s) and is not shared by `miri`.

This PR loosens the rules around use of statics to allow statics to evaluate other statics by value, allowing all of the above examples to compile and run successfully.
Reads from extern (foreign) statics are however still disallowed by miri, because there is no compile-time value to be read.

```rust
extern static A: u32;

static B: u32 = A; // error
```

This opens up a new avenue of potential issues, as a static can now not just refer to other statics or read from other statics, but even contain references that point into itself.
While it might seem like this could cause subtle bugs like allowing a static to be initialized by its own value, this is inherently impossible in miri.
Reading from a static causes the `const_eval` query for that static to be invoked. Calling the `const_eval` query for a static while already inside the `const_eval` query of said static will cause cycle errors.
It is not possible to accidentally create a bug in miri that would enable initializing a static with itself, because the memory of the static *does not exist* while being initialized.
The memory is not uninitialized, it is not there. Thus any change that would accidentally allow reading from a not yet initialized static would cause ICEs.

Tests have been modified according to the new rules, and new tests have been added for writing to `static mut`s within definitions of statics (which needs to fail), and incremental compilation with complex/interlinking static definitions.
Note that incremental compilation did not need to be adjusted, because all of this was already possible before with workarounds (like intermediate `const fn`s) and the encoding/decoding already supports all the possible cases.

r? @eddyb
@bors
Copy link
Contributor

bors commented Jul 2, 2018

☀️ Test successful - status-appveyor, status-travis
Approved by: eddyb
Pushing c697a56 to master...

@bors bors merged commit 1c34227 into rust-lang:master Jul 2, 2018
Mark-Simulacrum added a commit to Mark-Simulacrum/rust that referenced this pull request Jul 2, 2018
…ification, r=oli-obk

Removed `uninitialized_statics` field from `Memory` struct in miri

**IMPORTANT: Do not merge until rust-lang#51110 is merged**

r? @oli-obk

CC @eddyb
bors added a commit that referenced this pull request Jul 3, 2018
…r=oli-obk

Removed `uninitialized_statics` field from `Memory` struct in miri

based on #51110

r? @oli-obk

CC @eddyb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.