Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System.Security.VerificationException (type argument violates the constraint of type parameter) when running code on net 5.0 #45600

Closed
GureevLeonid opened this issue Dec 4, 2020 · 16 comments · Fixed by #47258
Assignees
Milestone

Comments

@GureevLeonid
Copy link

Description

Hi, we have encountered strange exception after one of ours project's target framework has been upgraded from netcoreapp3.1 to net5.0.

This is the exception message:

Unhandled exception. System.Security.VerificationException: Method ConsoleApp1.ComponentRegistry`1[ConsoleApp1.NamedObjectComponent`1[TNamedObject]].Register: type argument 'TComponentA' violates the constraint of type parameter 'TComponent'.
   at ConsoleApp1.SingleTypeNamedObjectContainer`1.Register[TComponentA]()
   at ConsoleApp1.NamedObjectContainer.Register[TNamedObject,TComponent]() in /Users/gureev/RiderProjects/ConsoleApp1/ConsoleApp1/Program.cs:line 87
   at ConsoleApp1.Program.Main(String[] args) in /Users/gureev/RiderProjects/ConsoleApp1/ConsoleApp1/Program.cs:line 108

I made simple console app to reproduce it:

    class Program
    {
		public abstract class NamedObject { }

		public class FooNamedObject : NamedObject { }

		public abstract class NamedObjectComponent { }

		public abstract class NamedObjectComponent<TNamedObject> : NamedObjectComponent where TNamedObject : NamedObject { }

		public class FooNamedObjectComponent : NamedObjectComponent<FooNamedObject> { }

		public abstract class SingleTypeNamedObjectContainer
		{
			internal SingleTypeNamedObjectContainer() { }

			internal abstract Type NamedObjectType { get; }
		}

		public class SingleTypeNamedObjectContainer<TNamedObject> : SingleTypeNamedObjectContainer
			where TNamedObject : NamedObject
		{
			private readonly ComponentRegistry<NamedObjectComponent<TNamedObject>> components =
				new ComponentRegistry<NamedObjectComponent<TNamedObject>>();

			internal override Type NamedObjectType => typeof(TNamedObject);

			public void Register<TComponentA>()
				where TComponentA : NamedObjectComponent<TNamedObject>, new()
			{
				components.Register<TComponentA>();
			}
		}

		public class ComponentRegistry<TBaseComponent> where TBaseComponent : class
		{
			private readonly HashSet<Type> componentTypes = new HashSet<Type>();

			private readonly Dictionary<Type, Func<TBaseComponent>> componentFactories =
				new Dictionary<Type, Func<TBaseComponent>>();

			public void Register<TComponent>()
				where TComponent : class, TBaseComponent, new()
			{
				Register(() => new TComponent());
			}

			public void Register<TComponent>(Func<TComponent> componentFactory)
				where TComponent : class, TBaseComponent
			{
				componentTypes.Add(typeof(TComponent));
				componentFactories.Add(typeof(TComponent), componentFactory);
			}
		}

		public class NamedObjectContainer
		{
			private readonly Dictionary<Type, SingleTypeNamedObjectContainer> subcontainersByNamedObjectType =
				new Dictionary<Type, SingleTypeNamedObjectContainer>();

			private readonly Dictionary<Type, SingleTypeNamedObjectContainer> subcontainersByRegisteredType =
				new Dictionary<Type, SingleTypeNamedObjectContainer>();

			public SingleTypeNamedObjectContainer<TNamedObject> RegisterNamedObjectType<TNamedObject>()
				where TNamedObject : NamedObject
			{
				return RegisterSubcontainer(new SingleTypeNamedObjectContainer<TNamedObject>());
			}

			public TSubcontainer RegisterSubcontainer<TSubcontainer>(TSubcontainer subcontainer)
				where TSubcontainer : SingleTypeNamedObjectContainer
			{
				subcontainersByNamedObjectType.Add(subcontainer.NamedObjectType, subcontainer);
				subcontainersByRegisteredType.Add(typeof(TSubcontainer), subcontainer);
				return subcontainer;
			}

			internal void Register<TNamedObject, TComponent>()
				where TNamedObject : NamedObject
				where TComponent : NamedObjectComponent<TNamedObject>, new()
			{
				GetSubcontainerFor<TNamedObject>().Register<TComponent>();
			}

			public SingleTypeNamedObjectContainer<TNamedObject> GetSubcontainerFor<TNamedObject>()
				where TNamedObject : NamedObject
			{
				return (SingleTypeNamedObjectContainer<TNamedObject>)GetSubcontainerFor(typeof(TNamedObject));
			}

			public SingleTypeNamedObjectContainer GetSubcontainerFor(Type baseNamedObjectType)
			{
				return subcontainersByNamedObjectType[baseNamedObjectType];
			}
		}
		
		static void Main(string[] args)
		{
			var contaner = new NamedObjectContainer();
			contaner.RegisterNamedObjectType<FooNamedObject>();
			contaner.Register<FooNamedObject, FooNamedObjectComponent>();
		}		
	}

If you run this code with target netcoreapp3.1 - it works.
But if you run with target netcoreapp5.0 - exception throws.

Configuration

Dotnet sdk 5.0.100
OS: MacOS Catalina (x64), Windows 10 1903 (x64)

Regression?

Yes, works on 3.1.

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Dec 4, 2020
@Dotnet-GitSync-Bot
Copy link
Collaborator

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost
Copy link

ghost commented Dec 4, 2020

Tagging subscribers to this area: @bartonjs, @vcsjones, @krwq
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

Hi, we have encountered strange exception after one of ours project's target framework has been upgraded from netcoreapp3.1 to net5.0.

This is the exception message:

Unhandled exception. System.Security.VerificationException: Method ConsoleApp1.ComponentRegistry`1[ConsoleApp1.NamedObjectComponent`1[TNamedObject]].Register: type argument 'TComponentA' violates the constraint of type parameter 'TComponent'.
   at ConsoleApp1.SingleTypeNamedObjectContainer`1.Register[TComponentA]()
   at ConsoleApp1.NamedObjectContainer.Register[TNamedObject,TComponent]() in /Users/gureev/RiderProjects/ConsoleApp1/ConsoleApp1/Program.cs:line 87
   at ConsoleApp1.Program.Main(String[] args) in /Users/gureev/RiderProjects/ConsoleApp1/ConsoleApp1/Program.cs:line 108

I made simple console app to reproduce it:

    class Program
    {
		public abstract class NamedObject { }

		public class FooNamedObject : NamedObject { }

		public abstract class NamedObjectComponent { }

		public abstract class NamedObjectComponent<TNamedObject> : NamedObjectComponent where TNamedObject : NamedObject { }

		public class FooNamedObjectComponent : NamedObjectComponent<FooNamedObject> { }

		public abstract class SingleTypeNamedObjectContainer
		{
			internal SingleTypeNamedObjectContainer() { }

			internal abstract Type NamedObjectType { get; }
		}

		public class SingleTypeNamedObjectContainer<TNamedObject> : SingleTypeNamedObjectContainer
			where TNamedObject : NamedObject
		{
			private readonly ComponentRegistry<NamedObjectComponent<TNamedObject>> components =
				new ComponentRegistry<NamedObjectComponent<TNamedObject>>();

			internal override Type NamedObjectType => typeof(TNamedObject);

			public void Register<TComponentA>()
				where TComponentA : NamedObjectComponent<TNamedObject>, new()
			{
				components.Register<TComponentA>();
			}
		}

		public class ComponentRegistry<TBaseComponent> where TBaseComponent : class
		{
			private readonly HashSet<Type> componentTypes = new HashSet<Type>();

			private readonly Dictionary<Type, Func<TBaseComponent>> componentFactories =
				new Dictionary<Type, Func<TBaseComponent>>();

			public void Register<TComponent>()
				where TComponent : class, TBaseComponent, new()
			{
				Register(() => new TComponent());
			}

			public void Register<TComponent>(Func<TComponent> componentFactory)
				where TComponent : class, TBaseComponent
			{
				componentTypes.Add(typeof(TComponent));
				componentFactories.Add(typeof(TComponent), componentFactory);
			}
		}

		public class NamedObjectContainer
		{
			private readonly Dictionary<Type, SingleTypeNamedObjectContainer> subcontainersByNamedObjectType =
				new Dictionary<Type, SingleTypeNamedObjectContainer>();

			private readonly Dictionary<Type, SingleTypeNamedObjectContainer> subcontainersByRegisteredType =
				new Dictionary<Type, SingleTypeNamedObjectContainer>();

			public SingleTypeNamedObjectContainer<TNamedObject> RegisterNamedObjectType<TNamedObject>()
				where TNamedObject : NamedObject
			{
				return RegisterSubcontainer(new SingleTypeNamedObjectContainer<TNamedObject>());
			}

			public TSubcontainer RegisterSubcontainer<TSubcontainer>(TSubcontainer subcontainer)
				where TSubcontainer : SingleTypeNamedObjectContainer
			{
				subcontainersByNamedObjectType.Add(subcontainer.NamedObjectType, subcontainer);
				subcontainersByRegisteredType.Add(typeof(TSubcontainer), subcontainer);
				return subcontainer;
			}

			internal void Register<TNamedObject, TComponent>()
				where TNamedObject : NamedObject
				where TComponent : NamedObjectComponent<TNamedObject>, new()
			{
				GetSubcontainerFor<TNamedObject>().Register<TComponent>();
			}

			public SingleTypeNamedObjectContainer<TNamedObject> GetSubcontainerFor<TNamedObject>()
				where TNamedObject : NamedObject
			{
				return (SingleTypeNamedObjectContainer<TNamedObject>)GetSubcontainerFor(typeof(TNamedObject));
			}

			public SingleTypeNamedObjectContainer GetSubcontainerFor(Type baseNamedObjectType)
			{
				return subcontainersByNamedObjectType[baseNamedObjectType];
			}
		}
		
		static void Main(string[] args)
		{
			var contaner = new NamedObjectContainer();
			contaner.RegisterNamedObjectType<FooNamedObject>();
			contaner.Register<FooNamedObject, FooNamedObjectComponent>();
		}		
	}

If you run this code with target netcoreapp3.1 - it works.
But if you run with target netcoreapp5.0 - exception throws.

Configuration

Dotnet sdk 5.0.100
OS: MacOS Catalina (x64), Windows 10 1903 (x64)

Regression?

Yes, works on 3.1.

Author: GureevLeonid
Assignees: -
Labels:

area-System.Security, untriaged

Milestone: -

@vcsjones
Copy link
Member

vcsjones commented Dec 4, 2020

Hmm, VerificationException, if I understand correctly, means that something or unverifiable at runtime. @janvorli might you know best how to proceed here?

@janvorli
Copy link
Member

janvorli commented Dec 4, 2020

Let me run the repro locally and see where the exception stems from in the native runtime.

@GureevLeonid
Copy link
Author

@janvorli Hi, is there any news?

@janvorli
Copy link
Member

I have looked into it three weeks ago, but I wasn't able to figure out what's wrong right away and then got side tracked by other high priority work and Christmas holidays. It is still on my list to get to as soon as possible.

@janvorli janvorli self-assigned this Jan 5, 2021
@janvorli
Copy link
Member

janvorli commented Jan 9, 2021

I have debugged the problem and discovered a weird behavior in MethodDesc::SatisfiesMethodConstraints where it somehow incorrectly ends up checking NamedObjectComponent<NamedObjectComponent<TNamedObject>> instead of NamedObjectComponent<TNamedObject>. It turns out it was caused by PR #34889 that added passing instantiation context to the SatisfiesConstraints method for the purpose of default interface methods. The instantiatoon context is wrong in this specific case. Reverting the change from that PR fixes the problem.
cc: @MichalStrehovsky

@janvorli
Copy link
Member

cc: @mangod9 - I've thought the change was made by Michal, but I've just noticed it was your change.

@mangod9
Copy link
Member

mangod9 commented Jan 11, 2021

Thanks Jan, I will take a look. @GureevLeonid is this blocking your move to 5, or do you have a workaround for this?

@GureevLeonid
Copy link
Author

@mangod9 Yes, this issue is blocking us.
The only workaround i know is to rewrite code - to remove some generic constraints and to use runtime type checking. I'm pretty sure we will have the same issue in runtime in other places in code (we use generics all over the place). So i'm pretty sure it's not the way we want to fix it.

@janvorli janvorli removed the untriaged New issue has not been triaged by the area owner label Jan 12, 2021
@mangod9 mangod9 added this to the 6.0.0 milestone Jan 12, 2021
@mangod9
Copy link
Member

mangod9 commented Jan 18, 2021

I debugged the issue further and duplicate instantiation comes from here:

Instantiation(thisinst, ntypars),

With the following call stack. @MichalStrehovsky might need your help on this. Thx

coreclr.dll!SigPointer::GetTypeHandleThrowing(Module   * pModule, const SigTypeContext * pTypeContext, ClassLoader::LoadTypesFlag   fLoadTypes, ClassLoadLevel level, int dropGenericArgumentLevel, const   Substitution * pSubst, const ZapSig::Context * pZapSigContext) Line 1431
coreclr.dll!LoadTypeVarConstraint(TypeVarTypeDesc   * pTypeVar, unsigned int tkConstraint, const InstantiationContext *   pInstContext) Line 1419
coreclr.dll!GatherConstraintsRecursive(TypeVarTypeDesc   * pTyArg, ArrayList * pArgList, const InstantiationContext * pInstContext,   TypeHandleList * pVisitedVars) Line 1688
coreclr.dll!TypeVarTypeDesc::SatisfiesConstraints(SigTypeContext   * pTypeContextOfConstraintDeclarer, TypeHandle thArg, const   InstantiationContext * pInstContext) Line 1807
coreclr.dll!MethodDesc::SatisfiesMethodConstraints(TypeHandle   thParent, int fThrowIfNotSatisfied) Line 1682
coreclr.dll!InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable   * pExactMT, MethodDesc * pGenericMDescInRepMT, MethodDesc * pWrappedMD,   Instantiation methodInst, int getWrappedCode) Line 468
coreclr.dll!MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc   * pDefMD, MethodTable * pExactMT, int forceBoxedEntryPoint, Instantiation   methodInst, int allowInstParam, int forceRemotableMethod, int allowCreate,   ClassLoadLevel level) Line 1188
coreclr.dll!CEEInfo::getCallInfo(CORINFO_RESOLVED_TOKEN   * pResolvedToken, CORINFO_RESOLVED_TOKEN * pConstrainedResolvedToken,   CORINFO_METHOD_STRUCT_ * callerHandle, CORINFO_CALLINFO_FLAGS flags,   CORINFO_CALL_INFO * pResult) Line 5605
clrjit.dll!Compiler::eeGetCallInfo(CORINFO_RESOLVED_TOKEN   * pResolvedToken, CORINFO_RESOLVED_TOKEN * pConstrainedToken,   CORINFO_CALLINFO_FLAGS flags, CORINFO_CALL_INFO * pResult) Line 35

