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

Almost complete sync of logic for generating DAM/RUC reflection access warnings between ILLink and NativeAOT #84770

Merged
merged 12 commits into from
Apr 24, 2023

Conversation

vitek-karas
Copy link
Member

@vitek-karas vitek-karas commented Apr 13, 2023

This enables the last of the disabled RequiresCapability tests for NativeAOT.

The implementation basically copies the logic from ILLink on how to produce warnings when method or field is marked due to reflection access.

We decided that producing warnings due to reflection access to compiler generated methods is more trouble than benefit and thus we won't do that anymore. See reasoning in #85042.

Otherwise the behavior in NativeAOT fully copies the one in ILLink (small exceptions due to better method overload resolution in NativeAOT).

High-level behavior changes in NativeAOT:

  • Finally allow the NativeAOT compiler to produce IL2110, IL2111, IL2113 and IL2115
  • Avoid generating warnings for override methods if the warning is already generated on the base method (for DAM marking)
  • Implement correct marking (and warnings) for "DAM on type" when the derived class has broader annotation than the base class/interface (this is mostly to avoid noise warnings)
  • Rename a set of methods and classes in data flow scanner to "Token", because they're only used when a given entity is accessed through its token (so ldtoken and similar). This allows for more precise handling of the cases above.

Testing changes:

  • Allow specifying tool exceptions for SkipKepItemsValidationAttribute - this allows to enable one of the reflection tests for its diagnostics coverage, avoiding the necessity to fix everything around Kept behavior yet.
  • Couple of fixes in the test validator to correctly compare expected/actual diagnostics

This fixes final bits of #68786
Fixes #68688
This helps with #82447

@ghost
Copy link

ghost commented Apr 13, 2023

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

Issue Details

This enables the last of the disabled RequiresCapability tests for NativeAOT.

The implementation basically copies the logic from ILLink on how to produce warnings when method or field is marked due to reflection access.

This also introduces a new cache into CompilerGeneratedCodeState which can answer the question "Does this compiler generated method require data flow analysis". Note that it is technically a heuristic since it doesn't run the same code as the compiler proper, but it should be very close as it uses the same primitives.

This had to be a new cache since it requires FlowAnnotations (also a cache). Original idea to put it into the existing CompilerGeneratedCodeState cache didn't work, because FlowAnnotations has a dependency on it - which would create a circular dependency between the two caches.

The new cache should end up being very small/cheap, since we only populate it for compiler generated code which is accessed via reflection (should be rare).

There's one missing bit: ILLink has a detection to figure out if a method is an implementation of an interface method and will suppress warnings due to annotations on such method, since the annotation on the interface methods must match. This effectively avoid duplicate warning generation. The NativeAOT implementation doesn't do this for interfaces (only for overrides of base class mehods).

This fixes final bits of #68786
This helps with #82447

Author: vitek-karas
Assignees: vitek-karas
Labels:

area-NativeAOT-coreclr

Milestone: 8.0.0

Copy link
Member

@MichalStrehovsky MichalStrehovsky left a comment

Choose a reason for hiding this comment

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

Left a question. I didn't review the algorithm in detail because it's rather complicated and I already lack quite some context. I still wish we just ignored the fact that compiler-generated things can be reflected on and left it as a wart.


Method = method;

bool requiresDataflowProcessing = ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForMethodBody(flowAnnotations, method);
Copy link
Member

Choose a reason for hiding this comment

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

Is there any reason to introduce a local here instead of setting RequiresDataflow directly? We could add an early out after processing an instruction as well.

Comment on lines 671 to 672
public MethodDesc Method;
public bool RequiresDataflow;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public MethodDesc Method;
public bool RequiresDataflow;
public readonly MethodDesc Method;
public readonly bool RequiresDataflow;

Comment on lines 759 to 760
if (_dataflowRequirementHashtable == null)
throw new InvalidOperationException();
Copy link
Member

Choose a reason for hiding this comment

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

This looks unnecessary. It's not a public API.

=> new DataflowRequirementValue(key, _flowAnnotations);
}

public void InitializeFlowAnnotations(FlowAnnotations flowAnnotations)
Copy link
Member

Choose a reason for hiding this comment

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

I wonder - could we avoid having this double initialization?

Can we for example move the logic that is getting added to CompilerGeneratedState somewhere else? Can this just be part of the regular FlowAnnotations state? And if not, could this logic be moved to a CompilerGeneratedState subclass (that is a nested type of FlowAnnotations) and change FlowAnnotations ctor to just new it up (instead of taking it as a parameter)?

@vitek-karas
Copy link
Member Author

I must admit I'm starting to agree that we should probably give up on at least some of this. For example I found out that ILLink doesn't warn in all cases of accessing compiler generated code, even when it technically should.
When ILLink detects if a method is potentially dangerous it doesn't detect usage of annotated generics - so if there's a method which only calls for example new Lazy<T>() it won't produce a warning, even though it's a potential hole since we can't guarantee that T will have default .ctor in this case.

I'm going to look into a couple more cases which popped up, just to make sure we're not missing something bigger first though.

@vitek-karas
Copy link
Member Author

/azp run runtime-extra-platforms

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@vitek-karas
Copy link
Member Author

This is ready for review.

I updated the comment to match the implemented behavior.

All the CI failures are unrelated.

Copy link
Member

@MichalStrehovsky MichalStrehovsky left a comment

Choose a reason for hiding this comment

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

Thank you!

@vitek-karas vitek-karas merged commit f8eb926 into dotnet:main Apr 24, 2023
@vitek-karas vitek-karas deleted the AdvancedDAMWarnings branch April 24, 2023 10:34
@ghost ghost locked as resolved and limited conversation to collaborators May 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

NativeAOT doesnt produce Requires warnings in Generics
2 participants