From f1af626bdec1661b2a2dbf146cabdbb1e84ac145 Mon Sep 17 00:00:00 2001 From: Peter Shrosbree Date: Tue, 20 Oct 2026 01:49:33 -0700 Subject: [PATCH 1/2] ILVerify: add a validation-only mode 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 #37391 --- .../ILVerification.Tests/ILMethodTester.cs | 21 +- .../ILTests/BranchingTests.il | 6 +- .../ILVerification.Tests/ILTests/CallTests.il | 6 +- .../ILTests/CalliTests.il | 34 +- .../ILTests/CastingTests.il | 4 +- .../ILTests/DelegateTests.il | 14 +- .../ILTests/ExceptionRegionTests.il | 4 +- .../ILVerification.Tests/ILTests/FtnTests.il | 18 +- .../ILTests/LoadStoreIndirectTests.il | 10 +- .../ILTests/LocalAllocTests.il | 6 +- .../ILTests/PrefixTests.il | 8 +- .../ILTests/ReturnTests.il | 4 +- .../ILTests/TypedReferenceTests.il | 8 +- .../ILTests/ValidationOnlyTests.il | 2085 +++++++++++++++++ .../ILTests/ValidationOnlyTests.ilproj | 9 + .../ILVerification.Tests/TestDataLoader.cs | 129 +- .../tools/ILVerification/ILImporter.Verify.cs | 346 ++- src/coreclr/tools/ILVerification/Verifier.cs | 14 +- .../tools/ILVerify/ILVerifyRootCommand.cs | 3 + src/coreclr/tools/ILVerify/Program.cs | 9 +- src/coreclr/tools/ILVerify/README.md | 13 + 21 files changed, 2622 insertions(+), 129 deletions(-) create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/ValidationOnlyTests.il create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/ValidationOnlyTests.ilproj diff --git a/src/coreclr/tools/ILVerification.Tests/ILMethodTester.cs b/src/coreclr/tools/ILVerification.Tests/ILMethodTester.cs index cfc169efc70c5c..aa406e5afb3564 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILMethodTester.cs +++ b/src/coreclr/tools/ILVerification.Tests/ILMethodTester.cs @@ -54,7 +54,23 @@ public static void TestMethodsWithInvalidIL(InvalidILTestCase invalidIL) } } - private static IEnumerable Verify(TestCase testCase) + [Theory(DisplayName = "")] + [MemberData(nameof(TestDataLoader.GetMethodsForValidationOnlyMode), MemberType = typeof(TestDataLoader))] + [Trait("", "Validation-only IL Tests")] + public static void TestMethodsInValidationOnlyMode(ValidationOnlyTestCase testCase) + { + // Unlike verification, validation must not throw for invalid IL, so exceptions are not caught here. + var results = Verify(testCase, validationOnly: true).ToList(); + var actual = string.Join(",", results.Select(e => e.Code.ToString())); + + Assert.True(testCase.ExpectedVerifierErrors.Count == results.Count, $"Actual errors were: {actual}"); + foreach (var item in testCase.ExpectedVerifierErrors) + { + Assert.True(results.Any(r => r.Code == item), $"Actual errors were: {actual}"); + } + } + + private static IEnumerable Verify(TestCase testCase, bool validationOnly = false) { EcmaModule module = TestDataLoader.GetModuleForTestAssembly(testCase.ModuleName, allowIgnoresAccessChecksTo: true); var methodHandle = (MethodDefinitionHandle) MetadataTokens.EntityHandle(testCase.MetadataToken); @@ -62,7 +78,8 @@ private static IEnumerable Verify(TestCase testCase) var verifier = new Verifier((ILVerifyTypeSystemContext)method.Context, new VerifierOptions { IncludeMetadataTokensInErrorMessages = true, - SanityChecks = true + SanityChecks = true, + ValidationOnly = validationOnly }); return verifier.Verify(module.PEReader, methodHandle); diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/BranchingTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/BranchingTests.il index 113fbb35b9a9b7..f2f8f164bb5325 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/BranchingTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/BranchingTests.il @@ -81,7 +81,7 @@ IL_0014: ret } - .method static public hidebysig void Branching.NullConditional_Invalid_StackUnexpected() cil managed + .method static public hidebysig void Branching.NullConditional_Invalid_StackUnexpected_Validation_Valid() cil managed { //object o = null; //Type t = o != null ? o.GetType() : o; @@ -175,7 +175,7 @@ ret } - .method static public hidebysig void Branching.UnequalStackMergeArrays_Invalid_StackUnexpected(class CustomComparer[] comparers, + .method static public hidebysig void Branching.UnequalStackMergeArrays_Invalid_StackUnexpected_Validation_Valid(class CustomComparer[] comparers, class [System.Runtime]System.Collections.IEqualityComparer[] customComparer) cil managed { .maxstack 1 @@ -949,7 +949,7 @@ ret } - .method static public hidebysig void Branch.BackwardWithoutPredecessor_Invalid_BackwardBranch() cil managed + .method static public hidebysig void Branch.BackwardWithoutPredecessor_Invalid_BackwardBranch_Validation_Valid() cil managed { br MethodEnd diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/CallTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/CallTests.il index afa40882fa3b5e..553dbc637a9e1f 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/CallTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/CallTests.il @@ -54,7 +54,7 @@ ret } - .method public hidebysig instance void Call.BaseMethodModThisLdarga_Invalid_ThisMismatch() cil managed + .method public hidebysig instance void Call.BaseMethodModThisLdarga_Invalid_ThisMismatch_Validation_Valid() cil managed { ldarg.0 call instance void SimpleClass::VirtualMethod() @@ -63,7 +63,7 @@ ret } - .method public hidebysig instance void Call.BaseMethodModThisStarg_Invalid_ThisMismatch() cil managed + .method public hidebysig instance void Call.BaseMethodModThisStarg_Invalid_ThisMismatch_Validation_Valid() cil managed { ldarg.0 call instance void SimpleClass::VirtualMethod() @@ -128,7 +128,7 @@ ret } - .method public hidebysig instance void Call.ExternBaseMethod_Invalid_ThisMismatch(class DerivedClass c) cil managed + .method public hidebysig instance void Call.ExternBaseMethod_Invalid_ThisMismatch_Validation_Valid(class DerivedClass c) cil managed { ldarg.1 call instance void SimpleClass::VirtualMethod() diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il index cd0f85d74234da..8bab3c054ffedb 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il @@ -35,7 +35,7 @@ ret } - .method public hidebysig static void Calli.StaticFromLdftn_Invalid_Unverifiable() cil managed + .method public hidebysig static void Calli.StaticFromLdftn_Invalid_Unverifiable_Validation_Valid() cil managed { ldc.i4.1 ldftn void CalliTests::StaticTarget(int32) @@ -43,14 +43,14 @@ ret } - .method public hidebysig static int32 Calli.ReturnValue_Invalid_Unverifiable() cil managed + .method public hidebysig static int32 Calli.ReturnValue_Invalid_Unverifiable_Validation_Valid() cil managed { ldftn int32 CalliTests::ReturnsInt32() calli int32() ret } - .method public hidebysig static void Calli.ErrorAfterCalli_Invalid_Unverifiable.StackUnderflow() cil managed + .method public hidebysig static void Calli.ErrorAfterCalli_Invalid_Unverifiable.StackUnderflow_Validation_StackUnderflow() cil managed { ldc.i4.1 ldftn void CalliTests::StaticTarget(int32) @@ -59,7 +59,7 @@ ret } - .method public hidebysig static void Calli.FromArgument_Invalid_Unverifiable(method void *(int32) f) cil managed + .method public hidebysig static void Calli.FromArgument_Invalid_Unverifiable_Validation_Valid(method void *(int32) f) cil managed { ldc.i4.1 ldarg.0 @@ -67,7 +67,7 @@ ret } - .method public hidebysig static void Calli.ArgumentMismatch_Invalid_Unverifiable.StackUnexpected(method void *(int32) f) cil managed + .method public hidebysig static void Calli.ArgumentMismatch_Invalid_Unverifiable.StackUnexpected_Validation_StackUnexpected(method void *(int32) f) cil managed { ldnull ldarg.0 @@ -75,7 +75,7 @@ ret } - .method public hidebysig static void Calli.MethodPointerNotNativeInt_Invalid_Unverifiable.StackMethod() cil managed + .method public hidebysig static void Calli.MethodPointerNotNativeInt_Invalid_Unverifiable.StackMethod_Validation_StackMethod() cil managed { ldc.i4.1 ldc.i4.0 @@ -83,7 +83,7 @@ ret } - .method public hidebysig instance void Calli.ImplicitThis_Invalid_Unverifiable(method instance void *(int32) f) cil managed + .method public hidebysig instance void Calli.ImplicitThis_Invalid_Unverifiable_Validation_Valid(method instance void *(int32) f) cil managed { ldarg.0 ldc.i4.1 @@ -92,7 +92,7 @@ ret } - .method public hidebysig instance void Calli.ExplicitThis_Invalid_Unverifiable() cil managed + .method public hidebysig instance void Calli.ExplicitThis_Invalid_Unverifiable_Validation_Valid() cil managed { ldarg.0 ldc.i4.1 @@ -102,7 +102,7 @@ } // A vararg call site with no extra arguments has no sentinel. - .method public hidebysig static void Calli.Vararg_Invalid_Unverifiable(method vararg void *(int32) f) cil managed + .method public hidebysig static void Calli.Vararg_Invalid_Unverifiable_Validation_Valid(method vararg void *(int32) f) cil managed { ldc.i4.1 ldarg.0 @@ -110,14 +110,14 @@ ret } - .method public hidebysig static void Calli.TailCall_Invalid_Unverifiable() cil managed + .method public hidebysig static void Calli.TailCall_Invalid_Unverifiable_Validation_Valid() cil managed { ldftn void CalliTests::ManagedTarget() tail. calli void() ret } - .method public hidebysig static void Calli.TailCallByRefArgument_Invalid_Unverifiable.TailByRef(method void *(int32&) f) cil managed + .method public hidebysig static void Calli.TailCallByRefArgument_Invalid_Unverifiable.TailByRef_Validation_TailByRef(method void *(int32&) f) cil managed { .locals init (int32 V_0) ldloca.s V_0 @@ -126,7 +126,7 @@ ret } - .method public hidebysig static void Calli.TailCallByRefThis_Invalid_Unverifiable.TailByRef(method instance void *() f) cil managed + .method public hidebysig static void Calli.TailCallByRefThis_Invalid_Unverifiable.TailByRef_Validation_TailByRef(method instance void *() f) cil managed { .locals init (int32 V_0) ldloca.s V_0 @@ -135,7 +135,7 @@ ret } - .method public hidebysig static void Calli.TailCallNotFollowedByRet_Invalid_Unverifiable.TailRet() cil managed + .method public hidebysig static void Calli.TailCallNotFollowedByRet_Invalid_Unverifiable.TailRet_Validation_TailRet() cil managed { ldftn void CalliTests::ManagedTarget() tail. calli void() @@ -145,14 +145,14 @@ // The tail call's return checks are shared with call. A void method cannot tail-call a method returning a value; // the value then left on the stack also makes the ret report ReturnVoid. - .method public hidebysig static void Calli.TailCallReturnsValue_Invalid_Unverifiable.TailRetVoid.ReturnVoid() cil managed + .method public hidebysig static void Calli.TailCallReturnsValue_Invalid_Unverifiable.TailRetVoid.ReturnVoid_Validation_TailRetVoid.ReturnVoid() cil managed { ldftn int32 CalliTests::ReturnsInt32() tail. calli int32() ret } - .method public hidebysig static int32& Calli.ByRefReturn_Invalid_Unverifiable(method int32& *() f) cil managed + .method public hidebysig static int32& Calli.ByRefReturn_Invalid_Unverifiable_Validation_Valid(method int32& *() f) cil managed { ldarg.0 calli int32&() @@ -160,7 +160,7 @@ } // The calli operand is the TypeDef token of CalliTests (0x02000002), not a StandAloneSig token. - .method public hidebysig static void Calli.NotSignatureToken_Invalid_Unverifiable.ExpectedSignatureToken() cil managed + .method public hidebysig static void Calli.NotSignatureToken_Invalid_Unverifiable.ExpectedSignatureToken_Validation_ExpectedSignatureToken() cil managed { ldftn void CalliTests::ManagedTarget() .emitbyte 0x29 @@ -193,7 +193,7 @@ ret } - .method public hidebysig instance void 'special.CalliUninitThis..ctor_Invalid_Unverifiable.UninitStack'(method void *(object) f) cil managed + .method public hidebysig instance void 'special.CalliUninitThis..ctor_Invalid_Unverifiable.UninitStack_Validation_UninitStack'(method void *(object) f) cil managed { ret } diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/CastingTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/CastingTests.il index 9a6efb67c55ac4..b974c9283f4617 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/CastingTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/CastingTests.il @@ -234,7 +234,7 @@ ret } - .method public hidebysig instance void Casting.AssignThisToSameTypeWithOtherGenericArgs_Invalid_StackUnexpected() cil managed + .method public hidebysig instance void Casting.AssignThisToSameTypeWithOtherGenericArgs_Invalid_StackUnexpected_Validation_Valid() cil managed { .locals init ( class GenericCastingTestsType`1 V_0 @@ -257,7 +257,7 @@ ret } - .method public hidebysig static void Casting.AssignToSameTypeWithOtherGenericArgs_Invalid_StackUnexpected() cil managed + .method public hidebysig static void Casting.AssignToSameTypeWithOtherGenericArgs_Invalid_StackUnexpected_Validation_Valid() cil managed { .locals init ( class GenericCastingTestsType`1 V_0, diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/DelegateTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/DelegateTests.il index e38baa3e682289..1919a9341e944c 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/DelegateTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/DelegateTests.il @@ -38,7 +38,7 @@ // assignment from Func to Func is invalid .method private hidebysig instance class [System.Runtime]System.Func`2 - DelegateAssignmentParameter_Invalid_StackUnexpected(class [System.Runtime]System.Func`2 input) cil managed + DelegateAssignmentParameter_Invalid_StackUnexpected_Validation_Valid(class [System.Runtime]System.Func`2 input) cil managed { ldarg.1 ret @@ -46,7 +46,7 @@ // assignment from Func to Func is invalid .method private hidebysig instance class [System.Runtime]System.Func`2 - DelegateAssignment_Invalid_StackUnexpected(class [System.Runtime]System.Func`2 input) cil managed + DelegateAssignment_Invalid_StackUnexpected_Validation_Valid(class [System.Runtime]System.Func`2 input) cil managed { ldarg.1 ret @@ -54,7 +54,7 @@ // assignment from Func to Func is invalid .method private hidebysig instance class [System.Runtime]System.Func`1 - AssignIntFuncToByteFunc_Invalid_StackUnexpected(class [System.Runtime]System.Func`2 input) cil managed + AssignIntFuncToByteFunc_Invalid_StackUnexpected_Validation_Valid(class [System.Runtime]System.Func`2 input) cil managed { ldarg.1 ret @@ -62,7 +62,7 @@ // assignment from Func to Func is invalid .method private hidebysig instance class [System.Runtime]System.Func`1 - AssignByteFuncToIntFunc_Invalid_StackUnexpected(class [System.Runtime]System.Func`1 input) cil managed + AssignByteFuncToIntFunc_Invalid_StackUnexpected_Validation_Valid(class [System.Runtime]System.Func`1 input) cil managed { ldarg.1 ret @@ -70,7 +70,7 @@ // assignment from Func to Func is invalid .method private hidebysig instance class [System.Runtime]System.Func`1 - AssignByteActionToIntAction_Invalid_StackUnexpected(class [System.Runtime]System.Func`1 input) cil managed + AssignByteActionToIntAction_Invalid_StackUnexpected_Validation_Valid(class [System.Runtime]System.Func`1 input) cil managed { ldarg.1 ret @@ -78,7 +78,7 @@ // assignment from Action to Action is invalid .method private hidebysig instance class [System.Runtime]System.Action`1 - AssignByteActionToIntAction_Invalid_StackUnexpected(class [System.Runtime]System.Action`1 input) cil managed + AssignByteActionToIntAction_Invalid_StackUnexpected_Validation_Valid(class [System.Runtime]System.Action`1 input) cil managed { ldarg.1 ret @@ -86,7 +86,7 @@ // assignment from Action to Action is invalid .method private hidebysig instance class [System.Runtime]System.Action`1 - AssignByteActionToByteEnumAction_Invalid_StackUnexpected(class [System.Runtime]System.Action`1 input) cil managed + AssignByteActionToByteEnumAction_Invalid_StackUnexpected_Validation_Valid(class [System.Runtime]System.Action`1 input) cil managed { ldarg.1 ret diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/ExceptionRegionTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/ExceptionRegionTests.il index 27ec0b97238972..5cdb834c7fa733 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/ExceptionRegionTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/ExceptionRegionTests.il @@ -1899,7 +1899,7 @@ ret } - .method public static void Filter.NestedInCatchWithoutCast_Invalid_StackUnexpected() cil managed + .method public static void Filter.NestedInCatchWithoutCast_Invalid_StackUnexpected_Validation_Valid() cil managed { .maxstack 1 @@ -1957,7 +1957,7 @@ ret } - .method public static void Filter.ExceptionLeftOnStack_Invalid_EndfilterStack() cil managed + .method public static void Filter.ExceptionLeftOnStack_Invalid_EndfilterStack_Validation_Valid() cil managed { .maxstack 2 diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/FtnTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/FtnTests.il index bdb05bd56542e3..dfbe3a99ab803c 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/FtnTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/FtnTests.il @@ -307,7 +307,7 @@ } // a boxed int32 is not a valid target for an instance method on an unrelated class - .method static public hidebysig void LdFtn.BoxedValueTypeForTestClassInstance_Invalid_DelegateCtor() cil managed + .method static public hidebysig void LdFtn.BoxedValueTypeForTestClassInstance_Invalid_DelegateCtor_Validation_Valid() cil managed { ldc.i4.s 42 box [System.Runtime]System.Int32 @@ -391,7 +391,7 @@ ret } - .method static public hidebysig void LdvirtFtn.ValueTypeWrongBox_Invalid_StackUnexpected.DelegateCtorSigO.DelegatePattern.DelegateCtor(int32 i) cil managed + .method static public hidebysig void LdvirtFtn.ValueTypeWrongBox_Invalid_StackUnexpected.DelegateCtorSigO.DelegatePattern.DelegateCtor_Validation_StackUnexpected.DelegateCtorSigO.DelegateCtor(int32 i) cil managed { // (int i) // var f = new System.Func(i.CompareTo); @@ -408,7 +408,7 @@ ret } - .method static public hidebysig void LdvirtFtn.ObjectForTestClassInstance_Invalid_StackUnexpected.DelegateCtor(object c) cil managed + .method static public hidebysig void LdvirtFtn.ObjectForTestClassInstance_Invalid_StackUnexpected.DelegateCtor_Validation_Valid(object c) cil managed { // (object c) // var a = new System.Action(c.InstanceMethod); @@ -439,7 +439,7 @@ ret } - .method static public hidebysig void NewObj.DelegateFromNativeIntArgument_Invalid_StackMethod.DelegatePattern(native int fn) cil managed + .method static public hidebysig void NewObj.DelegateFromNativeIntArgument_Invalid_StackMethod.DelegatePattern_Validation_Valid(native int fn) cil managed { // The function pointer is an argument, not the result of ldftn/ldvirtftn. @@ -450,7 +450,7 @@ ret } - .method static public hidebysig void LdFtn.NopInDelegatePattern_Invalid_DelegatePattern() cil managed + .method static public hidebysig void LdFtn.NopInDelegatePattern_Invalid_DelegatePattern_Validation_Valid() cil managed { // var a = new System.Action(TestClass.StaticMethod); // a(); @@ -463,7 +463,7 @@ ret } - .method static public hidebysig void LdvirtFtn.NopInDelegatePattern_Invalid_DelegatePattern(class TestClass c) cil managed + .method static public hidebysig void LdvirtFtn.NopInDelegatePattern_Invalid_DelegatePattern_Validation_Valid(class TestClass c) cil managed { // (TestClass c) // var a = new System.Action(c.InstanceMethod); @@ -478,7 +478,7 @@ ret } - .method static public hidebysig void LdFtn.BranchIntoDelegatePattern_Invalid_DelegatePattern() cil managed + .method static public hidebysig void LdFtn.BranchIntoDelegatePattern_Invalid_DelegatePattern_Validation_Valid() cil managed { ldnull ldftn void TestClass::StaticMethod() @@ -493,7 +493,7 @@ ret } - .method static public hidebysig void LdvirtFtn.BranchIntoDelegatePattern_Invalid_DelegatePattern(class TestClass t) cil managed + .method static public hidebysig void LdvirtFtn.BranchIntoDelegatePattern_Invalid_DelegatePattern_Validation_Valid(class TestClass t) cil managed { ldarg.0 dup @@ -528,7 +528,7 @@ ret } - .method static public hidebysig void LdFtn.VirtualMethodNonThisPtr_Invalid_LdftnNonFinalVirtual(class TestClass c) cil managed + .method static public hidebysig void LdFtn.VirtualMethodNonThisPtr_Invalid_LdftnNonFinalVirtual_Validation_Valid(class TestClass c) cil managed { ldarg.0 ldftn instance void TestClass::VirtInstanceMethod() diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/LoadStoreIndirectTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/LoadStoreIndirectTests.il index 67f99c03a97fcf..653b8c36e64a3a 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/LoadStoreIndirectTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/LoadStoreIndirectTests.il @@ -28,7 +28,7 @@ ret } - .method static public hidebysig void LoadIndirect.FromInt64Ref_Invalid_StackUnexpected(int64&) cil managed + .method static public hidebysig void LoadIndirect.FromInt64Ref_Invalid_StackUnexpected_Validation_Valid(int64&) cil managed { ldarg.0 ldind.i4 @@ -36,7 +36,7 @@ ret } - .method static public hidebysig void LoadIndirectRef.FromInt64Ref_Invalid_StackUnexpected(int64&) cil managed + .method static public hidebysig void LoadIndirectRef.FromInt64Ref_Invalid_StackUnexpected_Validation_Valid(int64&) cil managed { ldarg.0 ldind.ref @@ -77,7 +77,7 @@ ret } - .method static public hidebysig void StoreIndirect.AssignToRefString_Invalid_StackUnexpected(string&) cil managed + .method static public hidebysig void StoreIndirect.AssignToRefString_Invalid_StackUnexpected_Validation_Valid(string&) cil managed { .locals init (object V_0) @@ -114,7 +114,7 @@ ret } - .method static public hidebysig void StoreObject.AssignStringToRefStringAsObject_Invalid_StackUnexpected(string&) cil managed + .method static public hidebysig void StoreObject.AssignStringToRefStringAsObject_Invalid_StackUnexpected_Validation_Valid(string&) cil managed { // ref string x; // string y = "a"; @@ -144,7 +144,7 @@ ret } - .method static public hidebysig void StoreObject.InvalidTypeToken_Invalid_StackUnexpected() cil managed + .method static public hidebysig void StoreObject.InvalidTypeToken_Invalid_StackUnexpected_Validation_Valid() cil managed { .locals init (object V_0) diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/LocalAllocTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/LocalAllocTests.il index 5789906868b1f8..2cc666bb2297a1 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/LocalAllocTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/LocalAllocTests.il @@ -16,7 +16,7 @@ .class public auto ansi beforefieldinit LocAllocTests extends [System.Runtime]System.Object { - .method private hidebysig instance void Load.LocAllocShouldFailWhenStackNotEmpty_Invalid_Unverifiable.LocallocStackNotEmpty() cil managed + .method private hidebysig instance void Load.LocAllocShouldFailWhenStackNotEmpty_Invalid_Unverifiable.LocallocStackNotEmpty_Validation_LocallocStackNotEmpty() cil managed { ldnull // pretend there's meaningfull value on stack ldc.i4.2 // size parameter for localloc @@ -26,14 +26,14 @@ ret } - .method private hidebysig instance void Load.LocAllocShouldFailWhenSizeIsAbsent_Invalid_Unverifiable.StackUnderflow() cil managed + .method private hidebysig instance void Load.LocAllocShouldFailWhenSizeIsAbsent_Invalid_Unverifiable.StackUnderflow_Validation_StackUnderflow() cil managed { localloc pop // pop localloc pointer ret } - .method private hidebysig instance void Load.LocAllocShouldSuccesWhenSizeIsPresent_Invalid_Unverifiable() cil managed + .method private hidebysig instance void Load.LocAllocShouldSuccesWhenSizeIsPresent_Invalid_Unverifiable_Validation_Valid() cil managed { ldc.i4.2 // size parameter for localloc localloc diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il index 51d849d95b2834..98fb3c70ed1102 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il @@ -64,14 +64,14 @@ ret } - .method static public hidebysig void ConstrainedCall.UnconstrainedTypeParam_Invalid_ConstrainedTypeNoInterfaceImpl() cil managed + .method static public hidebysig void ConstrainedCall.UnconstrainedTypeParam_Invalid_ConstrainedTypeNoInterfaceImpl_Validation_Valid() cil managed { constrained. !!T call void IStaticInterface::StaticAbstractMethod() ret } - .method static public hidebysig void ConstrainedCall.NonImplStruct_Invalid_ConstrainedTypeNoInterfaceImpl() cil managed + .method static public hidebysig void ConstrainedCall.NonImplStruct_Invalid_ConstrainedTypeNoInterfaceImpl_Validation_Valid() cil managed { constrained. NonImplStruct call void IStaticInterface::StaticAbstractMethod() @@ -101,7 +101,7 @@ ret } - .method static public hidebysig void ConstrainedLdftn.UnconstrainedTypeParam_Invalid_ConstrainedTypeNoInterfaceImpl() cil managed + .method static public hidebysig void ConstrainedLdftn.UnconstrainedTypeParam_Invalid_ConstrainedTypeNoInterfaceImpl_Validation_Valid() cil managed { constrained. !!T ldftn void IStaticInterface::StaticAbstractMethod() @@ -109,7 +109,7 @@ ret } - .method static public hidebysig void ConstrainedLdftn.NonImplStruct_Invalid_ConstrainedTypeNoInterfaceImpl() cil managed + .method static public hidebysig void ConstrainedLdftn.NonImplStruct_Invalid_ConstrainedTypeNoInterfaceImpl_Validation_Valid() cil managed { constrained. NonImplStruct ldftn void IStaticInterface::StaticAbstractMethod() diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/ReturnTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/ReturnTests.il index a6918b4db83c62..6131840aab547c 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/ReturnTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/ReturnTests.il @@ -114,7 +114,7 @@ ret } - .method public hidebysig instance int32& Return.LocalArgByRef_Invalid_ReturnPtrToStack(int32 i) cil managed + .method public hidebysig instance int32& Return.LocalArgByRef_Invalid_ReturnPtrToStack_Validation_Valid(int32 i) cil managed { ldarga.s i ret @@ -133,7 +133,7 @@ ret } - .method public hidebysig instance int32& Return.LocalStructFieldByRef_Invalid_ReturnPtrToStack(valuetype Struct s) cil managed + .method public hidebysig instance int32& Return.LocalStructFieldByRef_Invalid_ReturnPtrToStack_Validation_Valid(valuetype Struct s) cil managed { ldarg.1 ldflda int32 Struct::instanceField diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.il index 6a510a76a09d38..578306ea017dca 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.il @@ -56,7 +56,7 @@ } // Correct CIL, but verification requires a managed pointer. - .method public hidebysig static void Mkrefany.NativeIntPointer_Invalid_StackByRef() cil managed + .method public hidebysig static void Mkrefany.NativeIntPointer_Invalid_StackByRef_Validation_Valid() cil managed { ldc.i4.0 conv.i @@ -65,7 +65,7 @@ ret } - .method public hidebysig static void Mkrefany.TypeMismatch_Invalid_StackUnexpected() cil managed + .method public hidebysig static void Mkrefany.TypeMismatch_Invalid_StackUnexpected_Validation_Valid() cil managed { .locals init (int32 V_0) ldloca.s V_0 @@ -75,7 +75,7 @@ } // The pointer must point to exactly the type of the class token, not to a derived type. - .method public hidebysig static void Mkrefany.ReferenceTypeMismatch_Invalid_StackUnexpected() cil managed + .method public hidebysig static void Mkrefany.ReferenceTypeMismatch_Invalid_StackUnexpected_Validation_Valid() cil managed { .locals init (string V_0) ldloca.s V_0 @@ -152,7 +152,7 @@ } // The address in a typed reference can point to a local of a caller. - .method public hidebysig static int32& Refanyval.ReturnAddress_Invalid_ReturnPtrToStack(typedref tr) cil managed + .method public hidebysig static int32& Refanyval.ReturnAddress_Invalid_ReturnPtrToStack_Validation_Valid(typedref tr) cil managed { ldarg.0 refanyval [System.Runtime]System.Int32 diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/ValidationOnlyTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/ValidationOnlyTests.il new file mode 100644 index 00000000000000..5b12df9250acb9 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/ValidationOnlyTests.il @@ -0,0 +1,2085 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Runtime +{ +} + +.assembly ValidationOnlyTests +{ +} + +// Tests for VerifierOptions.ValidationOnly. Each method reaches one check that only verifiable IL must follow +// (valid but unverifiable, so validation-only mode does not report it), or the part of a check that valid IL +// must follow (reported in both modes). Native int parameters stand for unmanaged pointers. + +.class public sequential ansi sealed beforefieldinit ValidationOnlyStruct + extends [System.Runtime]System.ValueType +{ + .field public int32 F + + .method public hidebysig instance void M() cil managed + { + ret + } +} + +.class public sequential ansi sealed beforefieldinit ValidationOnlyOtherStruct + extends [System.Runtime]System.ValueType +{ + .field public int64 G +} + +.class public sequential ansi sealed beforefieldinit ValidationOnlyRefStruct + extends [System.Runtime]System.ValueType +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = ( 01 00 00 00 ) + .field public int32& P +} + +.class public auto ansi beforefieldinit ValidationOnlyClass + extends [System.Runtime]System.Object +{ + .field public int32 F + .field public string S +} + +.class public auto ansi beforefieldinit ValidationOnlyTestsType + extends [System.Runtime]System.Object +{ + .field public static int32 StaticField + + .method public hidebysig static void Target() cil managed + { + ret + } + + .method public hidebysig static void TakesRef(int32& r) cil managed + { + ret + } + + .method public hidebysig static void TakesString(string s) cil managed + { + ret + } + + .method public hidebysig static void TakesPointer(void* p) cil managed + { + ret + } + + // A managed pointer passed where a native int is expected: the conversion conv.u makes explicitly (Table III.8) + + .method public hidebysig static void Call.ByRefForNativeInt_Invalid_StackUnexpected_Validation_Valid(int32& a) cil managed + { + ldarg.0 + call void ValidationOnlyTestsType::TakesPointer(void*) + ret + } + + .method public hidebysig static void Newobj.SpanOverStaticField_Invalid_StackUnexpected_Validation_Valid() cil managed + { + // The C# compiler's pattern for u8 literals and static data. + ldsflda int32 ValidationOnlyTestsType::StaticField + ldc.i4.4 + newobj instance void valuetype [System.Runtime]System.ReadOnlySpan`1::.ctor(void*, int32) + pop + ret + } + + .method public hidebysig static void Calli.ByRefForNativeInt_Invalid_Unverifiable.StackUnexpected_Validation_Valid(int32& a) cil managed + { + ldarg.0 + ldftn void ValidationOnlyTestsType::TakesPointer(void*) + calli void(void*) + ret + } + + .method public hidebysig static void Call.ReadOnlyByRefForNativeInt_Invalid_StackUnexpected(int32[] a) cil managed + { + ldarg.0 + ldc.i4.0 + readonly. + ldelema [System.Runtime]System.Int32 + call void ValidationOnlyTestsType::TakesPointer(void*) + ret + } + + .method public hidebysig static void Newobj.DelegateByRefMethodPointer_Invalid_StackMethod.DelegatePattern_Validation_Valid(int32& a) cil managed + { + ldnull + ldarg.0 + newobj instance void [System.Runtime]System.Action::.ctor(object, native int) + pop + ret + } + + .method public hidebysig static void StoreLocal.ByRefToNativeInt_Invalid_StackUnexpected(int32& a) cil managed + { + .locals init (native int V_0) + ldarg.0 + stloc.0 + ret + } + + // A native int and a managed pointer merge to a native int + + .method public hidebysig static void Merge.ByRefIntoNativeInt_Invalid_PathStackUnexpected_Validation_Valid(bool c, int32& a, native int p) cil managed + { + .locals init (native int V_0) + ldarg.0 + brtrue.s OTHER + ldarg.2 + br.s JOIN + OTHER: + ldarg.1 + JOIN: + stloc.0 + ret + } + + .method public hidebysig static void Merge.NativeIntIntoByRef_Invalid_PathStackUnexpected_Validation_Valid(int32& a, native int p, bool c) cil managed + { + // The managed pointer reaches JOIN first; the native int then turns the slot into a native int before + // JOIN is imported. + .locals init (native int V_0) + ldarg.0 + ldarg.2 + brtrue.s JOIN + pop + ldarg.1 + JOIN: + stloc.0 + ret + } + + .method public hidebysig static void Merge.Int32AndByRef_Invalid_PathStackUnexpected(bool c, int32& a) cil managed + { + ldarg.0 + brfalse.s OTHER + ldarg.1 + br.s JOIN + OTHER: + ldc.i4.0 + JOIN: + pop + ret + } + + .method public hidebysig static void Merge.ByRefIntoInt32_Invalid_PathStackUnexpected(bool c, int32& a) cil managed + { + ldarg.0 + brfalse.s OTHER + ldc.i4.0 + br.s JOIN + OTHER: + ldarg.1 + JOIN: + pop + ret + } + + .method public hidebysig static void Merge.NativeIntAfterImport_Invalid_ExpectedNumericType(int32& a, native int p, bool c) cil managed + { + // JOIN is imported with the managed pointer first, where & - & is valid. The native int then turns the + // slot into a native int and JOIN is imported again, where native int - & is not valid (Table III.2). + ldarg.2 + brtrue.s OTHER + ldarg.0 + br.s JOIN + OTHER: + ldarg.1 + JOIN: + ldarg.0 + sub + pop + ret + } + + // constrained. with an unmanaged pointer as 'this' + + .method public hidebysig static void Constrained.NativeIntThis_Invalid_ConstrainedCallWithNonByRefThis.StackUnexpected_Validation_Valid(native int p) cil managed + { + ldarg.0 + constrained. ValidationOnlyStruct + callvirt instance string [System.Runtime]System.Object::ToString() + pop + ret + } + + // stind: the type the pointer points to is not a stack type; the value's stack type is checked + + .method public hidebysig static void StoreIndirect.PointeeMismatch_Invalid_StackUnexpected_Validation_Valid(int64& a) cil managed + { + ldarg.0 + ldc.i4.0 + stind.i4 + ret + } + + .method public hidebysig static void StoreIndirect.ObjectValue_Invalid_StackUnexpected(int32& a, object o) cil managed + { + ldarg.0 + ldarg.1 + stind.i4 + ret + } + + // Only stack types are checked (I.12.3.2.1): not the class of an object reference, the type a managed pointer + // points to, or the element type of an array + + .method public hidebysig static string Constrained.OtherType_Invalid_StackUnexpected_Validation_Valid(int32& a) cil managed + { + ldarg.0 + constrained. [System.Runtime]System.Int64 + callvirt instance string [System.Runtime]System.Object::ToString() + ret + } + + .method public hidebysig static void Calli.ClassMismatch_Invalid_Unverifiable.StackUnexpected_Validation_Valid(object o) cil managed + { + ldarg.0 + ldftn void ValidationOnlyTestsType::TakesString(string) + calli void(string) + ret + } + + .method public hidebysig static void LoadElement.Int32FromInt64Array_Invalid_StackUnexpected_Validation_Valid(int64[] a) cil managed + { + ldarg.0 + ldc.i4.0 + ldelem.i4 + pop + ret + } + + .method public hidebysig static void LoadElement.RefFromInt32Array_Invalid_StackUnexpected_Validation_Valid(int32[] a) cil managed + { + ldarg.0 + ldc.i4.0 + ldelem.ref + pop + ret + } + + .method public hidebysig static void StoreElement.Int32ToInt64Array_Invalid_StackUnexpected_Validation_Valid(int64[] a) cil managed + { + ldarg.0 + ldc.i4.0 + ldc.i4.0 + stelem.i4 + ret + } + + .method public hidebysig static void StoreElement.NullRefToInt32Array_Invalid_StackUnexpected.StackUnexpected_Validation_Valid(int32[] a) cil managed + { + ldarg.0 + ldc.i4.0 + ldnull + stelem.ref + ret + } + + .method public hidebysig static void LoadElementAddress.Int64OfInt32Array_Invalid_StackUnexpectedArrayType_Validation_Valid(int32[] a) cil managed + { + ldarg.0 + ldc.i4.0 + ldelema [System.Runtime]System.Int64 + pop + ret + } + + .method public hidebysig static void StoreObject.UnmanagedPointerObjectAsString_Invalid_StackByRef_Validation_Valid(native int p, object o) cil managed + { + ldarg.0 + ldarg.1 + stobj [System.Runtime]System.String + ret + } + + .method public hidebysig static void Box.ObjectAsString_Invalid_StackUnexpected_Validation_Valid(object o) cil managed + { + ldarg.0 + box [System.Runtime]System.String + pop + ret + } + + .method public hidebysig static void StoreElement.ObjectToStringArray_Invalid_StackUnexpected_Validation_Valid(string[] a, object o) cil managed + { + ldarg.0 + ldc.i4.0 + ldarg.1 + stelem [System.Runtime]System.String + ret + } + + .method public hidebysig static void LoadIndirect.RefFromInt32RefIsObjRef_Invalid_StackUnexpected.StackUnexpected_Validation_Valid(int32& a) cil managed + { + // ldind.ref loads an object reference whatever the pointer points to. + ldarg.0 + ldind.ref + call void ValidationOnlyTestsType::TakesString(string) + ret + } + + .method public hidebysig static object LoadElement.RefOfUnknownClassArray_Invalid_StackByRef_Validation_Valid(native int p) cil managed + { + // ldind.ref through an unmanaged pointer pushes an object reference of unknown class; ldelem.ref accepts it as + // an array, as it accepts null, and pushes a null reference. + ldarg.0 + ldind.ref + ldc.i4.0 + ldelem.ref + ret + } + + .method public hidebysig static void LoadElement.RefFromInt32ArrayIsObjRef_Invalid_StackUnexpected.StackUnexpected_Validation_Valid(int32[] a) cil managed + { + // ldelem.ref loads an object reference whatever the array holds. + ldarg.0 + ldc.i4.0 + ldelem.ref + call void ValidationOnlyTestsType::TakesString(string) + ret + } + + .method public hidebysig static void StoreIndirect.RefThroughInt32Ref_Invalid_StackUnexpected_Validation_Valid(int32& a, object o) cil managed + { + ldarg.0 + ldarg.1 + stind.ref + ret + } + + .method public hidebysig static class ValidationOnlyClass TailCall.ReturnOtherClass_Invalid_TailRetType.StackUnexpected_Validation_Valid() cil managed + { + tail. call string ValidationOnlyKeptTestsType::ReturnsString() + ret + } + + // Instructions that are never verifiable (III.3.37, III.3.30, III.3.36, III.2.2) + + .method public hidebysig static void Jmp.ToTarget_Invalid_Unverifiable_Validation_Valid() cil managed + { + jmp void ValidationOnlyTestsType::Target() + } + + .method public hidebysig static void CopyBlock.UnmanagedPointers_Invalid_Unverifiable_Validation_Valid(native int destination, native int source) cil managed + { + ldarg.0 + ldarg.1 + ldc.i4.4 + cpblk + ret + } + + .method public hidebysig static void InitBlock.UnmanagedPointer_Invalid_Unverifiable_Validation_Valid(native int destination) cil managed + { + ldarg.0 + ldc.i4.0 + ldc.i4.4 + initblk + ret + } + + .method public hidebysig static void NoPrefix.Castclass_Invalid_Unverifiable_Validation_Valid(object o) cil managed + { + ldarg.0 + // no. typecheck + .emitbyte 0xFE + .emitbyte 0x19 + .emitbyte 0x01 + castclass [System.Runtime]System.Object + pop + ret + } + + // localsinit is required only for verifiable methods (III.1.8.1.1) + + .method public hidebysig static void InitLocals.LoadLocal_Invalid_InitLocals_Validation_Valid() cil managed + { + .locals (int32 V_0) + ldloc.0 + pop + ret + } + + .method public hidebysig static void InitLocals.LoadLocalAddress_Invalid_InitLocals_Validation_Valid() cil managed + { + .locals (int32 V_0) + ldloca.s V_0 + pop + ret + } + + // Unmanaged pointers (III.1.1.5.1) + + .method public hidebysig static void UnmanagedPointer.LoadArgument_Invalid_UnmanagedPointer_Validation_Valid(int32* p) cil managed + { + ldarg.0 + pop + ret + } + + // Tail calls with managed pointers: correct IL passes none into the frame being removed (III.2.4 Correctness). + // ILVerify does not track which frame a pointer points into, so both modes report every managed pointer. + + .method public hidebysig static void TailCall.LocalAddress_Invalid_TailByRef() cil managed + { + .locals init (int32 V_0) + ldloca.s V_0 + tail. call void ValidationOnlyTestsType::TakesRef(int32&) + ret + } + + .method public hidebysig static void TailCall.LocalAddressAsNativeInt_Invalid_StackUnexpected_Validation_TailByRef() cil managed + { + // Validation-only mode accepts a managed pointer for a native int argument; it is still a managed pointer. + .locals init (int32 V_0) + ldloca.s V_0 + tail. call void ValidationOnlyTestsType::TakesPointer(void*) + ret + } + + .method public hidebysig static void TailCall.CalliLocalAddressAsNativeInt_Invalid_Unverifiable.StackUnexpected_Validation_TailByRef() cil managed + { + .locals init (int32 V_0) + ldloca.s V_0 + ldftn void ValidationOnlyTestsType::TakesPointer(void*) + tail. calli void(void*) + ret + } + + .method public hidebysig static void TailCall.ByRefArgument_Invalid_TailByRef(int32& r) cil managed + { + ldarg.0 + tail. call void ValidationOnlyTestsType::TakesRef(int32&) + ret + } + + .method public hidebysig static void TailCall.ByRefThis_Invalid_TailByRef(valuetype ValidationOnlyStruct& s) cil managed + { + ldarg.0 + tail. call instance void ValidationOnlyStruct::M() + ret + } + + .method public hidebysig static string TailCall.Constrained_Invalid_Constrained.TailByRef(!!T& t) cil managed + { + ldarg.0 + constrained. !!T + tail. callvirt instance string [System.Runtime]System.Object::ToString() + ret + } + + // initobj: only verification checks the type pointed to (III.4.5) + + .method public hidebysig static void InitObj.PointeeMismatch_Invalid_StackUnexpected_Validation_Valid() cil managed + { + .locals init (int32 V_0) + ldloca.s V_0 + initobj [System.Runtime]System.Int64 + ret + } + + .method public hidebysig static void InitObj.ReadOnlyAddress_Invalid_StackUnexpected(int64[] a) cil managed + { + // A controlled-mutability managed pointer cannot be the destination of initobj (III.1.8.1.2.2). + ldarg.0 + ldc.i4.0 + readonly. + ldelema [System.Runtime]System.Int64 + initobj [System.Runtime]System.Int64 + ret + } + + .method public hidebysig static void InitObj.UnmanagedPointer_Invalid_StackByRef.StackUnexpected_Validation_Valid(native int p) cil managed + { + ldarg.0 + initobj [System.Runtime]System.Int32 + ret + } + + .method public hidebysig static void InitObj.Int32Address_Invalid_StackByRef.StackUnexpected_Validation_StackByRef() cil managed + { + ldc.i4.0 + initobj [System.Runtime]System.Int32 + ret + } + + // stloc: only the stack type is a validity rule (III.3.63); a native int is valid as a byref + + .method public hidebysig static void StoreLocal.NativeIntToByRef_Invalid_StackUnexpected_Validation_Valid(native int p) cil managed + { + .locals init (int32& V_0) + ldarg.0 + stloc.0 + ret + } + + .method public hidebysig static void StoreLocal.ByRefToOtherByRef_Invalid_StackUnexpected_Validation_Valid(int32& r) cil managed + { + .locals init (int64& V_0) + ldarg.0 + stloc.0 + ret + } + + .method public hidebysig static void StoreLocal.Int32ToObject_Invalid_StackUnexpected() cil managed + { + .locals init (object V_0) + ldc.i4.0 + stloc.0 + ret + } + + .method public hidebysig static void StoreLocal.ReadOnlyByRef_Invalid_StackUnexpected(object[] a) cil managed + { + .locals init (object& V_0) + ldarg.0 + ldc.i4.0 + readonly. + ldelema [System.Runtime]System.Object + stloc.0 + ret + } + + // starg: only the stack type is checked, and a native int is valid as a byref (III.3.61) + + .method public hidebysig static void StoreArgument.NativeIntToByRef_Invalid_StackUnexpected_Validation_Valid(int32& r, native int p) cil managed + { + ldarg.1 + starg.s r + ret + } + + .method public hidebysig static void StoreArgument.ObjectToString_Invalid_StackUnexpected_Validation_Valid(string s, object o) cil managed + { + ldarg.1 + starg.s s + ret + } + + .method public hidebysig static void StoreArgument.Int32ToString_Invalid_StackUnexpected(string s) cil managed + { + ldc.i4.0 + starg.s s + ret + } + + // Calls: correct IL also passes a native int as a byref (III.1.6) + + .method public hidebysig static void Call.NativeIntForByRef_Invalid_StackUnexpected_Validation_Valid(native int p) cil managed + { + ldarg.0 + call void ValidationOnlyTestsType::TakesRef(int32&) + ret + } + + .method public hidebysig static void Call.NativeIntForValueTypeThis_Invalid_StackUnexpected_Validation_Valid(native int p) cil managed + { + ldarg.0 + call instance void ValidationOnlyStruct::M() + ret + } + + .method public hidebysig static void Calli.NativeIntForByRef_Invalid_Unverifiable.StackUnexpected_Validation_Valid(native int p) cil managed + { + ldarg.0 + ldftn void ValidationOnlyTestsType::TakesRef(int32&) + calli void(int32&) + ret + } + + // ret: only the stack type is a validity rule (III.3.57) + + .method public hidebysig static int64& Return.ByRefToOtherByRef_Invalid_StackUnexpected_Validation_Valid() cil managed + { + ldsflda int32 ValidationOnlyTestsType::StaticField + ret + } + + .method public hidebysig static int32& Return.NativeIntForByRef_Invalid_StackUnexpected.ReturnPtrToStack_Validation_Valid(native int p) cil managed + { + ldarg.0 + ret + } + + // Merging managed pointers to different types (I.12.3.2.1, III.1.8.1.3) + + .method public hidebysig static void Merge.ByRefsToDifferentTypes_Invalid_PathStackUnexpected_Validation_Valid(bool c, int32& a, int64& b) cil managed + { + ldarg.0 + brfalse.s OTHER + ldarg.1 + br.s JOIN + OTHER: + ldarg.2 + JOIN: + pop + ret + } + + .method public hidebysig static void Merge.WritableThenReadOnly_Invalid_PathStackUnexpected_Validation_ReadOnlyIllegalWrite(bool c, int32& p, int64[] a) cil managed + { + // The writable int32& reaches the join first; the readonly int64& makes the merged value readonly. + ldarg.0 + brfalse.s OTHER + ldarg.1 + br.s JOIN + OTHER: + ldarg.2 + ldc.i4.0 + readonly. + ldelema [System.Runtime]System.Int64 + JOIN: + ldc.i4.0 + stind.i4 + ret + } + + .method public hidebysig static void Merge.ReadOnlyThenWritable_Invalid_ReadOnlyIllegalWrite.StackUnexpected.PathStackUnexpected_Validation_ReadOnlyIllegalWrite(bool c, int32& p, int64[] a) cil managed + { + // The readonly int64& reaches the join first and stays readonly. + ldarg.0 + brfalse.s OTHER + ldarg.2 + ldc.i4.0 + readonly. + ldelema [System.Runtime]System.Int64 + br.s JOIN + OTHER: + ldarg.1 + JOIN: + ldc.i4.0 + stind.i4 + ret + } + + .method public hidebysig static void Merge.NativeIntThenReadOnly_Invalid_StackByRef_Validation_PathStackUnexpected(bool c, native int p, int64[] a) cil managed + { + // A readonly & does not merge with a native int. + ldarg.0 + brfalse.s OTHER + ldarg.1 + br.s JOIN + OTHER: + ldarg.2 + ldc.i4.0 + readonly. + ldelema [System.Runtime]System.Int64 + JOIN: + ldc.i4.0 + stind.i4 + ret + } + + .method public hidebysig static void Merge.ReadOnlyThenNativeInt_Invalid_ReadOnlyIllegalWrite.StackUnexpected.PathStackUnexpected_Validation_ReadOnlyIllegalWrite.PathStackUnexpected(bool c, native int p, int64[] a) cil managed + { + // A readonly & does not merge with a native int. + ldarg.0 + brfalse.s OTHER + ldarg.2 + ldc.i4.0 + readonly. + ldelema [System.Runtime]System.Int64 + br.s JOIN + OTHER: + ldarg.1 + JOIN: + ldc.i4.0 + stind.i4 + ret + } + + .method public hidebysig static void Merge.DifferentValueTypes_Invalid_PathStackUnexpected(bool c, valuetype ValidationOnlyStruct a, valuetype ValidationOnlyOtherStruct b) cil managed + { + ldarg.0 + brfalse.s OTHER + ldarg.1 + br.s JOIN + OTHER: + ldarg.2 + JOIN: + pop + ret + } + + // Managed pointer arithmetic: shaded cells of Tables III.2 and III.7 are valid, x cells are not + + .method public hidebysig static void Arithmetic.AddByRefInt32_Invalid_ExpectedNumericType_Validation_Valid(int32& a) cil managed + { + ldarg.0 + ldc.i4.4 + add + pop + ret + } + + .method public hidebysig static void Arithmetic.AddInt32ByRef_Invalid_ExpectedNumericType_Validation_Valid(int32& a) cil managed + { + ldc.i4.4 + ldarg.0 + add + pop + ret + } + + .method public hidebysig static void Arithmetic.SubByRefNativeInt_Invalid_ExpectedNumericType_Validation_Valid(int32& a, native int n) cil managed + { + ldarg.0 + ldarg.1 + sub + pop + ret + } + + .method public hidebysig static void Arithmetic.SubByRefByRef_Invalid_ExpectedNumericType_Validation_Valid(int32& a, int32& b) cil managed + { + ldarg.0 + ldarg.1 + sub + pop + ret + } + + .method public hidebysig static void Arithmetic.AddOvfUnByRefNativeInt_Invalid_ExpectedIntegerType_Validation_Valid(int32& a, native int n) cil managed + { + ldarg.0 + ldarg.1 + add.ovf.un + pop + ret + } + + .method public hidebysig static void Arithmetic.AddOvfUnInt32ByRef_Invalid_ExpectedIntegerType_Validation_Valid(int32& a) cil managed + { + ldc.i4.4 + ldarg.0 + add.ovf.un + pop + ret + } + + .method public hidebysig static void Arithmetic.SubOvfUnByRefByRef_Invalid_ExpectedIntegerType_Validation_Valid(int32& a, int32& b) cil managed + { + ldarg.0 + ldarg.1 + sub.ovf.un + pop + ret + } + + .method public hidebysig static void Arithmetic.AddByRefInt64_Invalid_ExpectedNumericType(int32& a) cil managed + { + ldarg.0 + ldc.i8 4 + add + pop + ret + } + + .method public hidebysig static void Arithmetic.AddResultIsByRef_Invalid_ExpectedNumericType_Validation_Valid(bool c, int32& a) cil managed + { + // The sum merges with a managed pointer, so it must be one. + ldarg.0 + brfalse.s OTHER + ldarg.1 + ldc.i4.4 + add + br.s JOIN + OTHER: + ldarg.1 + JOIN: + pop + ret + } + + .method public hidebysig static void Arithmetic.SubResultIsNativeInt_Invalid_ExpectedNumericType_Validation_Valid(bool c, int32& a, int32& b, native int n) cil managed + { + // The difference merges with a native int, so it must be one. + ldarg.0 + brfalse.s OTHER + ldarg.1 + ldarg.2 + sub + br.s JOIN + OTHER: + ldarg.3 + JOIN: + pop + ret + } + + .method public hidebysig static void Arithmetic.AddInt64ByRef_Invalid_ExpectedNumericType(int32& a) cil managed + { + ldc.i8 4 + ldarg.0 + add + pop + ret + } + + .method public hidebysig static void Arithmetic.AddByRefByRef_Invalid_ExpectedNumericType(int32& a, int32& b) cil managed + { + ldarg.0 + ldarg.1 + add + pop + ret + } + + .method public hidebysig static void Arithmetic.MulByRefInt32_Invalid_ExpectedNumericType(int32& a) cil managed + { + ldarg.0 + ldc.i4.4 + mul + pop + ret + } + + .method public hidebysig static void Arithmetic.SubInt32ByRef_Invalid_ExpectedNumericType(int32& a) cil managed + { + ldc.i4.4 + ldarg.0 + sub + pop + ret + } + + .method public hidebysig static void Arithmetic.AddOvfByRefInt32_Invalid_ExpectedIntegerType(int32& a) cil managed + { + ldarg.0 + ldc.i4.4 + add.ovf + pop + ret + } + + .method public hidebysig static void Arithmetic.AddReadOnlyByRefInt32_Invalid_ExpectedNumericType(int32[] a) cil managed + { + ldarg.0 + ldc.i4.0 + readonly. + ldelema [System.Runtime]System.Int32 + ldc.i4.4 + add + pop + ret + } + + // Conversions that stop GC tracking: shaded cells of Table III.8 are valid, x cells are not + + .method public hidebysig static void Convert.ByRefToNativeUInt_Invalid_ExpectedNumericType_Validation_Valid(int32& a) cil managed + { + ldarg.0 + conv.u + pop + ret + } + + .method public hidebysig static void Convert.ByRefToNativeInt_Invalid_ExpectedNumericType_Validation_Valid(int32& a) cil managed + { + ldarg.0 + conv.i + pop + ret + } + + .method public hidebysig static void Convert.ObjectToInt64_Invalid_ExpectedNumericType_Validation_Valid(object o) cil managed + { + ldarg.0 + conv.i8 + pop + ret + } + + .method public hidebysig static void Convert.ObjectToUInt64_Invalid_ExpectedNumericType_Validation_Valid(object o) cil managed + { + ldarg.0 + conv.u8 + pop + ret + } + + .method public hidebysig static void Convert.ByRefToInt32_Invalid_ExpectedNumericType(int32& a) cil managed + { + ldarg.0 + conv.i4 + pop + ret + } + + .method public hidebysig static void Convert.ObjectToFloat64_Invalid_ExpectedNumericType(object o) cil managed + { + ldarg.0 + conv.r8 + pop + ret + } + + .method public hidebysig static void Convert.ReadOnlyByRefToNativeUInt_Invalid_ExpectedNumericType(int32[] a) cil managed + { + ldarg.0 + ldc.i4.0 + readonly. + ldelema [System.Runtime]System.Int32 + conv.u + pop + ret + } + + // Fields through an unmanaged pointer (III.4.10, III.4.11, III.4.28) + + .method public hidebysig static void LoadField.UnmanagedPointer_Invalid_StackUnexpected_Validation_Valid(native int p) cil managed + { + ldarg.0 + ldfld int32 ValidationOnlyStruct::F + pop + ret + } + + .method public hidebysig static void LoadField.WrongClass_Invalid_StackUnexpected_Validation_Valid(string s) cil managed + { + ldarg.0 + ldfld int32 ValidationOnlyClass::F + pop + ret + } + + .method public hidebysig static void LoadFieldAddress.UnmanagedPointer_Invalid_StackUnexpected.StackUnexpected_Validation_Valid(native int p) cil managed + { + // Through an unmanaged pointer, ldflda pushes an unmanaged pointer. + .locals init (native int V_0) + ldarg.0 + ldflda int32 ValidationOnlyStruct::F + stloc.0 + ret + } + + .method public hidebysig static void LoadFieldAddress.WrongClass_Invalid_StackUnexpected_Validation_Valid(string s) cil managed + { + ldarg.0 + ldflda int32 ValidationOnlyClass::F + pop + ret + } + + .method public hidebysig static void StoreField.UnmanagedPointer_Invalid_StackUnexpected_Validation_Valid(native int p) cil managed + { + ldarg.0 + ldc.i4.0 + stfld int32 ValidationOnlyStruct::F + ret + } + + .method public hidebysig static void StoreField.WrongClass_Invalid_StackUnexpected_Validation_Valid(string s) cil managed + { + ldarg.0 + ldc.i4.0 + stfld int32 ValidationOnlyClass::F + ret + } + + .method public hidebysig static void StoreField.NativeIntToByRefField_Invalid_StackUnexpected_Validation_Valid(valuetype ValidationOnlyRefStruct& r, native int p) cil managed + { + ldarg.0 + ldarg.1 + stfld int32& ValidationOnlyRefStruct::P + ret + } + + .method public hidebysig static void StoreField.ObjectToString_Invalid_StackUnexpected_Validation_Valid(class ValidationOnlyClass c, object o) cil managed + { + ldarg.0 + ldarg.1 + stfld string ValidationOnlyClass::S + ret + } + + .method public hidebysig static void StoreField.Int32ToString_Invalid_StackUnexpected(class ValidationOnlyClass c) cil managed + { + ldarg.0 + ldc.i4.0 + stfld string ValidationOnlyClass::S + ret + } + + .method public hidebysig static void LoadField.Int32Object_Invalid_StackUnexpected(int32 i) cil managed + { + ldarg.0 + ldfld int32 ValidationOnlyClass::F + pop + ret + } + + .method public hidebysig static void LoadFieldAddress.Int32Object_Invalid_StackUnexpected(int32 i) cil managed + { + ldarg.0 + ldflda int32 ValidationOnlyClass::F + pop + ret + } + + .method public hidebysig static void StoreField.Int32Object_Invalid_StackUnexpected(int32 i) cil managed + { + ldarg.0 + ldc.i4.0 + stfld int32 ValidationOnlyClass::F + ret + } + + // Indirect loads and stores through an unmanaged pointer (III.3.42, III.3.62, III.4.13, III.4.29) + + .method public hidebysig static void LoadIndirect.UnmanagedPointer_Invalid_StackByRef_Validation_Valid(native int p) cil managed + { + ldarg.0 + ldind.i4 + pop + ret + } + + .method public hidebysig static void LoadObject.UnmanagedPointer_Invalid_StackByRef_Validation_Valid(native int p) cil managed + { + ldarg.0 + ldobj [System.Runtime]System.Int64 + pop + ret + } + + .method public hidebysig static void LoadIndirectRef.UnmanagedPointer_Invalid_StackByRef_Validation_Valid(native int p) cil managed + { + // The object's class is unknown, so it can be passed as a string. + ldarg.0 + ldind.ref + call void ValidationOnlyTestsType::TakesString(string) + ret + } + + .method public hidebysig static void LoadIndirect.ObjectAddress_Invalid_StackByRef(object o) cil managed + { + ldarg.0 + ldind.i4 + pop + ret + } + + .method public hidebysig static void LoadObject.PointeeMismatch_Invalid_StackUnexpected_Validation_Valid(int64& a) cil managed + { + ldarg.0 + ldobj [System.Runtime]System.Int32 + pop + ret + } + + .method public hidebysig static void StoreIndirect.UnmanagedPointer_Invalid_StackByRef_Validation_Valid(native int p) cil managed + { + ldarg.0 + ldc.i4.0 + stind.i4 + ret + } + + .method public hidebysig static void StoreIndirectRef.UnmanagedPointer_Invalid_StackByRef_Validation_Valid(native int p) cil managed + { + ldarg.0 + ldstr "a" + stind.ref + ret + } + + .method public hidebysig static void StoreIndirectRef.UnmanagedPointerInt32_Invalid_StackByRef_Validation_StackObjRef(native int p) cil managed + { + ldarg.0 + ldc.i4.0 + stind.ref + ret + } + + .method public hidebysig static void StoreIndirect.UnmanagedPointerObject_Invalid_StackByRef_Validation_StackUnexpected(native int p, object o) cil managed + { + ldarg.0 + ldarg.1 + stind.i4 + ret + } + + .method public hidebysig static void StoreIndirect.ObjectAddress_Invalid_StackByRef(object o) cil managed + { + ldarg.0 + ldc.i4.0 + stind.i4 + ret + } + + // cpobj and mkrefany with an unmanaged pointer (III.4.4, III.4.19) + + .method public hidebysig static void CopyObject.UnmanagedPointerSource_Invalid_StackByRef_Validation_Valid(int32& d, native int s) cil managed + { + ldarg.0 + ldarg.1 + cpobj [System.Runtime]System.Int32 + ret + } + + .method public hidebysig static void CopyObject.UnmanagedPointerDestination_Invalid_StackByRef_Validation_Valid(native int d, int32& s) cil managed + { + ldarg.0 + ldarg.1 + cpobj [System.Runtime]System.Int32 + ret + } + + .method public hidebysig static void CopyObject.Int32Source_Invalid_StackByRef(int32& d) cil managed + { + ldarg.0 + ldc.i4.0 + cpobj [System.Runtime]System.Int32 + ret + } + + .method public hidebysig static void CopyObject.Int32Destination_Invalid_StackByRef(int32& s) cil managed + { + ldc.i4.0 + ldarg.0 + cpobj [System.Runtime]System.Int32 + ret + } + + .method public hidebysig static void Mkrefany.Int32Value_Invalid_StackByRef() cil managed + { + ldc.i4.0 + mkrefany [System.Runtime]System.Int32 + pop + ret + } +} + +.class public abstract auto ansi beforefieldinit ValidationOnlyAbstract + extends [System.Runtime]System.Object +{ + .method public hidebysig newslot abstract virtual instance void M() cil managed + { + } + + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed + { + ldarg.0 + call instance void [System.Runtime]System.Object::.ctor() + ret + } +} + +.class public auto ansi sealed ValidationOnlyThreeParameterDelegate + extends [System.Runtime]System.MulticastDelegate +{ + .method public hidebysig specialname rtspecialname instance void .ctor(object o, native int m, int32 x) runtime managed + { + } + + .method public hidebysig newslot virtual instance void Invoke() runtime managed + { + } +} + +.class public auto ansi sealed ValidationOnlyInt32Delegate + extends [System.Runtime]System.MulticastDelegate +{ + .method public hidebysig specialname rtspecialname instance void .ctor(object o, int32 m) runtime managed + { + } + + .method public hidebysig newslot virtual instance void Invoke() runtime managed + { + } +} + +.class public auto ansi sealed ValidationOnlyInt32ObjectDelegate + extends [System.Runtime]System.MulticastDelegate +{ + .method public hidebysig specialname rtspecialname instance void .ctor(int32 o, native int m) runtime managed + { + } + + .method public hidebysig newslot virtual instance void Invoke() runtime managed + { + } +} + +.class public auto ansi sealed ValidationOnlyStringObjectDelegate + extends [System.Runtime]System.MulticastDelegate +{ + .method public hidebysig specialname rtspecialname instance void .ctor(string o, native int m) runtime managed + { + } + + .method public hidebysig newslot virtual instance class [System.Runtime]System.Type Invoke() runtime managed + { + } +} + +.class public auto ansi beforefieldinit ValidationOnlyPrivate + extends [System.Runtime]System.Object +{ + .field private static int32 s_private + .field private int32 _private +} + +// Rules that valid IL must follow and that no other test reached in validation-only mode. Each method reaches +// one such rule (some also reach others on the way); validation-only mode reports what verification reports, +// minus the rules that only verifiable IL must follow. +.class public auto ansi beforefieldinit ValidationOnlyKeptTestsType + extends [System.Runtime]System.Object +{ + .field public int32 InstanceField + + .method public hidebysig static void Target() cil managed + { + ret + } + + .method public hidebysig static void TakesInt32(int32 i) cil managed + { + ret + } + + .method public hidebysig static string ReturnsString() cil managed + { + ldnull + ret + } + + .method public hidebysig static void StructOnly() cil managed + { + ret + } + + // Stack, tokens and the instruction stream + + .method public hidebysig static void Kept.StackOverflow_Invalid_StackOverflow() cil managed + { + .maxstack 1 + ldc.i4.0 + ldc.i4.0 + pop + pop + ret + } + + .method public hidebysig static void Kept.ArgumentNumber_Invalid_UnrecognizedArgumentNumber(int32 a) cil managed + { + // ldarg.s 5 + .emitbyte 0x0E + .emitbyte 0x05 + pop + ret + } + + .method public hidebysig static void Kept.LocalNumber_Invalid_UnrecognizedLocalNumber() cil managed + { + .locals init (int32 V_0) + // ldloc.s 5 + .emitbyte 0x11 + .emitbyte 0x05 + pop + ret + } + + .method public hidebysig static void Kept.TokenResolve_Invalid_TokenResolve() cil managed + { + // ldtoken 0x0100FFFF, a TypeRef row that does not exist + .emitbyte 0xD0 + .emitbyte 0xFF + .emitbyte 0xFF + .emitbyte 0x00 + .emitbyte 0x01 + pop + ret + } + + .method public hidebysig static void Kept.TypeTokenIsMethod_Invalid_ExpectedTypeToken(object o) cil managed + { + ldarg.0 + // castclass 0x06000001 (a method) + .emitbyte 0x74 + .emitbyte 0x01 + .emitbyte 0x00 + .emitbyte 0x00 + .emitbyte 0x06 + pop + ret + } + + .method public hidebysig static void Kept.FieldTokenIsMethod_Invalid_ExpectedFieldToken() cil managed + { + // ldsfld 0x06000001 (a method) + .emitbyte 0x7E + .emitbyte 0x01 + .emitbyte 0x00 + .emitbyte 0x00 + .emitbyte 0x06 + pop + ret + } + + .method public hidebysig static void Kept.MethodTokenIsField_Invalid_ExpectedMethodToken() cil managed + { + // call 0x04000001 (a field) + .emitbyte 0x28 + .emitbyte 0x01 + .emitbyte 0x00 + .emitbyte 0x00 + .emitbyte 0x04 + ret + } + + .method public hidebysig static void Kept.StringTokenIsMethod_Invalid_StringOperand() cil managed + { + // ldstr 0x06000001 (a method) + .emitbyte 0x72 + .emitbyte 0x01 + .emitbyte 0x00 + .emitbyte 0x00 + .emitbyte 0x06 + pop + ret + } + + .method public hidebysig static void Kept.MethodEndsInsideInstruction_Invalid_MethodEnd() cil managed + { + ret + // ldc.i4 without its operand + .emitbyte 0x20 + } + + .method public hidebysig static void Kept.UnknownOpcode_Invalid_UnknownOpcode() cil managed + { + .emitbyte 0xA6 + ret + } + + .method public hidebysig static void Kept.UnalignedPrefixOnNop_Invalid_Unaligned() cil managed + { + unaligned. 1 + nop + ret + } + + .method public hidebysig static void Kept.VolatilePrefixOnNop_Invalid_Volatile() cil managed + { + volatile. + nop + ret + } + + .method public hidebysig static void Kept.PathStackDepth_Invalid_PathStackDepth(bool c) cil managed + { + ldc.i4.0 + ldarg.0 + brfalse.s JOIN + ldc.i4.0 + JOIN: + pop + ret + } + + // Arguments, locals, calls and fields + + .method public hidebysig static void Kept.ByrefOfByref_Invalid_ByrefOfByref(int32& r) cil managed + { + ldarga.s r + pop + ret + } + + .method public hidebysig static void Kept.CallVirtOnValueType_Invalid_CallVirtOnValueType() cil managed + { + .locals init (valuetype ValidationOnlyStruct V_0) + ldloca.s V_0 + callvirt instance void ValidationOnlyStruct::M() + ret + } + + .method public hidebysig static void Kept.CallAbstract_Invalid_CallAbstract.ThisMismatch_Validation_CallAbstract(class ValidationOnlyAbstract a) cil managed + { + ldarg.0 + call instance void ValidationOnlyAbstract::M() + ret + } + + .method public hidebysig static void Kept.NewobjAbstractClass_Invalid_NewobjAbstractClass() cil managed + { + newobj instance void ValidationOnlyAbstract::.ctor() + pop + ret + } + + .method public hidebysig static void Kept.NewobjArrayOfByRefLike_Invalid_ArrayByRef() cil managed + { + ldc.i4.1 + newobj instance void valuetype ValidationOnlyRefStruct[]::.ctor(int32) + pop + ret + } + + .method public hidebysig static void Kept.CallCtorOnOtherObject_Invalid_CallCtor(object o) cil managed + { + ldarg.0 + call instance void [System.Runtime]System.Object::.ctor() + ret + } + + .method public hidebysig static string Kept.ConstrainedObjectThis_Invalid_ConstrainedCallWithNonByRefThis(object o) cil managed + { + ldarg.0 + constrained. [System.Runtime]System.Object + callvirt instance string [System.Runtime]System.Object::ToString() + ret + } + + .method public hidebysig static void Kept.UnsatisfiedMethodInst_Invalid_UnsatisfiedMethodInst() cil managed + { + call void ValidationOnlyKeptTestsType::StructOnly() + ret + } + + .method public hidebysig static void Kept.DelegateCtorThreeParameters_Invalid_DelegateCtor() cil managed + { + // ILVerify pops the two arguments of a delegate constructor and leaves the third, int32, on the stack. + ldc.i4.0 + ldnull + ldftn void ValidationOnlyKeptTestsType::Target() + newobj instance void ValidationOnlyThreeParameterDelegate::.ctor(object, native int, int32) + pop + pop + ret + } + + .method public hidebysig static void Kept.DelegateCtorInt32Object_Invalid_StackUnexpected_Validation_DelegateCtorSigO.StackUnexpected() cil managed + { + ldnull + ldftn void ValidationOnlyKeptTestsType::Target() + newobj instance void ValidationOnlyInt32ObjectDelegate::.ctor(int32, native int) + pop + ret + } + + .method public hidebysig static void Kept.DelegateCtorUnboxedTarget_Invalid_StackUnexpected.DelegateCtorSigO.DelegateCtor() cil managed + { + .locals init (valuetype ValidationOnlyStruct V_0) + ldloc.0 + ldftn instance void ValidationOnlyStruct::M() + newobj instance void [System.Runtime]System.Action::.ctor(object, native int) + pop + ret + } + + .method public hidebysig static void Kept.DelegateCtorStringObject_Invalid_StackUnexpected_Validation_DelegateCtorSigO() cil managed + { + // A delegate constructor whose first parameter is not System.Object (II.14.6). + newobj instance void [System.Runtime]System.Object::.ctor() + ldftn instance class [System.Runtime]System.Type [System.Runtime]System.Object::GetType() + newobj instance void ValidationOnlyStringObjectDelegate::.ctor(string, native int) + pop + ret + } + + .method public hidebysig static void Kept.DelegateCtorNullMethodPointer_Invalid_StackMethod.DelegatePattern_Validation_StackUnexpected() cil managed + { + ldnull + ldnull + newobj instance void [System.Runtime]System.Action::.ctor(object, native int) + pop + ret + } + + .method public hidebysig static void Kept.DelegateCtorFloatMethodPointer_Invalid_StackMethod.DelegatePattern_Validation_StackUnexpected() cil managed + { + ldnull + ldc.r8 1 + newobj instance void [System.Runtime]System.Action::.ctor(object, native int) + pop + ret + } + + .method public hidebysig static void Kept.DelegateCtorInt32Method_Invalid_DelegateCtorSigI() cil managed + { + ldnull + ldftn void ValidationOnlyKeptTestsType::Target() + newobj instance void ValidationOnlyInt32Delegate::.ctor(object, int32) + pop + ret + } + + .method public hidebysig static void Kept.LdftnCtor_Invalid_LdftnCtor() cil managed + { + ldftn instance void [System.Runtime]System.Object::.ctor() + pop + ret + } + + .method public hidebysig static int32 Kept.TailCallReturnType_Invalid_TailRetType.StackUnexpected() cil managed + { + tail. call string ValidationOnlyKeptTestsType::ReturnsString() + ret + } + + .method public hidebysig static void Kept.TailCallStackNotEmpty_Invalid_TailStackEmpty.ReturnVoid() cil managed + { + ldc.i4.0 + ldc.i4.0 + tail. call void ValidationOnlyKeptTestsType::TakesInt32(int32) + ret + } + + .method public hidebysig static void Kept.LoadStaticFromInstanceField_Invalid_ExpectedStaticField() cil managed + { + ldsfld int32 ValidationOnlyKeptTestsType::InstanceField + pop + ret + } + + .method public hidebysig static void Kept.LoadStaticAddressOfInstanceField_Invalid_ExpectedStaticField() cil managed + { + ldsflda int32 ValidationOnlyKeptTestsType::InstanceField + pop + ret + } + + .method public hidebysig static void Kept.StoreStaticToInstanceField_Invalid_ExpectedStaticField() cil managed + { + ldc.i4.0 + stsfld int32 ValidationOnlyKeptTestsType::InstanceField + ret + } + + .method public hidebysig static void Kept.LoadAddressOfPrivateField_Invalid_FieldAccess() cil managed + { + ldsflda int32 ValidationOnlyPrivate::s_private + pop + ret + } + + .method public hidebysig static void Kept.StorePrivateField_Invalid_FieldAccess() cil managed + { + ldc.i4.0 + stsfld int32 ValidationOnlyPrivate::s_private + ret + } + + .method public hidebysig static void Kept.StoreIndirectRefNativeInt_Invalid_StackUnexpected_Validation_StackObjRef(valuetype ValidationOnlyRefStruct& r, native int p) cil managed + { + // stind.ref stores an object reference (III.3.62) + ldarg.0 + ldflda int32& ValidationOnlyRefStruct::P + ldarg.1 + stind.ref + ret + } + + .method public hidebysig static void Kept.StoreThroughReadOnlyByRef_Invalid_ReadOnlyIllegalWrite(int32[] a) cil managed + { + ldarg.0 + ldc.i4.0 + readonly. + ldelema [System.Runtime]System.Int32 + ldc.i4.0 + stind.i4 + ret + } + + // Operands of single instructions + + .method public hidebysig static void Kept.BranchOnFloat_Invalid_StackUnexpected() cil managed + { + ldc.r8 1 + brtrue.s END + END: + ret + } + + .method public hidebysig static void Kept.BranchInt32Int64_Invalid_StackUnexpected() cil managed + { + ldc.i4.0 + ldc.i8 0 + beq.s END + END: + ret + } + + .method public hidebysig static void Kept.CompareInt32Int64_Invalid_StackUnexpected() cil managed + { + ldc.i4.0 + ldc.i8 0 + ceq + pop + ret + } + + .method public hidebysig static void Kept.AddInt32Int64_Invalid_StackUnexpected() cil managed + { + ldc.i4.0 + ldc.i8 0 + add + pop + ret + } + + .method public hidebysig static void Kept.NegateObject_Invalid_ExpectedNumericType(object o) cil managed + { + ldarg.0 + neg + pop + ret + } + + .method public hidebysig static void Kept.NotFloat_Invalid_ExpectedIntegerType() cil managed + { + ldc.r8 1 + not + pop + ret + } + + .method public hidebysig static void Kept.ThrowInt32_Invalid_StackObjRef.ThrowOrCatchOnlyExceptionType() cil managed + { + ldc.i4.0 + throw + } + + .method public hidebysig static void Kept.BoxNullAsInt32_Invalid_StackUnexpected() cil managed + { + ldnull + box [System.Runtime]System.Int32 + pop + ret + } + + .method public hidebysig static void Kept.UnboxInt32_Invalid_StackObjRef() cil managed + { + ldc.i4.0 + unbox.any [System.Runtime]System.Int32 + pop + ret + } + + .method public hidebysig static void Kept.UnboxReferenceType_Invalid_ValueTypeExpected(object o) cil managed + { + ldarg.0 + unbox [System.Runtime]System.String + pop + ret + } + + .method public hidebysig static void Kept.CkfiniteInt32_Invalid_ExpectedFloatType() cil managed + { + ldc.i4.0 + ckfinite + pop + ret + } + + .method public hidebysig static void Kept.LocallocFloatSize_Invalid_Unverifiable.ExpectedIntegerType_Validation_ExpectedIntegerType() cil managed + { + ldc.r8 1 + localloc + pop + ret + } + + .method public hidebysig static void Kept.CopyBlockFloatSize_Invalid_Unverifiable.ExpectedIntegerType_Validation_ExpectedIntegerType(native int d, native int s) cil managed + { + ldarg.0 + ldarg.1 + ldc.r8 1 + cpblk + ret + } + + .method public hidebysig static void Kept.InitBlockFloatSize_Invalid_Unverifiable.ExpectedIntegerType_Validation_ExpectedIntegerType(native int d) cil managed + { + ldarg.0 + ldc.i4.0 + ldc.r8 1 + initblk + ret + } + + .method public hidebysig static void Kept.InitBlockFloatValue_Invalid_Unverifiable.ExpectedIntegerType_Validation_ExpectedIntegerType(native int d) cil managed + { + ldarg.0 + ldc.r8 1 + ldc.i4.4 + initblk + ret + } + + // Arrays + + .method public hidebysig static void Kept.LoadElementInt64Index_Invalid_StackUnexpected(int32[] a) cil managed + { + ldarg.0 + ldc.i8 0 + ldelem.i4 + pop + ret + } + + .method public hidebysig static void Kept.LoadElementFromInt32_Invalid_ExpectedArray() cil managed + { + ldc.i4.0 + ldc.i4.0 + ldelem.i4 + pop + ret + } + + .method public hidebysig static void Kept.StoreElementInt64Index_Invalid_StackUnexpected(int32[] a) cil managed + { + ldarg.0 + ldc.i8 0 + ldc.i4.0 + stelem.i4 + ret + } + + .method public hidebysig static void Kept.StoreElementToInt32_Invalid_ExpectedArray() cil managed + { + ldc.i4.0 + ldc.i4.0 + ldc.i4.0 + stelem.i4 + ret + } + + .method public hidebysig static void Kept.StoreElementRefInt32_Invalid_StackUnexpected_Validation_StackObjRef(int32[] a) cil managed + { + // stelem.ref stores an object reference (III.4.26) + ldarg.0 + ldc.i4.0 + ldc.i4.0 + stelem.ref + ret + } + + .method public hidebysig static void Kept.StoreElementObjectToInt32Array_Invalid_StackUnexpected(int32[] a, object o) cil managed + { + ldarg.0 + ldc.i4.0 + ldarg.1 + stelem.i4 + ret + } + + .method public hidebysig static void Kept.LoadElementAddressInt64Index_Invalid_StackUnexpected(int32[] a) cil managed + { + ldarg.0 + ldc.i8 0 + ldelema [System.Runtime]System.Int32 + pop + ret + } + + .method public hidebysig static void Kept.LoadElementAddressFromInt32_Invalid_ExpectedArray() cil managed + { + ldc.i4.0 + ldc.i4.0 + ldelema [System.Runtime]System.Int32 + pop + ret + } + + .method public hidebysig static void Kept.LoadLengthOfObject_Invalid_ExpectedArray(object o) cil managed + { + ldarg.0 + ldlen + pop + ret + } + + // Exception handling + + .method public hidebysig static void Kept.EndfinallyOutsideHandler_Invalid_Endfinally() cil managed + { + endfinally + } + + .method public hidebysig static void Kept.EndfilterNotLast_Invalid_EHClauseFilterEnd.Endfilter() cil managed + { + .try + { + leave.s END + } + filter + { + pop + ldc.i4.1 + endfilter + ldc.i4.1 + endfilter + } + { + pop + leave.s END + } + END: + ret + } + + .method public hidebysig static void Kept.EndfilterObject_Invalid_StackUnexpected() cil managed + { + .try + { + leave.s END + } + filter + { + pop + ldnull + endfilter + } + { + pop + leave.s END + } + END: + ret + } + + .method public hidebysig static void Kept.LeaveIntoNestedTry_Invalid_LeaveIntoTry() cil managed + { + .try + { + leave.s INNER + .try + { + nop + .try + { + INNER: + leave.s END + } + finally + { + endfinally + } + leave.s END + } + finally + { + endfinally + } + } + finally + { + endfinally + } + END: + ret + } + + .method public hidebysig static void Kept.LeaveFromCatchIntoTry_Invalid_LeaveIntoTry.LeaveIntoTry() cil managed + { + .try + { + leave.s END + } + catch [System.Runtime]System.Object + { + pop + leave.s MIDDLE + } + .try + { + nop + MIDDLE: + leave.s END + } + finally + { + endfinally + } + END: + ret + } + + .method public hidebysig static void Kept.FallthroughFromNestedTry_Invalid_FallthroughException() cil managed + { + OUTER_BEGIN: + nop + INNER_END: + nop + ldc.i4.0 + brtrue.s INNER_END + leave.s END + INNER_HANDLER: + endfinally + OUTER_END: + endfinally + OUTER_HANDLER_END: + END: + ret + .try OUTER_BEGIN to INNER_END finally handler INNER_HANDLER to OUTER_END + .try OUTER_BEGIN to OUTER_END finally handler OUTER_END to OUTER_HANDLER_END + } + + .method public hidebysig static void Kept.FallthroughBetweenCatchHandlers_Invalid_FallthroughIntoHandler() cil managed + { + TRY1: + leave.s END + TRY2: + leave.s END + H1: + pop + nop + H2: + pop + leave.s END + H2_END: + END: + ret + .try TRY1 to TRY2 catch [System.Runtime]System.Object handler H1 to H2 + .try TRY2 to H1 catch [System.Runtime]System.Object handler H2 to H2_END + } + + .method public hidebysig static void Kept.BranchBetweenCatchHandlers_Invalid_BranchIntoHandler() cil managed + { + TRY1: + leave.s END + TRY2: + leave.s END + H1: + pop + br.s H2_MIDDLE + H2: + pop + H2_MIDDLE: + leave.s END + H2_END: + END: + ret + .try TRY1 to TRY2 catch [System.Runtime]System.Object handler H1 to H2 + .try TRY2 to H1 catch [System.Runtime]System.Object handler H2 to H2_END + } + + .method public hidebysig static void Kept.FallthroughOutOfNestedCatch_Invalid_FallthroughException() cil managed + { + TRY_B: + leave.s END + HB: + pop + TRY_A: + leave.s AFTER + HA: + pop + nop + AFTER: + leave.s END + HB_END: + END: + ret + .try TRY_A to HA catch [System.Runtime]System.Object handler HA to AFTER + .try TRY_B to HB catch [System.Runtime]System.Object handler HB to HB_END + } + + .method public hidebysig static void Kept.BranchOutOfNestedFinally_Invalid_BranchOutOfFinally() cil managed + { + TRY_B: + leave.s END + HB: + pop + TRY_A: + leave.s AFTER + HA: + br.s AFTER + AFTER: + leave.s END + HB_END: + END: + ret + .try TRY_A to HA finally handler HA to AFTER + .try TRY_B to HB catch [System.Runtime]System.Object handler HB to HB_END + } + + .method public hidebysig static void Kept.BranchOutOfNestedCatch_Invalid_BranchOutOfHandler() cil managed + { + TRY_B: + leave.s END + HB: + pop + TRY_A: + leave.s AFTER + HA: + pop + br.s AFTER + AFTER: + leave.s END + HB_END: + END: + ret + .try TRY_A to HA catch [System.Runtime]System.Object handler HA to AFTER + .try TRY_B to HB catch [System.Runtime]System.Object handler HB to HB_END + } + + .method public hidebysig static void Kept.BranchBetweenFilters_Invalid_BranchIntoFilter() cil managed + { + TRY1: + leave.s END + TRY2: + leave.s END + F1: + pop + ldc.i4.0 + brtrue.s F2_MIDDLE + ldc.i4.1 + endfilter + H1: + pop + leave.s END + F2: + pop + F2_MIDDLE: + ldc.i4.1 + endfilter + H2: + pop + leave.s END + H2_END: + END: + ret + .try TRY1 to TRY2 filter F1 handler H1 to F2 + .try TRY2 to F1 filter F2 handler H2 to H2_END + } + + .method public hidebysig static void Kept.BranchBackBetweenFilters_Invalid_BranchOutOfFilter() cil managed + { + TRY1: + leave.s TRY2 + TRY2: + leave.s END + F1: + pop + F1_MIDDLE: + ldc.i4.1 + endfilter + H1: + pop + leave.s END + F2: + pop + ldc.i4.0 + brtrue.s F1_MIDDLE + ldc.i4.1 + endfilter + H2: + pop + leave.s END + H2_END: + END: + ret + .try TRY1 to TRY2 filter F1 handler H1 to F2 + .try TRY2 to F1 filter F2 handler H2 to H2_END + } +} diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/ValidationOnlyTests.ilproj b/src/coreclr/tools/ILVerification.Tests/ILTests/ValidationOnlyTests.ilproj new file mode 100644 index 00000000000000..356b4dcc778989 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/ValidationOnlyTests.ilproj @@ -0,0 +1,9 @@ + + + $(MSBuildProjectName) + + + + + + diff --git a/src/coreclr/tools/ILVerification.Tests/TestDataLoader.cs b/src/coreclr/tools/ILVerification.Tests/TestDataLoader.cs index 877743ee4f6b3b..4484faa4c2fcc3 100644 --- a/src/coreclr/tools/ILVerification.Tests/TestDataLoader.cs +++ b/src/coreclr/tools/ILVerification.Tests/TestDataLoader.cs @@ -174,6 +174,102 @@ private static VerifierError ParseExpectedError(string name, string testName) return error; } + /// + /// Returns all methods that contain valid or invalid IL, as for and + /// , with the errors expected when verifying with + /// set. These are the errors in the method name, unless an + /// Invalid method name ends with the following suffix: + /// _Validation_[Valid | ExpectedVerifierError1.ExpectedVerifierError2....ExpectedVerifierErrorN] + /// E.g.: 'LocAlloc_Invalid_Unverifiable_Validation_Valid' + /// + public static IEnumerable GetMethodsForValidationOnlyMode() + { + var methodSelector = new Func((mparams, methodHandle) => + { + if (mparams.Length == 2 && mparams[1] == "Valid") + { + return new ValidationOnlyTestCase + { + MetadataToken = MetadataTokens.GetToken(methodHandle), + ExpectedVerifierErrors = new List() + }; + } + if (mparams.Length == 3 && mparams[1] == "Invalid") + { + var verificationErrors = new List(); + foreach (var item in mparams[2].Split('.')) + { + if (Enum.TryParse(item, out VerifierError expectedError)) + { + verificationErrors.Add(expectedError); + } + } + + return new ValidationOnlyTestCase + { + MetadataToken = MetadataTokens.GetToken(methodHandle), + ExpectedVerifierErrors = verificationErrors + }; + } + return null; + }); + return GetTestMethodsFromDll(methodSelector); + } + + private const string ValidationSuffix = "_Validation_"; + + /// + /// Splits a test method name into the parts that the selectors take (see the examples below), or returns null + /// if the name does not follow the naming convention. gets the errors of + /// a '_Validation_' suffix, or null without one. A malformed suffix throws, so no method silently drops out. + /// + private static string[] SplitTestMethodName(string methodName, out List validationErrors) + { + validationErrors = null; + var name = methodName; + var validationIndex = methodName.LastIndexOf(ValidationSuffix, StringComparison.Ordinal); + if (validationIndex >= 0) + { + name = methodName.Substring(0, validationIndex); + if (name.Contains(ValidationSuffix, StringComparison.Ordinal)) + throw new ArgumentException($"Test '{methodName}' has more than one '{ValidationSuffix}' suffix."); + + validationErrors = new List(); + var errors = methodName.Substring(validationIndex + ValidationSuffix.Length); + if (errors != "Valid") + { + foreach (var item in errors.Split('.')) + { + validationErrors.Add(ParseExpectedError(item, methodName)); + } + } + } + + string[] mparams = null; + var index = name.LastIndexOf("_Valid", StringComparison.Ordinal); + if (index < 0) + index = name.LastIndexOf("_Invalid", StringComparison.Ordinal); + if (index >= 0) + { + var substring = name.Substring(index + 1); + var split = substring.Split('_'); + mparams = new string[split.Length + 1]; + split.CopyTo(mparams, 1); + mparams[0] = name.Substring(0, index); + // examples of methodName to mparams transformation: + // * `get_Property` -> [ 'get_Property' ] + // * `CheckSomething_Valid` -> [ 'CheckSomething', 'Valid' ] + // * 'WrongMethod_Invalid_BranchOutOfTry' -> [ 'WrongMethod', 'Invalid', 'BranchOutOfTry' ] + // * 'MoreWrongMethod_Invalid_TypeAccess.InitOnly' -> [ 'MoreWrongMethod', 'Invalid', 'TypeAccess', 'InitOnly' ] + // * 'special.set_MyProperty.set_MyProperty_Invalid_InitOnly' -> [ 'special.set_MyProperty.set_MyProperty', 'Invalid', 'InitOnly' ] + } + + if (validationErrors != null && (mparams == null || mparams.Length != 3 || mparams[1] != "Invalid")) + throw new ArgumentException($"Test '{methodName}' has a '{ValidationSuffix}' suffix, but only an Invalid test can have one."); + + return mparams; + } + private static List GetTestMethodsFromDll(Func methodSelector) { var retVal = new List(); @@ -190,24 +286,11 @@ private static List GetTestMethodsFromDll(Func validationErrors); + if (mparams == null) continue; - var substring = methodName.Substring(index + 1); - var split = substring.Split('_'); - string[] mparams = new string[split.Length + 1]; - split.CopyTo(mparams, 1); - mparams[0] = methodName.Substring(0, index); - // examples of methodName to mparams transformation: - // * `get_Property` -> [ 'get_Property' ] - // * `CheckSomething_Valid` -> [ 'CheckSomething', 'Valid' ] - // * 'WrongMethod_Invalid_BranchOutOfTry' -> [ 'WrongMethod', 'Invalid', 'BranchOutOfTry' ] - // * 'MoreWrongMethod_Invalid_TypeAccess.InitOnly' -> [ 'MoreWrongMethod', 'Invalid', 'TypeAccess', 'InitOnly' ] - // * 'special.set_MyProperty.set_MyProperty_Invalid_InitOnly' -> [ 'special.set_MyProperty.set_MyProperty', 'Invalid', 'InitOnly' ] - var specialMethodHandle = HandleSpecialTests(mparams, method); var newItem = methodSelector(mparams, specialMethodHandle); @@ -217,6 +300,9 @@ private static List GetTestMethodsFromDll(Func errors) } } + /// + /// Describes a test case with a method and the VerifierErrors expected in validation-only mode + /// + public class ValidationOnlyTestCase : InvalidILTestCase + { + public override string ToString() + { + return base.ToString() + " [validation-only]"; + } + } + public class ValidTypeTestCase : TestCase { } public class InvalidTypeTestCase : TestCase diff --git a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs index a9f2abc01856a1..ca8a0fcb5edd8d 100644 --- a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs +++ b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs @@ -216,6 +216,9 @@ public ILImporter(MethodDesc method, MethodIL methodIL) public bool SanityChecks { set; private get; } + // Report the rules that valid IL must follow, and not most rules only for verifiable IL (ECMA-335 III.1.8). + public bool ValidationOnly { set; private get; } + public void Verify() { // Check code size before any other processing @@ -591,6 +594,46 @@ bool Check(bool cond, VerifierError error, StackValue found, StackValue expected return cond; } + // Reports a rule that only verifiable IL must follow; validation-only mode reports nothing. + void UnverifiableError(VerifierError error) + { + if (!ValidationOnly) + VerificationError(error); + } + + void UnverifiableError(VerifierError error, object found) + { + if (!ValidationOnly) + VerificationError(error, found); + } + + void UnverifiableError(VerifierError error, object found, object expected) + { + if (!ValidationOnly) + VerificationError(error, found, expected); + } + + // Like Check, for a rule that only verifiable IL must follow; validation-only mode reports nothing. + bool CheckVerifiable(bool cond, VerifierError error) + { + if (!cond && !ValidationOnly) + VerificationError(error); + return cond; + } + + bool CheckVerifiable(bool cond, VerifierError error, StackValue found) + { + if (!cond && !ValidationOnly) + VerificationError(error, found); + return cond; + } + + // An unmanaged pointer (native int) is valid where verification requires a managed pointer (III.1.1.5.1). + bool AcceptsUnmanagedPointer(StackValue value) + { + return ValidationOnly && value.Kind == StackValueKind.NativeInt; + } + void CheckIsNumeric(StackValue value) { if (!Check(StackValueKind.Int32 <= value.Kind && value.Kind <= StackValueKind.Float, @@ -635,12 +678,44 @@ void CheckIsArray(StackValue value) } } - void CheckIsAssignable(StackValue src, StackValue dst, VerifierError error = VerifierError.StackUnexpected) + // Which type mismatches of a value and its destination validation-only mode reports. + enum ValidAssignment { - if (!IsAssignable(src, dst)) + // Only a stack type mismatch (I.12.3.2.1); a native int is also valid for a managed pointer (III.1.6). + StackType, + // As StackType, and an argument can be a managed pointer where a native int is declared (Table III.8). + Argument, + // Every mismatch verification reports. Used by SanityChecks, whose rules are not verification rules. + Verifiable, + } + + void CheckIsAssignable(StackValue src, StackValue dst, VerifierError error = VerifierError.StackUnexpected, + ValidAssignment valid = ValidAssignment.StackType) + { + if (!IsAssignable(src, dst) && !(ValidationOnly && IsValidAssignment(src, dst, valid))) VerificationError(error, src, dst); } + static bool IsValidAssignment(StackValue src, StackValue dst, ValidAssignment valid) + { + if (valid == ValidAssignment.Verifiable) + return false; + + if (src.Kind == StackValueKind.NativeInt && dst.Kind == StackValueKind.ByRef) + return true; + + // A controlled-mutability managed pointer cannot be used as an ordinary one (III.1.8.1.2.2). + if (valid == ValidAssignment.Argument && src.Kind == StackValueKind.ByRef && !src.IsReadOnly && + dst.Kind == StackValueKind.NativeInt) + return true; + + if (src.Kind != dst.Kind) + return false; + + return src.Kind == StackValueKind.ObjRef || + (src.Kind == StackValueKind.ByRef && (!src.IsReadOnly || dst.IsReadOnly)); + } + private void CheckIsValidLeaveTarget(BasicBlock src, BasicBlock target) { if (!_validTargetOffsets[target.StartOffset]) @@ -1053,11 +1128,13 @@ static string TypeToStringForIsAssignable(TypeDesc type) return StackValue.CreateFromType(type).ToString(); } + // The TypeDesc checks below compare pointed-to and element types, which are not stack types (I.12.3.2.1). + void CheckIsAssignable(TypeDesc src, TypeDesc dst) { if (!IsAssignable(src, dst)) { - VerificationError(VerifierError.StackUnexpected, TypeToStringForIsAssignable(src), TypeToStringForIsAssignable(dst)); + UnverifiableError(VerifierError.StackUnexpected, TypeToStringForIsAssignable(src), TypeToStringForIsAssignable(dst)); } } @@ -1066,7 +1143,7 @@ void CheckIsArrayElementCompatibleWith(TypeDesc src, TypeDesc dst) if (!IsAssignable(src, dst, true)) { // TODO: Better error message - VerificationError(VerifierError.StackUnexpected, TypeToStringForIsAssignable(src)); + UnverifiableError(VerifierError.StackUnexpected, TypeToStringForIsAssignable(src)); } } @@ -1076,17 +1153,19 @@ void CheckIsPointerElementCompatibleWith(TypeDesc src, TypeDesc dst) { // TODO: Better error message // VerificationError(VerifierError.StackUnexpected, TypeToStringForIsAssignable(src), TypeToStringForIsAssignable(dst)); - VerificationError(VerifierError.StackUnexpectedArrayType, TypeToStringForIsAssignable(src)); + UnverifiableError(VerifierError.StackUnexpectedArrayType, TypeToStringForIsAssignable(src)); } } - void CheckIsObjRef(TypeDesc type) + bool CheckIsObjRef(TypeDesc type) { if (!IsAssignable(type, GetWellKnownType(WellKnownType.Object), false)) { // TODO: Better error message - VerificationError(VerifierError.StackUnexpected, TypeToStringForIsAssignable(type)); + UnverifiableError(VerifierError.StackUnexpected, TypeToStringForIsAssignable(type)); + return false; } + return true; } void CheckIsObjRef(StackValue value) @@ -1098,7 +1177,7 @@ void CheckIsObjRef(StackValue value) private void CheckIsNotPointer(TypeDesc type) { if (type.IsPointer) - VerificationError(VerifierError.UnmanagedPointer); + UnverifiableError(VerifierError.UnmanagedPointer); } void CheckIsComparable(StackValue a, StackValue b, ILOpcode op) @@ -1236,10 +1315,11 @@ bool IsDelegateAssignable(MethodDesc targetMethod, TypeDesc delegateType, StackV // For a closed delegate it is the target pushed on the stack, so compare against the stack value itself: // it keeps track of boxed value types, which are object references and assignable to anything they can be cast to - // (e.g. a boxed int32 target for ToString()). + // (e.g. a boxed int32 target for ToString()). Validation-only mode compares only its stack type. bool firstArgAssignable = isOpenDelegate ? IsAssignable(invokeSignature[0], firstTargetArg) - : IsAssignable(firstArg, StackValue.CreateObjRef(firstTargetArg)); + : IsAssignable(firstArg, StackValue.CreateObjRef(firstTargetArg)) || + (ValidationOnly && IsValidAssignment(firstArg, StackValue.CreateObjRef(firstTargetArg), ValidAssignment.StackType)); if (!firstArgAssignable) return false; @@ -1268,7 +1348,7 @@ ILOpcode GetOpcodeAt(int instructionOffset) void Unverifiable() { - VerificationError(VerifierError.Unverifiable); + UnverifiableError(VerifierError.Unverifiable); } TypeDesc GetWellKnownType(WellKnownType wellKnownType) @@ -1400,7 +1480,7 @@ void StartImportingBasicBlock(BasicBlock basicBlock) if (SanityChecks && basicBlock.EntryStack[0] != StackValue.CreateObjRef(GetWellKnownType(WellKnownType.Object))) { CheckIsAssignable(basicBlock.EntryStack[0], StackValue.CreateObjRef(GetWellKnownType(WellKnownType.Exception)), - VerifierError.ThrowOrCatchOnlyExceptionType); + VerifierError.ThrowOrCatchOnlyExceptionType, ValidAssignment.Verifiable); } } } @@ -1427,7 +1507,8 @@ void StartImportingBasicBlock(BasicBlock basicBlock) // ECMA III 1.7.5 Backward Branch Constraints // if stack is not empty at beginning of this block, // there must exist a predecessor block with lower IL offset. - Check(basicBlock.HasPredecessorWithLowerOffset, VerifierError.BackwardBranch); + // The ECMA-335 augments delete this rule, so it is no longer a validity rule. + CheckVerifiable(basicBlock.HasPredecessorWithLowerOffset, VerifierError.BackwardBranch); } // Copy stack state @@ -1482,7 +1563,7 @@ void ImportLoadVar(int index, bool argument) var varType = GetVarType(index, argument); if (!argument) - Check(_initLocals, VerifierError.InitLocals); + CheckVerifiable(_initLocals, VerifierError.InitLocals); CheckIsNotPointer(varType); @@ -1513,7 +1594,7 @@ void ImportAddressOfVar(int index, bool argument) var varType = GetVarType(index, argument); if (!argument) - Check(_initLocals, VerifierError.InitLocals); + CheckVerifiable(_initLocals, VerifierError.InitLocals); Check(!varType.IsByRef, VerifierError.ByrefOfByref); @@ -1590,8 +1671,9 @@ void ImportCall(ILOpcode opcode, int token) constrained = _constrained; // The constrained type must implement the interface declaring the static virtual method + // (a Verifiability rule of the augmented III.2.1). if (!constrained.CanCastTo(method.OwningType)) - VerificationError(VerifierError.ConstrainedTypeNoInterfaceImpl, constrained, method.OwningType); + UnverifiableError(VerifierError.ConstrainedTypeNoInterfaceImpl, constrained, method.OwningType); } } @@ -1630,18 +1712,26 @@ void ImportCall(ILOpcode opcode, int token) Check(declaredFtn.Kind == StackValueKind.NativeInt, VerifierError.DelegateCtorSigI, declaredFtn); + // This mode does not compare the target's class, so it checks that the parameter is object (II.14.6). + if (ValidationOnly) + Check(sig[0].IsObject, VerifierError.DelegateCtorSigO, declaredObj); + var actualFtn = Pop(); var actualObj = Pop(); - Check(actualFtn.IsMethod, VerifierError.StackMethod); + // Only ldftn and dup; ldvirtftn are verifiable (III.4.21), but the pointer must still be a native int. + CheckVerifiable(actualFtn.IsMethod, VerifierError.StackMethod); + if (ValidationOnly) + CheckIsAssignable(actualFtn, StackValue.CreatePrimitive(StackValueKind.NativeInt), valid: ValidAssignment.Argument); CheckIsAssignable(actualObj, declaredObj); Check(actualObj.Kind == StackValueKind.ObjRef, VerifierError.DelegateCtorSigO, actualObj); - CheckDelegateCreation(actualFtn, actualObj); + if (!ValidationOnly) + CheckDelegateCreation(actualFtn, actualObj); // A function pointer that did not come from ldftn/ldvirtftn carries no method; - // StackMethod has already been reported for it above. + // In verification mode, StackMethod has already been reported for it above. if (actualFtn.IsMethod && !IsDelegateAssignable(actualFtn.Method, methodType, actualObj)) VerificationError(VerifierError.DelegateCtor); } @@ -1652,11 +1742,11 @@ void ImportCall(ILOpcode opcode, int token) var actual = Pop(allowUninitThis: true); var declared = StackValue.CreateFromType(sig[i]); - CheckIsAssignable(actual, declared); + CheckIsAssignable(actual, declared, valid: ValidAssignment.Argument); // check that the argument is not a byref for tailcalls if (tailCall) - Check(!IsByRefLike(declared), VerifierError.TailByRef, declared); + CheckTailCallArgument(actual, declared); } } @@ -1708,11 +1798,13 @@ void ImportCall(ILOpcode opcode, int token) if (constrained != null) { - Check(actualThis.Kind == StackValueKind.ByRef, VerifierError.ConstrainedCallWithNonByRefThis); + // An unmanaged pointer is valid as 'this' (III.1.1.5.1), and the type it points to is not a stack type. + if (!AcceptsUnmanagedPointer(actualThis)) + Check(actualThis.Kind == StackValueKind.ByRef, VerifierError.ConstrainedCallWithNonByRefThis); // We just dereference this and test for equality //todo: improve error - "this type mismatch with constrained type operand" - Check(actualThis.Type == constrained, VerifierError.StackUnexpected); + CheckVerifiable(actualThis.Type == constrained, VerifierError.StackUnexpected); // Now pretend the this type is the boxed constrained type, for the sake of subsequent checks actualThis = StackValue.CreateObjRef(constrained); @@ -1723,6 +1815,7 @@ void ImportCall(ILOpcode opcode, int token) { declaredThis.SetIsReadOnly(); } + // A native int is also valid as the 'this' of a value type method (III.1.6, III.1.1.5.1). CheckIsAssignable(actualThis, declaredThis); if (opcode == ILOpcode.call) @@ -1749,7 +1842,7 @@ void ImportCall(ILOpcode opcode, int token) { var methodTypeDef = methodType.GetTypeDefinition() as MetadataType; // Method is always considered final if owning type is sealed if (methodTypeDef == null || !methodTypeDef.IsSealed) - Check(actualThis.IsThisPtr && !_modifiesThisPtr, VerifierError.ThisMismatch); + CheckVerifiable(actualThis.IsThisPtr && !_modifiesThisPtr, VerifierError.ThisMismatch); } } @@ -1808,6 +1901,14 @@ void ImportCall(ILOpcode opcode, int token) } } + // ILVerify cannot tell which frame a managed pointer points into (III.2.4), so a tail call may pass none. + // Validation-only mode also checks the value, as ValidAssignment.Argument accepts a & for a native int. + void CheckTailCallArgument(StackValue actual, StackValue declared) + { + if (Check(!IsByRefLike(declared), VerifierError.TailByRef, declared) && ValidationOnly) + Check(!IsByRefLike(actual), VerifierError.TailByRef, actual); + } + void CheckTailCallReturn(TypeDesc returnType) { TypeDesc callerReturnType = _methodSignature.ReturnType; @@ -1825,7 +1926,8 @@ void CheckTailCallReturn(TypeDesc returnType) { var retStackType = StackValue.CreateFromType(returnType); var callerRetStackType = StackValue.CreateFromType(callerReturnType); - Check(IsAssignable(retStackType, callerRetStackType), VerifierError.TailRetType, retStackType, callerRetStackType); + // As for ret, validation-only mode compares only the stack types. + CheckIsAssignable(retStackType, callerRetStackType, VerifierError.TailRetType); } // for tailcall, stack must be empty @@ -1862,10 +1964,10 @@ void ImportCalli(int token) for (int i = 0; i < sig.Length; i++) { var declared = StackValue.CreateFromType(sig[i]); - CheckIsAssignable(args[firstParameter + i], declared); + CheckIsAssignable(args[firstParameter + i], declared, valid: ValidAssignment.Argument); if (tailCall) - Check(!IsByRefLike(declared), VerifierError.TailByRef, declared); + CheckTailCallArgument(args[firstParameter + i], declared); } if (implicitThis && tailCall) @@ -1908,7 +2010,7 @@ void ImportLdFtn(int token, ILOpcode opCode) { ClearPendingPrefix(Prefix.Constrained); if (!_constrained.CanCastTo(method.OwningType)) - VerificationError(VerifierError.ConstrainedTypeNoInterfaceImpl, _constrained, method.OwningType); + UnverifiableError(VerifierError.ConstrainedTypeNoInterfaceImpl, _constrained, method.OwningType); } else { @@ -2012,7 +2114,8 @@ void ImportReturn() var actualReturnType = Pop(); CheckIsAssignable(actualReturnType, StackValue.CreateFromType(expectedReturnType)); - Check((!expectedReturnType.IsByRef && !expectedReturnType.IsByRefLike) || actualReturnType.IsPermanentHome, VerifierError.ReturnPtrToStack); + // Byref returns are checked for verification only (III.1.8.1.2.1). + CheckVerifiable((!expectedReturnType.IsByRef && !expectedReturnType.IsByRefLike) || actualReturnType.IsPermanentHome, VerifierError.ReturnPtrToStack); } } } @@ -2151,6 +2254,18 @@ void PropagateControlFlow(BasicBlock next, bool isFallthrough) for (int i = 0; i < entryStack.Length; i++) { // TODO: Do we need to allow conversions? + if (ValidationOnly && IsNativeIntAndByRef(entryStack[i], _stack[i])) + { + // A native int and a managed pointer merge to a native int (III.1.6). + if (entryStack[i].Kind != StackValueKind.NativeInt) + { + entryStack[i] = StackValue.CreatePrimitive(StackValueKind.NativeInt); + if (next.ErrorCount == 0 && next.State != BasicBlock.ImportState.IsPending) + next.State = BasicBlock.ImportState.Unmarked; // Make sure block is reverified + } + continue; + } + FatalCheck(entryStack[i].Kind == _stack[i].Kind, VerifierError.PathStackUnexpected, entryStack[i], _stack[i]); if (entryStack[i].Type != _stack[i].Type) @@ -2159,7 +2274,22 @@ void PropagateControlFlow(BasicBlock next, bool isFallthrough) { StackValue mergedValue; if (!TryMergeStackValues(entryStack[i], _stack[i], out mergedValue)) + { + // Merging the types managed pointers point to is verification only (III.1.8.1.3). + // Keep the entry stack, controlled-mutability if either value is (III.1.8.1.2.2). + if (ValidationOnly && entryStack[i].Kind == StackValueKind.ByRef) + { + if (_stack[i].IsReadOnly && !entryStack[i].IsReadOnly) + { + entryStack[i].SetIsReadOnly(); + if (next.ErrorCount == 0 && next.State != BasicBlock.ImportState.IsPending) + next.State = BasicBlock.ImportState.Unmarked; // Make sure block is reverified + } + continue; + } + FatalCheck(false, VerifierError.PathStackUnexpected, entryStack[i], _stack[i]); + } // If merge actually changed entry stack if (mergedValue != entryStack[i]) @@ -2186,11 +2316,24 @@ void PropagateControlFlow(BasicBlock next, bool isFallthrough) MarkBasicBlock(next); } + // A controlled-mutability managed pointer cannot become a native int (III.1.8.1.2.2). + static bool IsNativeIntAndByRef(StackValue a, StackValue b) + { + return (a.Kind == StackValueKind.NativeInt && b.Kind == StackValueKind.ByRef && !b.IsReadOnly) || + (a.Kind == StackValueKind.ByRef && !a.IsReadOnly && b.Kind == StackValueKind.NativeInt); + } + void ImportBinaryOperation(ILOpcode opcode) { var op1 = Pop(); var op2 = Pop(); + if (ValidationOnly && IsValidPointerArithmetic(opcode, op2, op1, out StackValue pointerResult)) + { + Push(pointerResult); + return; + } + switch (opcode) { case ILOpcode.add: @@ -2235,6 +2378,45 @@ void ImportBinaryOperation(ILOpcode opcode) Push(result); } + // Pointer arithmetic that is valid but not verifiable (Tables III.2, III.7): & +/- int, int + & and & - &. + // value1 is the operand pushed first. + static bool IsValidPointerArithmetic(ILOpcode opcode, StackValue value1, StackValue value2, out StackValue result) + { + result = default; + + bool add = opcode is ILOpcode.add or ILOpcode.add_ovf_un; + bool sub = opcode is ILOpcode.sub or ILOpcode.sub_ovf_un; + if (!add && !sub) + return false; + + // Arithmetic is not one of the uses of a controlled-mutability managed pointer (III.1.8.1.2.2). + if (value1.IsReadOnly || value2.IsReadOnly) + return false; + + bool integer1 = value1.Kind is StackValueKind.Int32 or StackValueKind.NativeInt; + bool integer2 = value2.Kind is StackValueKind.Int32 or StackValueKind.NativeInt; + + if (value1.Kind == StackValueKind.ByRef && integer2) + { + result = StackValue.CreateByRef(value1.Type, false, value1.IsPermanentHome); + return true; + } + + if (add && integer1 && value2.Kind == StackValueKind.ByRef) + { + result = StackValue.CreateByRef(value2.Type, false, value2.IsPermanentHome); + return true; + } + + if (sub && value1.Kind == StackValueKind.ByRef && value2.Kind == StackValueKind.ByRef) + { + result = StackValue.CreatePrimitive(StackValueKind.NativeInt); + return true; + } + + return false; + } + void ImportShiftOperation(ILOpcode opcode) { var shiftBy = Pop(); @@ -2260,6 +2442,15 @@ void ImportConvert(WellKnownType wellKnownType, bool checkOverflow, bool unsigne { var value = Pop(); + // Converting a & or an object reference to int64 or native int is valid but not verifiable (Table III.8). + if (ValidationOnly && !value.IsReadOnly && + value.Kind is StackValueKind.ByRef or StackValueKind.ObjRef && + wellKnownType is WellKnownType.Int64 or WellKnownType.UInt64 or WellKnownType.IntPtr or WellKnownType.UIntPtr) + { + Push(StackValue.CreateFromType(GetWellKnownType(wellKnownType))); + return; + } + CheckIsNumeric(value); Push(StackValue.CreateFromType(GetWellKnownType(wellKnownType))); @@ -2293,7 +2484,9 @@ void ImportLoadField(int token, bool isStatic) var declaredThis = owningType.IsValueType ? StackValue.CreateByRef(owningType, readOnly : true) : StackValue.CreateObjRef(owningType); - CheckIsAssignable(actualThis, declaredThis); + // obj can be an unmanaged pointer, which is valid but not verifiable (III.4.10). + if (!AcceptsUnmanagedPointer(actualThis)) + CheckIsAssignable(actualThis, declaredThis); instance = actualThis.Type; } @@ -2307,6 +2500,7 @@ void ImportAddressOfField(int token, bool isStatic) { var field = ResolveFieldToken(token); bool isPermanentHome = false; + bool throughUnmanagedPointer = false; TypeDesc instance; if (isStatic) @@ -2332,7 +2526,10 @@ void ImportAddressOfField(int token, bool isStatic) var declaredThis = owningType.IsValueType ? StackValue.CreateByRef(owningType) : StackValue.CreateObjRef(owningType); - CheckIsAssignable(actualThis, declaredThis); + // obj can be an unmanaged pointer, which is valid but not verifiable; so is the result (III.4.11). + throughUnmanagedPointer = AcceptsUnmanagedPointer(actualThis); + if (!throughUnmanagedPointer) + CheckIsAssignable(actualThis, declaredThis); isPermanentHome = actualThis.Kind == StackValueKind.ObjRef || actualThis.IsPermanentHome; instance = actualThis.Type; @@ -2344,7 +2541,8 @@ void ImportAddressOfField(int token, bool isStatic) Check(_method.OwningType.CanAccess(field, instance), VerifierError.FieldAccess); - Push(StackValue.CreateByRef(field.FieldType, false, isPermanentHome)); + Push(throughUnmanagedPointer ? StackValue.CreatePrimitive(StackValueKind.NativeInt) : + StackValue.CreateByRef(field.FieldType, false, isPermanentHome)); } void ImportStoreField(int token, bool isStatic) @@ -2379,7 +2577,9 @@ void ImportStoreField(int token, bool isStatic) var declaredThis = owningType.IsValueType ? StackValue.CreateByRef(owningType) : StackValue.CreateObjRef(owningType); - CheckIsAssignable(actualThis, declaredThis); + // obj can be an unmanaged pointer, which is valid but not verifiable (III.4.28). + if (!AcceptsUnmanagedPointer(actualThis)) + CheckIsAssignable(actualThis, declaredThis); instance = actualThis.Type; @@ -2407,11 +2607,24 @@ void ImportLoadIndirect(TypeDesc type) ClearPendingPrefix(Prefix.Volatile); var address = Pop(); + + // addr can be an unmanaged pointer, which carries no type (III.4.13); ldind.ref then pushes the null type. + if (AcceptsUnmanagedPointer(address)) + { + Push(type == null ? StackValue.CreateObjRef(null) : StackValue.CreateFromType(type)); + return; + } + CheckIsByRef(address); if (type == null) { - CheckIsObjRef(address.Type); + // Validation-only mode does not check the type pointed to, so the reference's class is unknown. + if (!CheckIsObjRef(address.Type) && ValidationOnly) + { + Push(StackValue.CreateObjRef(null)); + return; + } type = address.Type; } else @@ -2436,15 +2649,35 @@ void ImportStoreIndirect(TypeDesc type) Check(!address.IsReadOnly, VerifierError.ReadOnlyIllegalWrite); + // addr can be an unmanaged pointer, which carries no type (III.4.29), so only the value is checked. + if (AcceptsUnmanagedPointer(address)) + { + if (type == null) + CheckIsObjRef(value); + else + CheckIsAssignable(value, StackValue.CreateFromType(type)); + return; + } + CheckIsByRef(address); if (type == null) + { + // Validation-only mode does not check the type pointed to, so it checks only the value. + if (ValidationOnly) + { + CheckIsObjRef(value); + return; + } type = address.Type; + } var typeVal = StackValue.CreateFromType(type); var addressVal = StackValue.CreateFromType(address.Type); - CheckIsAssignable(typeVal, addressVal); + // The type a pointer points to is not a stack type (I.12.3.2.1). + if (!ValidationOnly) + CheckIsAssignable(typeVal, addressVal); CheckIsAssignable(value, typeVal); } @@ -2457,7 +2690,7 @@ void ImportThrow() if (SanityChecks && value != StackValue.CreateObjRef(GetWellKnownType(WellKnownType.Object))) { CheckIsAssignable(value, StackValue.CreateFromType(GetWellKnownType(WellKnownType.Exception)), - VerifierError.ThrowOrCatchOnlyExceptionType); + VerifierError.ThrowOrCatchOnlyExceptionType, ValidAssignment.Verifiable); } EmptyTheStack(); @@ -2477,9 +2710,15 @@ void ImportInitObj(int token) var value = Pop(); - Check(value.Kind == StackValueKind.ByRef, VerifierError.StackByRef, value); + // dest can be an unmanaged pointer, which is valid but not verifiable (III.4.5). + if (AcceptsUnmanagedPointer(value)) + return; - CheckIsAssignable(value, StackValue.CreateByRef(type)); + bool isByRef = Check(value.Kind == StackValueKind.ByRef, VerifierError.StackByRef, value); + + // Only verification checks the type pointed to (III.4.5); a controlled-mutability & is still rejected. + if (isByRef || !ValidationOnly) + CheckIsAssignable(value, StackValue.CreateByRef(type)); } void ImportBox(int token) @@ -2599,7 +2838,13 @@ void ImportLoadElement(TypeDesc elementType) else { elementType = actualElementType; - CheckIsObjRef(elementType); + + // As for ldind.ref, validation-only mode leaves the class unknown for a non-reference element type. + if (!CheckIsObjRef(elementType) && ValidationOnly) + { + Push(StackValue.CreateObjRef(null)); + return; + } } } @@ -2632,7 +2877,13 @@ void ImportStoreElement(TypeDesc elementType) else { elementType = actualElementType; - CheckIsObjRef(elementType); + + // As for stind.ref, validation-only mode checks only the value for a non-reference element type. + if (!CheckIsObjRef(elementType) && ValidationOnly) + { + CheckIsObjRef(value); + return; + } } } @@ -2701,8 +2952,11 @@ void ImportCpOpj(int token) var src = Pop(); var dst = Pop(); - Check(src.Kind == StackValueKind.ByRef, VerifierError.StackByRef, src); - Check(dst.Kind == StackValueKind.ByRef, VerifierError.StackByRef, dst); + // Either address can be an unmanaged pointer, which is valid but not verifiable (III.4.4). + if (!AcceptsUnmanagedPointer(src)) + Check(src.Kind == StackValueKind.ByRef, VerifierError.StackByRef, src); + if (!AcceptsUnmanagedPointer(dst)) + Check(dst.Kind == StackValueKind.ByRef, VerifierError.StackByRef, dst); // TODO !!! // CheckIsAssignable(src.Type, type); @@ -2787,7 +3041,8 @@ void ImportEndFilter() var result = Pop(allowUninitThis: true); Check(result.Kind == StackValueKind.Int32, VerifierError.StackUnexpected, result); - Check(_stackTop == 0, VerifierError.EndfilterStack); + // Only the Verifiability paragraph of III.3.34 requires the value to be the only item. + CheckVerifiable(_stackTop == 0, VerifierError.EndfilterStack); } void ImportCpBlk() @@ -2994,13 +3249,14 @@ void ImportMkRefAny(int token) // ECMA-335 III.4.19: verification requires a managed pointer to an instance of the class. // The class may be byref-like: the ECMA-335 augments allow a managed pointer to a byref-like type. - if (Check(ptr.Kind == StackValueKind.ByRef, VerifierError.StackByRef, ptr)) + // An unmanaged pointer is valid, and does not carry the type it points to. + if (!AcceptsUnmanagedPointer(ptr) && Check(ptr.Kind == StackValueKind.ByRef, VerifierError.StackByRef, ptr)) { // III.1.8.1.2.2: a controlled-mutability managed pointer cannot be used with mkrefany. Check(!ptr.IsReadOnly, VerifierError.ReadOnlyInMkrefany); if (ptr.Type != type) - VerificationError(VerifierError.StackUnexpected, ptr, StackValue.CreateByRef(type)); + UnverifiableError(VerifierError.StackUnexpected, ptr, StackValue.CreateByRef(type)); } Check(_method.OwningType.CanAccess(type), VerifierError.TypeAccess); diff --git a/src/coreclr/tools/ILVerification/Verifier.cs b/src/coreclr/tools/ILVerification/Verifier.cs index 518071277c164e..c4173570d5269c 100644 --- a/src/coreclr/tools/ILVerification/Verifier.cs +++ b/src/coreclr/tools/ILVerification/Verifier.cs @@ -482,7 +482,8 @@ private IEnumerable VerifyMethod( { var importer = new ILImporter(method, methodIL) { - SanityChecks = _verifierOptions.SanityChecks + SanityChecks = _verifierOptions.SanityChecks, + ValidationOnly = _verifierOptions.ValidationOnly }; importer.ReportVerificationError = (args, code) => @@ -709,5 +710,16 @@ public class VerifierOptions /// Respect IgnoresAccessChecksToAttribute. Off by default, since code that relies on it is not verifiable. /// public bool AllowIgnoresAccessChecksTo { get; set; } + + /// + /// Gets or sets a value that indicates whether the verifier checks only that the IL is valid, and skips most + /// rules that only verifiable IL must follow (ECMA-335 Partition III, 1.8). + /// + /// + /// For example, unmanaged pointers, localloc and calli are not reported, and values on the stack + /// are mostly compared only by stack type. IL whose validity the verifier cannot decide is still reported. The + /// default is . + /// + public bool ValidationOnly { get; set; } } } diff --git a/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs index d5b5554f94e660..d2971224f42f39 100644 --- a/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs +++ b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs @@ -22,6 +22,8 @@ internal sealed class ILVerifyRootCommand : RootCommand new("--system-module", "-s") { Description = "System module name (default: mscorlib)" }; public Option SanityChecks { get; } = new("--sanity-checks", "-c") { Description = "Check for valid constructs that are likely mistakes" }; + public Option ValidationOnly { get; } = + new("--validation-only") { Description = "Report invalid IL; do not report most IL that is valid but unverifiable" }; public Option Include { get; } = new("--include", "-i") { Description = "Use only methods/types/namespaces, which match the given regular expression(s)" }; public Option IncludeFile { get; } = @@ -56,6 +58,7 @@ public ILVerifyRootCommand() Options.Add(Reference); Options.Add(SystemModule); Options.Add(SanityChecks); + Options.Add(ValidationOnly); Options.Add(Include); Options.Add(IncludeFile); Options.Add(Exclude); diff --git a/src/coreclr/tools/ILVerify/Program.cs b/src/coreclr/tools/ILVerify/Program.cs index 12843b4b168a8c..d4aeaddc02eee4 100644 --- a/src/coreclr/tools/ILVerify/Program.cs +++ b/src/coreclr/tools/ILVerify/Program.cs @@ -115,7 +115,8 @@ public int Run() { IncludeMetadataTokensInErrorMessages = Get(_command.Tokens), SanityChecks = Get(_command.SanityChecks), - AllowIgnoresAccessChecksTo = Get(_command.AllowIgnoresAccessChecks) + AllowIgnoresAccessChecksTo = Get(_command.AllowIgnoresAccessChecks), + ValidationOnly = Get(_command.ValidationOnly) }); _verifier.SetSystemModuleName(new AssemblyNameInfo(Get(_command.SystemModule) ?? "mscorlib")); @@ -262,17 +263,17 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) else if (metadataReferencesOnly) WriteLine("All metadata references in " + path + " resolved."); else - WriteLine("All types and methods in " + path + " verified."); + WriteLine("All types and methods in " + path + (Get(_command.ValidationOnly) ? " validated." : " verified.")); if (Get(_command.Statistics)) { if (!metadataReferencesOnly) { WriteLine($"Types found: {typeCounter}"); - WriteLine($"Types verified: {verifiedTypeCounter}"); + WriteLine($"Types {(Get(_command.ValidationOnly) ? "validated" : "verified")}: {verifiedTypeCounter}"); WriteLine($"Methods found: {methodCounter}"); - WriteLine($"Methods verified: {verifiedMethodCounter}"); + WriteLine($"Methods {(Get(_command.ValidationOnly) ? "validated" : "verified")}: {verifiedMethodCounter}"); } } diff --git a/src/coreclr/tools/ILVerify/README.md b/src/coreclr/tools/ILVerify/README.md index 8b7c2c5df219ee..ff8ef228e77038 100644 --- a/src/coreclr/tools/ILVerify/README.md +++ b/src/coreclr/tools/ILVerify/README.md @@ -23,6 +23,8 @@ All Classes and Methods in C:\test\hello.dll Verified. Note that `ILVerify` requires all dependencies of assembly that is being verified to be explicitly specified on the command line. +With `--validation-only`, ILVerify reports IL that is not valid and does not report most IL that is valid but unverifiable, such as unmanaged pointers or `localloc`. This is useful to check unsafe IL. Where ILVerify cannot tell whether IL is valid, it still reports it: for example, any managed pointer passed in a tail call. + ## Other tools Historically on .NET Framework IL generators used PEVerify to make sure that they generated correct IL. PEVerify has some major limitations (e.g. it is tied to the .NET Framework, it cannot verify mscorlib.dll, etc.), which initiated this project. @@ -75,6 +77,17 @@ The method name must contain 2 '`_`' characters. E.g.: ```SimpleAdd_Invalid_ExpectedNumericType``` +### Validation-only mode: + +Every Valid and Invalid method is also verified with `VerifierOptions.ValidationOnly` set, which does not report most IL that is valid but unverifiable. A Valid method is expected to have no errors in this mode too. An Invalid method is expected to have the same errors, unless its name ends with a suffix that gives the errors expected in this mode: +``` +[FriendlyName]_Invalid_[ExpectedVerifierError1]....[ExpectedVerifierErrorN]_Validation_[Valid | ExpectedVerifierError1....ExpectedVerifierErrorN] +``` + + E.g.: ```LocAlloc_Invalid_Unverifiable_Validation_Valid``` + +A malformed suffix fails the tests: a second suffix, a suffix on a method that is not Invalid, or an error name that is not a `VerifierError`. + ### Methods with special names: In order to test methods with special names (e.g. '.ctor'), the specialname method is defined as usual and a separate empty method is added to the type: From 1d9465658b60e1d8691becc79c770b230e5e97c4 Mon Sep 17 00:00:00 2001 From: Peter Shrosbree Date: Tue, 20 Oct 2026 11:48:54 -0700 Subject: [PATCH 2/2] ILVerify: continue past verifiability-only errors 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. --- .../ILTests/BranchingTests.il | 105 ++++++++++++++++++ .../tools/ILVerification/ILImporter.Verify.cs | 35 +++++- 2 files changed, 134 insertions(+), 6 deletions(-) diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/BranchingTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/BranchingTests.il index f2f8f164bb5325..5444e8a006047a 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/BranchingTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/BranchingTests.il @@ -1078,4 +1078,109 @@ pop ret } + + // A block whose only errors make the IL unverifiable still passes its stack on, so ret in the next block is checked. + + .method static public hidebysig int32 Propagate.AfterLocalloc_Invalid_Unverifiable.ReturnMissing_Validation_ReturnMissing() cil managed + { + ldc.i4.8 + localloc + pop + br.s Next + + Next: + ret + } + + .method static public hidebysig int32 Propagate.AfterCalli_Invalid_Unverifiable.ReturnMissing_Validation_ReturnMissing(method void *() f) cil managed + { + ldarg.0 + calli void() + br.s Next + + Next: + ret + } + + .method static public hidebysig int32 Propagate.AfterInitLocals_Invalid_InitLocals.ReturnMissing_Validation_ReturnMissing() cil managed + { + .locals (int32 V_0) + ldloc.0 + pop + br.s Next + + Next: + ret + } + + .method static public hidebysig int32 Propagate.AfterElementAddressType_Invalid_StackUnexpectedArrayType.ReturnMissing_Validation_ReturnMissing(int32[] a) cil managed + { + ldarg.0 + ldc.i4.0 + ldelema [System.Runtime]System.Int64 + pop + br.s Next + + Next: + ret + } + + .method static public hidebysig int32 Propagate.AfterPointeeType_Invalid_StackUnexpected.ReturnMissing_Validation_ReturnMissing(int64& p) cil managed + { + ldarg.0 + ldind.i4 + pop + br.s Next + + Next: + ret + } + + // ldind.ref and ldelem.ref through a non-reference type push that type, so the next block is not checked. + + .method static public hidebysig object Propagate.StopAfterLoadIndirectRef_Invalid_StackUnexpected_Validation_Valid(int32& p) cil managed + { + ldarg.0 + ldind.ref + br.s Next + + Next: + ret + } + + .method static public hidebysig object Propagate.StopAfterLoadElementRef_Invalid_StackUnexpected_Validation_Valid(int32[] a) cil managed + { + ldarg.0 + ldc.i4.0 + ldelem.ref + br.s Next + + Next: + ret + } + + .method static public hidebysig int32 Propagate.StopAfterInvalid_Invalid_StackUnexpected() cil managed + { + .locals init (int32 V_0) + ldnull + stloc.0 + br.s Next + + Next: + ret + } + + .method static public hidebysig int32 Propagate.StopAfterUnverifiableAndInvalid_Invalid_Unverifiable.StackUnexpected_Validation_StackUnexpected() cil managed + { + .locals init (int32 V_0) + ldc.i4.8 + localloc + pop + ldnull + stloc.0 + br.s Next + + Next: + ret + } } diff --git a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs index ca8a0fcb5edd8d..d4122f73edbbeb 100644 --- a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs +++ b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs @@ -132,6 +132,17 @@ public void IncrementErrorCount() { ErrorCount++; } + + // Errors that make the IL unverifiable but not invalid leave a valid stack for the successors. + public int UnverifiableErrorCount + { + get; + private set; + } + public void IncrementUnverifiableErrorCount() + { + UnverifiableErrorCount++; + } }; void EmptyTheStack() => _stackTop = 0; @@ -598,33 +609,42 @@ bool Check(bool cond, VerifierError error, StackValue found, StackValue expected void UnverifiableError(VerifierError error) { if (!ValidationOnly) + { VerificationError(error); + _currentBasicBlock?.IncrementUnverifiableErrorCount(); + } } void UnverifiableError(VerifierError error, object found) { if (!ValidationOnly) + { VerificationError(error, found); + _currentBasicBlock?.IncrementUnverifiableErrorCount(); + } } void UnverifiableError(VerifierError error, object found, object expected) { if (!ValidationOnly) + { VerificationError(error, found, expected); + _currentBasicBlock?.IncrementUnverifiableErrorCount(); + } } // Like Check, for a rule that only verifiable IL must follow; validation-only mode reports nothing. bool CheckVerifiable(bool cond, VerifierError error) { - if (!cond && !ValidationOnly) - VerificationError(error); + if (!cond) + UnverifiableError(error); return cond; } bool CheckVerifiable(bool cond, VerifierError error, StackValue found) { - if (!cond && !ValidationOnly) - VerificationError(error, found); + if (!cond) + UnverifiableError(error, found); return cond; } @@ -1162,7 +1182,9 @@ bool CheckIsObjRef(TypeDesc type) if (!IsAssignable(type, GetWellKnownType(WellKnownType.Object), false)) { // TODO: Better error message - UnverifiableError(VerifierError.StackUnexpected, TypeToStringForIsAssignable(type)); + // Not UnverifiableError: ldind.ref and ldelem.ref then push this type, so the block must not pass its stack on. + if (!ValidationOnly) + VerificationError(VerifierError.StackUnexpected, TypeToStringForIsAssignable(type)); return false; } return true; @@ -2239,7 +2261,8 @@ void ImportBranch(ILOpcode opcode, BasicBlock target, BasicBlock fallthrough) void PropagateControlFlow(BasicBlock next, bool isFallthrough) { - if (!IsValidBranchTarget(_currentBasicBlock, next, isFallthrough) || _currentBasicBlock.ErrorCount > 0) + if (!IsValidBranchTarget(_currentBasicBlock, next, isFallthrough) || + _currentBasicBlock.ErrorCount > _currentBasicBlock.UnverifiableErrorCount) return; PropagateThisState(_currentBasicBlock, next);