-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Incremental build panic running cargo test
#74890
Comments
Looks the same as #73640 as well. Similar panic and backtrace. |
@rustbot claim |
Thanks @sethp, 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 optimized_mir 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:
After:
The issues we've seen reported have been from Before:
After:
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:
After:
The ICE occurs when we try to deserialize and reuse the result of the optimized_mir(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 hir_owner(foo) (which is eval_always) 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 hir_owner(foo) is marked green. However, mir_built(foo) depends on hir_owner(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 mir_built(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 mir_built(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 mir_built(foo) green. Consequently, a chain of other queries is marked green, leading us finally to try to load the stored result of optimized_mir(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 hir_owner(foo) to be marked red, causing mir_built(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. |
incr-comp: hash and serialize span end line/column 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. 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. Fix rust-lang#46744, fix rust-lang#59954, fix rust-lang#63161, fix rust-lang#73640, fix rust-lang#73967, fix rust-lang#74890, fix rust-lang#75900 --- See rust-lang#74890 for a more in-depth analysis. I haven't thought about what other problems this root cause could be responsible for. Please let me know if anything springs to mind. I believe the issue has existed since the inception of incremental compilation.
Code
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 #73967 and maybe #73108. I suspect all three could be addressed by adopting a strategy as described in rust-lang/cargo#6529.
Meta
rustc --version --verbose
:rustup upgrade
did make the error "go away," I believe this is just because it invalidated the cache.Error output
Backtrace
Reproducibility and Theory
So far I've reduced the state down to this project: out-of-bounds.tar.gz.
tar xvzf out-of-bounds.tar.gz; cd out-of-bounds; cargo test
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:
cargo test
kicks off an incremental compile, and starts checking fingerprints.tests/config.rs
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 panicsSince this appears to be happening in the "check what work to do" phase,
rustc
never produces a new artifact that's in sync with the current state of the file, and eithercargo clean
or forcing a full rebuild is necessary to get back into sync.The two questions I haven't gotten a good idea about are:
rustc
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.The text was updated successfully, but these errors were encountered: