Change:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --I don't think it's specific to any code per se, but I'm seeing this just after modifying a (now) 245 line file and hitting "run test" in vscode, maybe at about the same time I hotkey'd "save"? See below for more info. This issue looks very similar to and maybe . I suspect all three could be addressed by adopting a strategy as described in <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: did make the error "go away," I believe this is just because it invalidated the cache. <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</detailsSo far I've reduced the state down to this project: . has so far reproduced the error 100% of the time for me, but it's highly sensitive to the fingerprint mechanism so changes to anything in there produces a project that builds just fine. Here's what I think is happening: kicks off an incremental compile, and starts checking fingerprints. order to build the dependency graph, it runs a "query" to determine part of executing that query, it loads a cached incremental compilation unit for that is out of sync with the current file (it refers to a point in time where the file was a few lines longer), which panics Since this appears to be happening in the "check what work to do" phase, never produces a new artifact that's in sync with the current state of the file, and either or forcing a full rebuild is necessary to get back into sync. The two questions I haven't gotten a good idea about are: does determine the last incremental build is valid to run queries against? It seems that in this case, it should prefer to look through the new file than treat the last session as the file is indeed changed. did I do to get the compiler into this state in the first place? Some details that I suspect may be relevant: I hit the "run test" inline helper in vscode, at about the same time as making other edits and re-saving the file I have vscode configured to format on save, which does something to modify the file either just before or just after saving I'm running on HFS on macOS, so my mtime timestamps are no more granular than 1 second apart <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
Looks the same as as well. Similar panic and backtrace.
claim
Thanks your project was instrumental in helping me find a repro. I'm still exploring this, but thought I'd post the analysis thus far, in case the root cause is obvious to anyone. The issue happens when you modify a function such that its span end position changes, without changing its start or length. When the optimizedmir query runs for this function, the cached result is used, but the result contains the incorrect span from the previous compilation session. This only manifests as an ICE when you modify the span such that the previous span end is now beyond the end of the new file. This is easiest to reproduce by removing new lines from a function at the end of a file and adding the same number of non-newlines: Before: fn main() { foo(); } fn foo() { println!("hi"); } After: fn main() { foo(); } fn foo() { println!("hi");// } The issues we've seen reported have been from functions, which produce slightly different backtraces because the attribute transforms the code. , , , , , and are all almost certainly the same issue, based on their backtraces. You can reproduce the version of the issue like so: Before: #[test] fn itworks() { asserteq!(2 + 2, 4); } After: #[test] fn itworks() { assert_eq!(2 + 2, 4);// } Note that you can completely change the content of the function in the "after" case (e.g. replace everything with spaces). The ICE will happen as long as the span start and length don't change. This doesn't mean that the function's old content is reused, though. I.e. if you change the function content, while causing the old span to be reused, but not in a way where the ICE manifests, the function's new content will still be used at runtime. I'm curious about the ramifications of the incorrect span being used when the ICE doesn't occur. As far as the concrete root cause goes, I'm still trying to figure that out. It seems to be that there is effectively only a dependency on the start and length of the span in certain cases. But I have yet to really dig into it.
Here's the root cause analysis, using the following example: Before: fn main() { foo(); } fn foo() { println!("hi"); } After: fn main() { foo(); } fn foo() { println!("hi");// } The ICE occurs when we try to deserialize and reuse the result of the optimizedmir(foo) query from the previous compilation session. In particular, when we try to deserialize from the result the span of the terminator of one of the basic blocks in the function body (call this span A). Span A starts and ends one character after the closing brace of the function. The line number contained in the previous span A isn't valid in the second compilation session. It's beyond the end of the file, so when we use it as an index into the file's lines vector, we panic. Ultimately, the problem is that Span's hash implementation does not take into account the line/column of the end of the span--only the start line/column and length. The ramifications of this for this example follow, working from the bottom up. When we change the span of foo's body (call this span B) as in the example, the result for hirowner(foo) (which is evalalways) has the same hash. The result contains the new span B, but the hash hasn't changed, because only its end line changed, not its start line/column or length. So hirowner(foo) is marked green. However, mirbuilt(foo) depends on hirowner(foo) in a way that depends specifically on the end line/column of span B--it derives span A from span B's end line/column. But in this case, the query system sees that all of mirbuilt(foo)'s dependencies are green, so it marks it green as well. It never calculates the new span A from the new span B. This is a time bomb, because the old span A is contained in mirbuilt(foo)'s stored result, and as soon as it's loaded, we're going to ICE. But as it happens, we don't try to load that result from disk. We've only marked mirbuilt(foo) green. Consequently, a chain of other queries is marked green, leading us finally to try to load the stored result of optimizedmir(foo). It also contains the old span A. But the old span A isn't valid. And in our case, it isn't valid in a way that causes an ICE. My proposed fix is to include end line/column information in span hashes. Any query which depends on the end line/column of a span returned by another query is susceptible to this kind of inconsistency otherwise. And adding the end line/column to the hash does in fact fix the problem. In the example, this causes hirowner(foo) to be marked red, causing mirbuilt(foo) to reexecute and produce the correct span A based on the new span B, and ultimately causing optimized_mir(foo) to not try to reuse its invalid result. I will put out a PR soon.

Discussion:
return; } <ins> let (_, line_hi, col_hi) = match ctx.byte_pos_to_line_and_col(span.hi) { Some(pos) => pos, None => { Hash::hash(&TAG_INVALID_SPAN, hasher); span.ctxt.hash_stable(ctx, hasher); return; } }; </ins> Hash::hash(&TAG_VALID_SPAN, hasher); // We truncate the stable ID hash and line and column numbers. The chances // of causing a collision this way should be minimal. Hash::hash(&(file_lo.name_hash as u64), hasher); <del> let col = (col_lo.0 as u64) & 0xFF; let line = ((line_lo as u64) & 0xFF_FF_FF) << 8; let len = ((span.hi - span.lo).0 as u64) << 32; let line_col_len = col | line | len; Hash::hash(&line_col_len, hasher); </del> <ins> // Hash both the length and the end location (line/column) of a span. If we // hash only the length, for example, then two otherwise equal spans with // different end locations will have the same hash. This can cause a problem // during incremental compilation wherein a previous result for a query that // depends on the end location of a span will be incorrectly reused when the // end location of the span it depends on has changed (see issue #74890). A // similar analysis applies if some query depends specifically on the length // of the span, but we only hash the end location. So hash both. let col_lo_trunc = (col_lo.0 as u64) & 0xFF; let line_lo_trunc = ((line_lo as u64) & 0xFF_FF_FF) << 8; let col_hi_trunc = (col_hi.0 as u64) & 0xFF << 32; let line_hi_trunc = ((line_hi as u64) & 0xFF_FF_FF) << 40; let col_line = col_lo_trunc | line_lo_trunc | col_hi_trunc | line_hi_trunc; let len = (span.hi - span.lo).0; Hash::hash(&col_line, hasher); Hash::hash(&len, hasher); </ins> span.ctxt.hash_stable(ctx, hasher); } }

