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

Use SRM's TypeName parser in ILLink #103740

Merged
merged 4 commits into from
Jun 25, 2024
Merged

Use SRM's TypeName parser in ILLink #103740

merged 4 commits into from
Jun 25, 2024

Conversation

sbomer
Copy link
Member

@sbomer sbomer commented Jun 20, 2024

This replaces the type name parser we copied from corert with the one in System.Reflection.Metadata.

Adds a test for resolving escaped type names, shared by ILLink and Native AOT. To support this testcase, the test infra also uses TypeNameResolver. The ILLink tests use it via IVT, and the Native AOT tests use it by including source files. The sources have been factored a little to make it possible to use just the TypeNameResolver from NativeAot without pulling in other components of ILLink.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jun 20, 2024
@dotnet-policy-service dotnet-policy-service bot added the linkable-framework Issues associated with delivering a linker friendly framework label Jun 20, 2024
@am11 am11 added area-Tools-ILLink .NET linker development as well as trimming analyzers and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jun 20, 2024
Copy link
Contributor

Tagging subscribers to this area: @agocke, @sbomer, @vitek-karas
See info in area-owners.md if you want to be subscribed.

readonly ITryResolveMetadata _metadataResolver;
readonly ITryResolveAssemblyName _assemblyResolver;

private static readonly TypeNameParseOptions s_typeNameParseOptions = new () { MaxNodes = int.MaxValue };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @adamsitnik @GrabYourPitchforks So far every single real-world use of TypeName.Parse except the one in NRBF overrides the low MaxNodes default with int.MaxValue. It makes me wonder whether we made the right choice for the default.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I just copied this default from the type name parser used in Native AOT.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int.MaxValue is the right value to use for tools like ILLinker. The tools like ILLinker are not hardened against too complex input. It is fine for these tools to crash with StackOverflowException or take a long time to run when the input is too complex.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far every single real-world use of TypeName.Parse except the one in NRBF overrides the low MaxNodes default with int.MaxValue. It makes me wonder whether we made the right choice for the default.

From my perspective, we wanted to be secure by default and this was the right choice.

IMO int.MaxValue is never the right value, as it is impossible to create such complex type names (runtime is unable to load them). For Runtime and its tools we should rather find a value that is large enough to allow for the most complex real-life types, but not big enough to cause SO.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Runtime and its tools we should rather find a value that is large enough to allow for the most complex real-life types, but not big enough to cause SO.

There is no point in doing that unless we make the runtime and tools hardened against too complex types. There are 1000 different ways how too complex types can introduce stackoverflow in the runtime and tools. Reducing that by one does not really help.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reducing that by one does not really help.

It does not solve the problem, but on the other hand it's almost free (the logic is implemented, it's just a matter of changing one value in few places) and it would not hurt.

But, I don't have the perspective of long term thinking about the .NET Runtime you have, so I am most probably missing a lot of details (like giving the users a false impression that Type.GetType(string) is hardened against all kinds of unsafe inputs)

sbomer added 2 commits June 20, 2024 19:59
Some tests have exported types that are kept even though the types
they point to are removed. The ResultCheckr resolution logic should not throw
in that case.
Copy link
Member

@jtschuster jtschuster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM aside from a couple test nits, thank you!

Comment on lines +1473 to +1475
if (!originalsTypeNameResolver.TryResolveTypeName (originalTargetAssembly, expectedTypeName, out TypeReference? expectedTypeRef, out _))
Assert.Fail($"Could not resolve original type `{expectedTypeName}' in assembly {assemblyName}");
TypeDefinition expectedType = expectedTypeRef.Resolve ();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Append to the errs list rather than failing immediately.

Suggested change
if (!originalsTypeNameResolver.TryResolveTypeName (originalTargetAssembly, expectedTypeName, out TypeReference? expectedTypeRef, out _))
Assert.Fail($"Could not resolve original type `{expectedTypeName}' in assembly {assemblyName}");
TypeDefinition expectedType = expectedTypeRef.Resolve ();
if (!originalsTypeNameResolver.TryResolveTypeName (originalTargetAssembly, expectedTypeName, out TypeReference? expectedTypeRef, out _)) {
errs.Add($"Could not resolve original type `{expectedTypeName}' in assembly {assemblyName}");
continue;
}
TypeDefinition expectedType = expectedTypeRef.Resolve ();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep this one as an assert because it indicates a test authoring issue, and this matches what we do in some other places that fail to resolve something from the input.

- Remove TestResolver.Instance
- Test type name with '/'
@sbomer sbomer merged commit afe6fbd into dotnet:main Jun 25, 2024
111 of 113 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Jul 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Tools-ILLink .NET linker development as well as trimming analyzers linkable-framework Issues associated with delivering a linker friendly framework
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants