Repository navigation
ILVerify: continue past verifiability-only errors - #136178
Draft
pcshrosbree wants to merge 2 commits into
Draft
pcshrosbree wants to merge 2 commits into
pcshrosbree wants to merge 2 commits into
Conversation
Contributor
|
Tagging subscribers to this area: @JulieLeeMSFT, @dotnet/jit-contrib |
pcshrosbree
force-pushed
the
feat/ilverify-propagate-past-unverifiable
branch
from
October 21, 2026 07:26
8fa8e74 to
e98857d
Compare
Add VerifierOptions.ValidationOnly and the ilverify --validation-only option. In this mode ILVerify reports IL that is not valid and does not report most IL that is valid but not verifiable (ECMA-335 III.1.8), such as unmanaged pointers, localloc, calli, pointer arithmetic, or methods without localsinit. Each reporting site is classified by the rule it enforces: a rule in an instruction's Correctness paragraph stays, and a rule found only in its Verifiability paragraph is generally not reported. Most checks of a value against a declared type compare only the stack types of I.12.3.2.1. Checks that compare two signatures, such as the delegate target check, still compare the types in those signatures, not only their stack types. A suppressed check does not abort the block or the method, so checking continues after it. With the option off, results are unchanged. Tests: every existing method test also runs in validation-only mode. A suffix on the test method name gives the errors expected in that mode when they differ, and a malformed suffix fails the tests. ValidationOnlyTests.il covers the suppressed checks and the checks that are kept. Fixes dotnet#37391
PropagateControlFlow returns early when the current block has reported any error, and every report counts, including Unverifiable() for localloc and calli and InitLocals for a skipped-init local. Code that is reachable only through such a block was never imported, so verification mode missed errors that validation-only mode reports. A block now passes its stack on if it has reported only errors that validation-only mode suppresses (UnverifiableError and CheckVerifiable). These errors make the IL unverifiable but not invalid. The importer builds the same stack after these reports as it does without them. Any other error still stops propagation. A fatal check or an aborted block ends the method, as before. CheckIsObjRef(TypeDesc) still stops propagation in verification mode: ldind.ref and ldelem.ref then push the pointee or element type, which is not an object reference. A block with errors is still not re-imported when its entry stack changes, so no error is reported twice. Validation-only mode is unchanged.
pcshrosbree
force-pushed
the
feat/ilverify-propagate-past-unverifiable
branch
from
October 21, 2026 09:18
e98857d to
1d94656
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Fixes #136177
This is a draft that builds on #136169, which is now rebased onto
mainafter #136133 merged. Only the last commit is new. It reuses #136169's split of reports into rules for valid IL and rules that only verifiable IL must follow.Change
BasicBlockgets anUnverifiableErrorCount.UnverifiableErrorincrements it after reporting, in verification mode only, andCheckVerifiablenow callsUnverifiableError.PropagateControlFlowstops whenErrorCount > UnverifiableErrorCount, that is, when the block reported at least one error that is not verifiability-only.ErrorCountstill counts every report, so a block that has reported is still never imported twice.CheckIsObjRef(TypeDesc)reports throughVerificationErroragain, so it still stops propagation. After aCheckIsObjReffailure in verification mode,ldind.refandldelem.refpush the pointee or element type (anint32, say) instead of an object reference. Passing that type on would produce a wrong error in the next block. Validation-only mode still suppresses theCheckIsObjReferror.After every other verifiability-only report, the importer still pushes the instruction's usual result, which does not depend on what made it unverifiable:
locallocpushes a native int,callipushes its return type,ldelemapushes a&to its token type, and so on. So the stack passed on is the one the importer would build for valid IL.FatalCheckandAbortBasicBlockVerificationend the method, as before (#37392).Validation-only mode is unchanged: there
UnverifiableErrorreports nothing and counts nothing.Results
These figures were measured before the rebase onto
main, which brought in #136159, #136033 and #136176. I have not re-run the scans.Verification mode over the .NET 10.0.12 shared framework (172 assemblies, 130,146 method bodies, linux-x64), before and after this commit:
InitLocalsInitLocalsaccounts for a net increase of 218,804 (219,078 new, 274 no longer reported). It is reported at every read of a local in a method withoutlocalsinit. Continuing past verifiability-only errors reaches more of those reads.BadTypeSpec149 (ILVerify reports BadTypeSpec for everyunmanaged-constrained generic since #129118 #136142),MethodAccess57 andStackUnexpected30. Validation-only mode already reports these errors in the same methods, with the same error codes and offsets where present. TheBadTypeSpecerrors have no offset. The 57MethodAccesserrors, all inCreateChecked,CreateSaturatingandCreateTruncatingof the numeric types, are false positives. ILVerify's family access check does not consider implemented interfaces, while the ECMA-335 augments and the runtime letInt32callINumberBase<TOther>'s protected members. That is a separate fix, ILVerify: allow family access through implemented interfaces #136176, which has since merged.BadTypeSpecerror. The new paths reach that error for the first time in 6 methods and earlier than before in 4. As a result, some blocks checked before this change are now skipped. All 136 methods still fail.Four package sets (IL SDK packages, the top 10 InlineIL users, the nuget.org top 300, saved dynamic proxies; 388,490 method bodies) give the same picture: no method changes between passing and failing, errors go from 39,774 to 100,188, and 114 errors in 25 methods are lost the same way.
Alternative not taken
I also considered treating a
CheckIsAssignablefailure as verifiability-only when validation-only mode accepts the IL. This would be a net increase of 1,371 framework errors (1,455 new, 84 lost) across 115 methods. None of these additional errors is also reported by validation-only mode. It would also need a special case forldfldathrough a native int, where verification mode pushes a&and validation-only mode a native int. I left it out.Tests
Nine new methods in
BranchingTests.il, each run in both modes:Propagate.After*: in each test, a block with one verifiability-only error branches to a block whoseretrequires a return value but finds an empty stack. TheReturnMissingthere is now reported. The five cases arelocalloc,callithrough a method pointer argument,InitLocals,ldelemaof a different element type, andldind.i4through anint64&. Together they reach everyUnverifiableErroroverload andCheckVerifiable.Propagate.StopAfterLoadIndirectRefandStopAfterLoadElementRef: theldind.refandldelem.refcase still stops.Propagate.StopAfterInvalidandStopAfterUnverifiableAndInvalid: propagation still stops after a correctness error, including when the same block also reports a verifiability-only error.Without the change, the five
After*tests fail in verification mode and the other new tests pass. Removing each part of the change in turn fails the test written for it. The one exception is the change toCheckVerifiable(bool, VerifierError, StackValue), which has no caller; it only keeps the overloads consistent.After the rebase,
ILVerification.Testspasses 1,461 of 1,461 in Release. The count is five lower because the five tests for #136169's test-name suffix parser were removed.Questions
InitLocalsat every read of a local makes the framework's error count about 3.6 times larger. Would you rather report it once per method, as a separate change?Note
This PR was prepared with AI assistance (Anthropic Claude and OpenAI Codex), under my direction. I reviewed it before posting.