<ins> include ../../run-make-fulldeps/tools.mk # FIXME https://github.com/rust-lang/rust/issues/78911 # ignore-32bit wrong/no cross compiler and sometimes we pass wrong gcc args (-m64) # Tests that we don't ICE during incremental compilation after modifying a # function span such that its previous end line exceeds the number of lines # in the new file, but its start line/column and length remain the same. SRC=$(TMPDIR)/src INCR=$(TMPDIR)/incr all: mkdir $(SRC) mkdir $(INCR) cp a.rs $(SRC)/main.rs $(RUSTC) -C incremental=$(INCR) $(SRC)/main.rs cp b.rs $(SRC)/main.rs $(RUSTC) -C incremental=$(INCR) $(SRC)/main.rs </ins>

<ins> fn main() { // foo must be used. foo(); } // For this test to operate correctly, foo's body must start on exactly the same // line and column and have the exact same length in bytes in a.rs and b.rs. In // a.rs, the body must end on a line number which does not exist in b.rs. // Basically, avoid modifying this file, including adding or removing whitespace! fn foo() { assert_eq!(1, 1); } </ins>

<ins> fn main() { // foo must be used. foo(); } // For this test to operate correctly, foo's body must start on exactly the same // line and column and have the exact same length in bytes in a.rs and b.rs. In // a.rs, the body must end on a line number which does not exist in b.rs. // Basically, avoid modifying this file, including adding or removing whitespace! fn foo() { assert_eq!(1, 1);//// } </ins>

include ../../run-make-fulldeps/tools.mk <ins> # FIXME https://github.com/rust-lang/rust/issues/78911 </ins> # ignore-32bit wrong/no cross compiler and sometimes we pass wrong gcc args (-m64) all: foo