@MichalStrehovsky
Copy link
Member

With the following call stack. @MichalStrehovsky might need your help on this. Thx

I haven't done work in this part of the type system so I don't really know about it without setting up a repro and debugging through that. I think @davidwrighton would know more. This part of the CoreCLR type system is very different from the managed type system that I know more about.

@davidwrighton
Copy link
Member

I'm taking a look today. Hopefully, I'll have something for the thread by EOD.

@davidwrighton
Copy link
Member

I've come up with what appears to be a fix although I think it needs a bit more testing, and the test case needs some additional work.

@davidwrighton
Copy link
Member

@GureevLeonid we would like permission to include code derived from your test case as a test in our testbed. May we do that?

@GureevLeonid
Copy link
Author

@GureevLeonid we would like permission to include code derived from your test case as a test in our testbed. May we do that?

Yes, sure.

davidwrighton added a commit that referenced this issue Mar 19, 2021
…aint satisfaction (#47258)

- The constraint processing logic in the runtime conflates the idea of constraint checking on open, closed over concrete types, and closed over non-concrete types
- This change adds a tweak to avoid using an instantiation context that isn't related to the type variable being instantiated

Fixes issue #45600 and adds a regression test from the customer.
@ghost ghost locked as resolved and limited conversation to collaborators Apr 18, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.