Fix and improve array and mdspan static analysis warnings
#4856
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
🗺️ Overview
Fixes DevCom-10342063 VSO-1804139 "False positive C28020 iterating over single element
std::array". Thanks to Hwi-sung Im from the static analysis team for explaining how to fix this with_Ret_range_(==, _Size).The issue is that we had SAL annotations on
array::operator[]explaining its precondition, but static analysis didn't know thatarray<T, N>::size()always returnsN. (By design, it doesn't do interprocedural analysis.) Adding a return annotation allows/analyzeto understand loops that are guarded byarr.size().Updating
arraythen revealed that<mdspan>(which usesarrayextensively) should be updated accordingly.📜 Commits
array::size()with_Ret_range_(==, _Size), which is what fixes the reported bug._In_range_(0, _Size - 1)with_In_range_(<, _Size). I wasn't previously aware of this form, but it appears elsewhere in the MSVC codebase, and is much more natural as it aligns with our_STL_VERIFYchecks.arrayand<mdspan>below, we're always working withsize_t, so we don't need to worry about negative numbers.<mdspan>, update a comment from citing DevCom-923103 to saying "guaranteed by how_Rank_dynamicis calculated". We have an ironclad guarantee here, but the proof is way more subtle than static analysis can reasonably be expected to understand, so this isn't a compiler bug workaround.rank()with_Ret_range_andstatic_extent(),extent(), andstride()with_In_range_.layout_stride::mapping::stride().mdspan::extent()isn't missing a check, it's just performed bythis->_Map.extents().extent(_Idx)and the message will be sufficiently relevant._In_range_(<, _Rank).FAIL, but one needs to be added (because we have more precondition annotations).test_mdspan_support.hpp, addassert(i < rank)so static analysis is happy with the precondition. (Attempting to propagate the annotation to the lambda led to more warnings, since it's used withinvoke()andiota_view.)Ext::rank()is now understood.P0009R18_mdspan_layout_strideto useExt::rank()instead of the equivalentstrs.size()so/analyzecan see the guarantee.P0218R1_filesystem, theEXPECTmacro calls a function that returns the givenbool, but/analyzedoesn't see that, so it doesn't realize we're checking the validity ofindex. Restructure this with a direct test andEXPECT(false)(which will still result in decent logging since the line number is captured)./analyzewarning when intentionally making out-of-bounds calls. I mention/analyzeinstead of the internal "PREfast" terminology here.suppresswithpush/disable/pop, as we've already completely done in the product code, sincesuppressis annoyingly fragile and we should avoid it, despite the additional lines needed to push/disable/pop.